From: Alex Rousskov Date: Thu, 3 Apr 2008 05:31:29 +0000 (-0600) Subject: Added an adaptation service group API to support groups of services. Current X-Git-Tag: SQUID_3_1_0_1~49^2~302^2~18 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=62c7f90e68bf54a3699bda369795e9f2a6b781c0;p=thirdparty%2Fsquid.git Added an adaptation service group API to support groups of services. Current code supports service sets and single-service groups. The former provides a way to group interchangeable services together so that one (the "best" available) service is applied to the message. The latter is an internal feature to allow user to mix service and group names in squid.conf ACLs. TODO: support service chains (as a service group) and perhaps group of groups. Moved adaptation access rule state from being shared between ICAPClass and ICAPAccessCheck classes into a dedicated AccessRule class. This simplifies both classes and allows for checking access rules in the correct order. Use group names when linking adaptation access rules to groups. This is less efficient (especially since we are still using Vector iteration to find a matching group) but much simpler. TODO: Optimize. All adaptation services, access rules, and service groups now have a finalize() method that is called after configuration parsing has been completed but before the main loop starts. This allows to verify or optimize name-based links to other services, rules, and groups. Moved adaptation access check and group classes into their own files. Added adaptation_service_set squid.conf option, deprecating icap_class. The new option has more accurate documentation and does not depend on the adaptation protocol so one can group eCAP and ICAP services. Added adaptation_service_set squid.conf option, deprecating icap_access. The new option has more accurate documentation and does not depend on the adaptation protocol so one can mix-and-match eCAP and ICAP ACL rules. --- diff --git a/src/ICAP/ICAPConfig.cc b/src/ICAP/ICAPConfig.cc index b485877607..36118ff94f 100644 --- a/src/ICAP/ICAPConfig.cc +++ b/src/ICAP/ICAPConfig.cc @@ -62,7 +62,7 @@ Adaptation::ServicePointer ICAPConfig::createService(const Adaptation::ServiceConfig &cfg) { ICAPServiceRep::Pointer s = new ICAPServiceRep(cfg); - s->finalize(s); + s->setSelf(s); return s.getRaw(); } diff --git a/src/ICAP/ICAPServiceRep.cc b/src/ICAP/ICAPServiceRep.cc index b16e00f999..c8de132f43 100644 --- a/src/ICAP/ICAPServiceRep.cc +++ b/src/ICAP/ICAPServiceRep.cc @@ -29,14 +29,18 @@ ICAPServiceRep::~ICAPServiceRep() changeOptions(0); } -bool -ICAPServiceRep::finalize(Pointer &aSelf) +void +ICAPServiceRep::setSelf(Pointer &aSelf) { assert(!self && aSelf != NULL); self = aSelf; +} - if (!Adaptation::Service::finalize()) - return false; +void +ICAPServiceRep::finalize() +{ + Adaptation::Service::finalize(); + assert(self != NULL); // use /etc/services or default port if needed const bool have_port = cfg().port >= 0; @@ -49,8 +53,6 @@ ICAPServiceRep::finalize(Pointer &aSelf) writeableCfg().port = 1344; } } - - return true; } void ICAPServiceRep::invalidate() diff --git a/src/ICAP/ICAPServiceRep.h b/src/ICAP/ICAPServiceRep.h index 6f1d54f94d..ea9bd00ee2 100644 --- a/src/ICAP/ICAPServiceRep.h +++ b/src/ICAP/ICAPServiceRep.h @@ -83,7 +83,9 @@ public: ICAPServiceRep(const Adaptation::ServiceConfig &config); virtual ~ICAPServiceRep(); - bool finalize(Pointer &aSelf); // needs self pointer for ICAPOptXact + void setSelf(Pointer &aSelf); // needs self pointer for ICAPOptXact + virtual void finalize(); + void invalidate(); // call when the service is no longer needed or valid bool probed() const; // see comments above diff --git a/src/Server.cc b/src/Server.cc index 489dc2006e..e9bc416bd0 100644 --- a/src/Server.cc +++ b/src/Server.cc @@ -40,7 +40,7 @@ #include "errorpage.h" #if USE_ADAPTATION -#include "adaptation/Service.h" +#include "adaptation/AccessCheck.h" #include "ICAP/ICAPConfig.h" extern ICAPConfig TheICAPConfig; #endif diff --git a/src/adaptation/AccessCheck.cc b/src/adaptation/AccessCheck.cc new file mode 100644 index 0000000000..1cc57331bf --- /dev/null +++ b/src/adaptation/AccessCheck.cc @@ -0,0 +1,234 @@ +#include "squid.h" +#include "structs.h" + +#include "ConfigParser.h" +#include "ACL.h" +#include "HttpRequest.h" +#include "HttpReply.h" +#include "ACLChecklist.h" +#include "adaptation/Service.h" +#include "adaptation/ServiceGroups.h" +#include "adaptation/AccessRule.h" +#include "adaptation/AccessCheck.h" + + +cbdata_type Adaptation::AccessCheck::CBDATA_AccessCheck = CBDATA_UNKNOWN; + +Adaptation::AccessCheck::AccessCheck(Method aMethod, + VectPoint aPoint, + HttpRequest *aReq, + HttpReply *aRep, + AccessCheckCallback *aCallback, + void *aCallbackData): AsyncJob("AccessCheck"), done(FALSE) +{ + // TODO: assign these at creation time + + method = aMethod; + point = aPoint; + + req = HTTPMSGLOCK(aReq); + rep = aRep ? HTTPMSGLOCK(aRep) : NULL; + + callback = aCallback; + + callback_data = cbdataReference(aCallbackData); + + acl_checklist = NULL; + + debugs(93, 5, "AccessCheck constructed for " << methodStr(method) << " " << vectPointStr(point)); +} + +Adaptation::AccessCheck::~AccessCheck() +{ + HTTPMSGUNLOCK(req); + HTTPMSGUNLOCK(rep); +} + +/* + * Walk the access rules list and find all classes that have at least + * one service with matching method and vectoring point. + */ +void +Adaptation::AccessCheck::check() +{ + debugs(93, 4, "Adaptation::AccessCheck::check"); + + typedef AccessRules::iterator ARI; + for (ARI i = AllRules().begin(); i != AllRules().end(); ++i) { + + /* + * We only find the first matching service because we only need + * one matching service to justify ACL-checking a class. We might + * use other services belonging to the class if the first service + * turns out to be unusable for some reason. + */ + AccessRule *r = *i; + ServicePointer service = findBestService(*r, false); + if (service != NULL) { + debugs(93, 5, "Adaptation::AccessCheck::check: rule '" << r->id << "' has candidate service '" << service->cfg().key << "'"); + candidates += r->id; + } + } + + checkCandidates(); +} + +// XXX: Here and everywhere we call FindRule(topCandidate()): +// Once we identified the candidate, we should not just ignore it +// if reconfigure changes rules. We should either lock the rule to +// prevent reconfigure from stealing it or restart the check with +// new rules. Throwing an exception may also be appropriate. +void +Adaptation::AccessCheck::checkCandidates() +{ + debugs(93, 3, "Adaptation::AccessCheck checks " << candidates.size()); + + while (!candidates.empty()) { + if (AccessRule *r = FindRule(topCandidate())) { + // XXX: we do not have access to conn->rfc931 here. + acl_checklist = aclChecklistCreate(r->acl, req, dash_str); + acl_checklist->nonBlockingCheck(AccessCheckCallbackWrapper, this); + return; + } + + candidates.shift(); // the rule apparently went away (reconfigure) + } + + // when there are no canidates, fake answer 1 + debugs(93, 3, "Adaptation::AccessCheck::check: NO candidates left"); + noteAnswer(1); +} + +void +Adaptation::AccessCheck::AccessCheckCallbackWrapper(int answer, void *data) +{ + debugs(93, 8, "AccessCheckCallbackWrapper: answer=" << answer); + AccessCheck *ac = (AccessCheck*)data; + ac->noteAnswer(answer); +} + +void +Adaptation::AccessCheck::noteAnswer(int answer) +{ + debugs(93, 5, HERE << "AccessCheck::noteAnswer " << answer); + if (candidates.size()) + debugs(93, 5, HERE << "was checking " << topCandidate()); + + if (!answer) { + checkCandidates(); + return; + } + + /* + * We use an event here to break deep function call sequences + */ + // XXX: use AsyncCall for callback and remove + CallJobHere(93, 5, this, Adaptation::AccessCheck::do_callback); +} + +void +Adaptation::AccessCheck::do_callback() +{ + debugs(93, 3, "Adaptation::AccessCheck::do_callback"); + + if (candidates.size()) + debugs(93, 3, HERE << "was checking rule" << topCandidate()); + + void *validated_cbdata; + if (!cbdataReferenceValidDone(callback_data, &validated_cbdata)) { + debugs(93,3,HERE << "do_callback: callback_data became invalid, skipping"); + return; + } + + ServicePointer service = NULL; + if (candidates.size()) { + if (AccessRule *r = FindRule(topCandidate())) { + service = findBestService(*r, true); + if (service != NULL) + debugs(93,3,HERE << "do_callback: with service " << service->cfg().uri); + else + debugs(93,3,HERE << "do_callback: no service for rule" << r->id); + } else { + debugs(93,3,HERE << "do_callback: no rule" << topCandidate()); + } + candidates.shift(); // done with topCandidate() + } else { + debugs(93,3,HERE << "do_callback: no candidate rules"); + } + + callback(service, validated_cbdata); + done = TRUE; +} + +Adaptation::ServicePointer +Adaptation::AccessCheck::findBestService(AccessRule &r, bool preferUp) { + + const char *what = preferUp ? "up " : ""; + debugs(93,7,HERE << "looking for the first matching " << + what << "service in group " << r.groupId); + + ServicePointer secondBest; + + ServiceGroup *g = FindGroup(r.groupId); + + if (!g) { + debugs(93,5,HERE << "lost " << r.groupId << " group in rule" << r.id); + return ServicePointer(); + } + + ServiceGroup::Loop loop(g->initialServices()); + typedef ServiceGroup::iterator SGI; + for (SGI i = loop.begin; i != loop.end; ++i) { + + ServicePointer service = FindService(*i); + + if (!service) + continue; + + if (method != service->cfg().method) + continue; + + if (point != service->cfg().point) + continue; + + // sending a message to a broken service is likely to cause errors + if (service->cfg().bypass && service->broken()) + continue; + + if (service->up()) { + // sending a message to a service that does not want it is useless + // note that we cannot check wantsUrl for service that is not "up" + // note that even essential services are skipped on unwanted URLs! + if (!service->wantsUrl(req->urlpath)) + continue; + } else { + if (!secondBest) + secondBest = service; + if (preferUp) { + // the caller asked for an "up" service and we can bypass this one + if (service->cfg().bypass) + continue; + debugs(93,5,HERE << "cannot skip an essential down service"); + what = "down-but-essential "; + } + } + + debugs(93,5,HERE << "found first matching " << + what << "service for " << r.groupId << " group in rule" << r.id << + ": " << service->cfg().key); + + return service; + } + + if (secondBest != NULL) { + what = "down "; + debugs(93,5,HERE << "found first matching " << + what << "service for " << r.groupId << " group in rule" << r.id << + ": " << secondBest->cfg().key); + return secondBest; + } + + debugs(93,5,HERE << "found no matching " << + what << "services for " << r.groupId << " group in rule" << r.id); + return ServicePointer(); +} diff --git a/src/adaptation/AccessCheck.h b/src/adaptation/AccessCheck.h new file mode 100644 index 0000000000..418c85b703 --- /dev/null +++ b/src/adaptation/AccessCheck.h @@ -0,0 +1,60 @@ +#ifndef SQUID_ADAPTATION__ACCESS_H +#define SQUID_ADAPTATION__ACCESS_H + +#include "ICAP/AsyncJob.h" +#include "adaptation/Elements.h" +#include "adaptation/forward.h" + +class HttpRequest; +class HttpReply; + +namespace Adaptation { + +class AccessRule; + +// checks adaptation_access rules to find a matching adaptation service +class AccessCheck: public virtual AsyncJob +{ + +public: + typedef void AccessCheckCallback(ServicePointer match, void *data); + AccessCheck(Method, VectPoint, HttpRequest *, HttpReply *, AccessCheckCallback *, void *); + ~AccessCheck(); + +private: + Method method; + VectPoint point; + HttpRequest *req; + HttpReply *rep; + AccessCheckCallback *callback; + void *callback_data; + ACLChecklist *acl_checklist; + + typedef int Candidate; + typedef Vector Candidates; + Candidates candidates; + Candidate topCandidate() { return *candidates.begin(); } + + void do_callback(); + ServicePointer findBestService(AccessRule &r, bool preferUp); + bool done; + +public: + void check(); + void checkCandidates(); + static void AccessCheckCallbackWrapper(int, void*); +#if 0 + static EVH AccessCheckCallbackEvent; +#endif + void noteAnswer(int answer); + +//AsyncJob virtual methods + virtual bool doneAll() const { return AsyncJob::doneAll() && done;} + +private: + CBDATA_CLASS2(AccessCheck); +}; + +} // namespace Adaptation + +#endif /* SQUID_ADAPTATION__ACCESS_H */ diff --git a/src/adaptation/AccessRule.cc b/src/adaptation/AccessRule.cc new file mode 100644 index 0000000000..c62d81948e --- /dev/null +++ b/src/adaptation/AccessRule.cc @@ -0,0 +1,63 @@ +#include "squid.h" +#include "structs.h" + +#include "ConfigParser.h" +#include "ACL.h" +#include "adaptation/AccessRule.h" +#include "adaptation/Service.h" +#include "adaptation/ServiceGroups.h" + + +int Adaptation::AccessRule::LastId = 0; + +Adaptation::AccessRule::AccessRule(): id(++LastId), acl(NULL) +{ +} + +Adaptation::AccessRule::~AccessRule() +{ + // XXX: leaking acls here? +} + +void +Adaptation::AccessRule::parse(ConfigParser &parser) +{ + ConfigParser::ParseString(&groupId); + aclParseAccessLine(parser, &acl); +} + +void +Adaptation::AccessRule::finalize() +{ + if (!group()) { + debugs(93,0, "ERROR: Unknown adaptation service or group name: '" << + groupId << "'"); // TODO: fail on failures + } +} + +Adaptation::ServiceGroup * +Adaptation::AccessRule::group() +{ + return FindGroup(groupId); +} + + +Adaptation::AccessRules & +Adaptation::AllRules() +{ + static AccessRules TheRules; + return TheRules; +} + +// TODO: make AccessRules::find work +Adaptation::AccessRule * +Adaptation::FindRule(const AccessRule::Id &id) +{ + typedef AccessRules::iterator ARI; + for (ARI i = AllRules().begin(); i != AllRules().end(); ++i) { + if ((*i)->id == id) + return *i; + } + + return NULL; +} diff --git a/src/adaptation/AccessRule.h b/src/adaptation/AccessRule.h new file mode 100644 index 0000000000..6d4b84e9a2 --- /dev/null +++ b/src/adaptation/AccessRule.h @@ -0,0 +1,40 @@ +#ifndef SQUID_ADAPTATION__ACCESS_RULE_H +#define SQUID_ADAPTATION__ACCESS_RULE_H + +#include "SquidString.h" +#include "adaptation/forward.h" + +class acl_access; + +namespace Adaptation { + +// manages adaptation_access configuration by associating an acl with +// an adaptation service group +class AccessRule { +public: + AccessRule(); + ~AccessRule(); + + void parse(ConfigParser &parser); + void finalize(); + + // service group consisting of one or more services + ServiceGroup *group(); + +public: + typedef int Id; + const Id id; + String groupId; + acl_access *acl; + +private: + static Id LastId; +}; + +typedef Vector AccessRules; +extern AccessRules &AllRules(); +extern AccessRule *FindRule(const AccessRule::Id &id); + +} // namespace Adaptation + +#endif /* SQUID_ADAPTATION__ACCESS_RULE_H */ diff --git a/src/adaptation/Config.cc b/src/adaptation/Config.cc index 68ba53e255..b6d1e7df87 100644 --- a/src/adaptation/Config.cc +++ b/src/adaptation/Config.cc @@ -37,329 +37,12 @@ #include "ACL.h" #include "Store.h" #include "Array.h" // really Vector -#include "HttpRequest.h" -#include "HttpReply.h" -#include "ACLChecklist.h" -#include "wordlist.h" #include "adaptation/Config.h" #include "adaptation/Service.h" +#include "adaptation/AccessRule.h" +#include "adaptation/ServiceGroups.h" -Adaptation::Config::Classes & -Adaptation::Config::AllClasses() -{ - static Classes TheClasses; - return TheClasses; -} - -Adaptation::Class * -Adaptation::Config::FindClass(const String& key) -{ - if (!key.size()) - return NULL; - - typedef Classes::iterator SI; - for (SI i = AllClasses().begin(); i != AllClasses().end(); ++i) { - if ((*i)->key == key) - return *i; - } - - return NULL; -} - -Adaptation::Config::Services & -Adaptation::Config::AllServices() -{ - static Services TheServices; - return TheServices; -} - -Adaptation::ServicePointer -Adaptation::Config::FindService(const String& key) -{ -debugs(1,1, HERE << "looking for " << key << " among " << AllServices().size() << " services"); - typedef Services::iterator SI; - for (SI i = AllServices().begin(); i != AllServices().end(); ++i) { -debugs(1,1, HERE << "\tcompare: " << key << " ? " << (*i)->cfg().key); - if ((*i)->cfg().key == key) - return *i; - } -debugs(1,1, HERE << "not found " << key << " among " << AllServices().size() << " services"); - - return NULL; -} - -void -Adaptation::Config::AddService(ServicePointer s) -{ - AllServices().push_back(s); -} - -void -Adaptation::Config::AddClass(Class *c) -{ - AllClasses().push_back(c); -} - - -Adaptation::Class::Class(): key(NULL), accessList(NULL), service_names(NULL) -{ - wordlistDestroy(&service_names); -} - -Adaptation::Class::~Class() -{ - wordlistDestroy(&service_names); -} - -int -Adaptation::Class::prepare() -{ - ConfigParser::ParseString(&key); - ConfigParser::ParseWordList(&service_names); - - if (service_names && service_names->next) { - debugs(3,0, "WARNING: Multiple services per icap_class are " << - "not yet supported. See Squid bug #2087."); - // TODO: fail on failures - } - - return 1; -} - -void -Adaptation::Class::finalize() -{ - for (wordlist *iter = service_names; iter; iter = iter->next) { - ServicePointer match = Config::FindService(iter->key); - if (match != NULL) - services += match; - } -} - -// ================================================================================ // - -cbdata_type Adaptation::AccessCheck::CBDATA_AccessCheck = CBDATA_UNKNOWN; - -Adaptation::AccessCheck::AccessCheck(Method aMethod, - VectPoint aPoint, - HttpRequest *aReq, - HttpReply *aRep, - AccessCheckCallback *aCallback, - void *aCallbackData): AsyncJob("AccessCheck"), done(FALSE) -{ - method = aMethod; - point = aPoint; - - req = HTTPMSGLOCK(aReq); - rep = aRep ? HTTPMSGLOCK(aRep) : NULL; - - callback = aCallback; - - callback_data = cbdataReference(aCallbackData); - - candidateClasses.clean(); - - matchedClass.clean(); - - acl_checklist = NULL; - - debugs(93, 5, "AccessCheck constructed for " << methodStr(method) << " " << vectPointStr(point)); -} - -Adaptation::AccessCheck::~AccessCheck() -{ - HTTPMSGUNLOCK(req); - HTTPMSGUNLOCK(rep); -} - -/* - * Walk the Access list and find all classes that have at least - * one service with matching method and vectoring point. - */ -void -Adaptation::AccessCheck::check() -{ - debugs(93, 3, "Adaptation::AccessCheck::check"); - - typedef Config::Classes::iterator CI; - for (CI ci = Config::AllClasses().begin(); ci != Config::AllClasses().end(); ++ci) { - - /* - * We only find the first matching service because we only need - * one matching service to justify ACL-checking a class. We might - * use other services belonging to the class if the first service - * turns out to be unusable for some reason. - */ - Class *c = *ci; - ServicePointer service = findBestService(c, false); - if (service != NULL) { - debugs(93, 3, "Adaptation::AccessCheck::check: class '" << c->key.buf() << "' has candidate service '" << service->cfg().key.buf() << "'"); - candidateClasses += c->key; - } - } - - checkCandidates(); -} - -void -Adaptation::AccessCheck::checkCandidates() -{ - while (!candidateClasses.empty()) { - // It didn't really match yet, but we use the name anyway. - matchedClass = candidateClasses.shift(); - Class *c = Config::FindClass(matchedClass); - - if (!c) // class apparently went away (reconfigure) - continue; - - // XXX we don't have access to conn->rfc931 here. - acl_checklist = aclChecklistCreate(c->accessList, req, dash_str); - - acl_checklist->nonBlockingCheck(AccessCheckCallbackWrapper, this); - - return; - } - - /* - * when there are no canidates, set matchedClass to NULL string - * and call the wrapper with answer = 1 - */ - debugs(93, 3, "Adaptation::AccessCheck::check: NO candidates or matches found"); - - matchedClass.clean(); - - AccessCheckCallbackWrapper(1, this); - - return; -} - -void -Adaptation::AccessCheck::AccessCheckCallbackWrapper(int answer, void *data) -{ - debugs(93, 5, "AccessCheckCallbackWrapper: answer=" << answer); - AccessCheck *ac = (AccessCheck*)data; - - if (ac->matchedClass.size()) { - debugs(93, 5, "AccessCheckCallbackWrapper matchedClass = " << ac->matchedClass.buf()); - } - - if (!answer) { - ac->checkCandidates(); - return; - } - - /* - * We use an event here to break deep function call sequences - */ - CallJobHere(93, 5, ac, Adaptation::AccessCheck::do_callback); -} - -#if 0 -void -Adaptation::AccessCheck::AccessCheckCallbackEvent(void *data) -{ - debugs(93, 5, "AccessCheckCallbackEvent"); - AccessCheck *ac = (AccessCheck*)data; - ac->do_callback(); - delete ac; -} -#endif - -void -Adaptation::AccessCheck::do_callback() -{ - debugs(93, 3, "Adaptation::AccessCheck::do_callback"); - - if (matchedClass.size()) { - debugs(93, 3, "Adaptation::AccessCheck::do_callback matchedClass = " << matchedClass.buf()); - } - - void *validated_cbdata; - if (!cbdataReferenceValidDone(callback_data, &validated_cbdata)) { - debugs(93,3,HERE << "do_callback: callback_data became invalid, skipping"); - return; - } - - ServicePointer service = NULL; - if (Class *c = Config::FindClass(matchedClass)) { - service = findBestService(c, true); - if (service != NULL) - debugs(93,3,HERE << "do_callback: with service " << service->cfg().uri); - else - debugs(93,3,HERE << "do_callback: no " << matchedClass << " service"); - } else { - debugs(93,3,HERE << "do_callback: no " << matchedClass << " class"); - } - - callback(service, validated_cbdata); - done = TRUE; -} - -Adaptation::ServicePointer -Adaptation::AccessCheck::findBestService(Class *c, bool preferUp) { - - const char *what = preferUp ? "up " : ""; - debugs(93,7,HERE << "looking for the first matching " << - what << "service among " << c->services.size() << - " services in class " << c->key); - - ServicePointer secondBest; - - Vector::iterator si; - for (si = c->services.begin(); si != c->services.end(); ++si) { - ServicePointer service = *si; - - if (method != service->cfg().method) - continue; - - if (point != service->cfg().point) - continue; - - // sending a message to a broken service is likely to cause errors - if (service->cfg().bypass && service->broken()) - continue; - - if (service->up()) { - // sending a message to a service that does not want it is useless - // note that we cannot check wantsUrl for service that is not "up" - // note that even essential services are skipped on unwanted URLs! - if (!service->wantsUrl(req->urlpath)) - continue; - } else { - if (!secondBest) - secondBest = service; - if (preferUp) { - // the caller asked for an "up" service and we can bypass this one - if (service->cfg().bypass) - continue; - debugs(93,5,HERE << "cannot skip an essential down service"); - what = "down-but-essential "; - } - } - - debugs(93,5,HERE << "found first matching " << - what << "service in class " << c->key << - ": " << service->cfg().key); - - return service; - } - - if (secondBest != NULL) { - what = "down "; - debugs(93,5,HERE << "found first matching " << - what << "service in class " << c->key << - ": " << secondBest->cfg().key); - return secondBest; - } - - debugs(93,5,HERE << "found no matching " << - what << "services in class " << c->key); - return ServicePointer(); -} - -// ================================================================================ // - void Adaptation::Config::parseService() { @@ -371,7 +54,10 @@ Adaptation::Config::parseService() void Adaptation::Config::freeService() { - // XXX: leaking Services and ServiceConfigs? + while (!serviceConfigs.empty()) { + delete serviceConfigs.back(); + serviceConfigs.pop_back(); + } } void @@ -392,85 +78,87 @@ Adaptation::Config::finalize() typedef Vector::const_iterator VISCI; const Vector &configs = serviceConfigs; debugs(93,3, "Found " << configs.size() << " service configs."); - for (VISCI ci = configs.begin(); ci != configs.end(); ++ci) { - ServicePointer s = createService(**ci); + for (VISCI i = configs.begin(); i != configs.end(); ++i) { + ServicePointer s = createService(**i); if (s != NULL) - AddService(s); + AllServices().push_back(s); } - debugs(93,1, "Initialized " << configs.size() << + debugs(93,3, "Created " << configs.size() << " message adaptation services."); } -void -Adaptation::Config::Finalize() +// poor man for_each +template +static void +FinalizeEach(Collection &collection, const char *label) { - // link classes with the service reps they use - typedef Classes::iterator CI; - for (CI ci = AllClasses().begin(); ci != AllClasses().end(); ++ci) { - Class *c = *ci; - c->finalize(); // TODO: fail on failures - } + typedef typename Collection::iterator CI; + for (CI i = collection.begin(); i != collection.end(); ++i) + (*i)->finalize(); - debugs(93,2, "Initialized " << AllClasses().size() << - " message adaptation service classes."); + debugs(93,2, "Initialized " << collection.size() << ' ' << label); } void -Adaptation::Config::parseClass() +Adaptation::Config::Finalize() { - Class *C = new Class(); - - if (C->prepare()) { - AddClass(C); - } else { - delete C; - } -}; + FinalizeEach(AllServices(), "message adaptation services"); + FinalizeEach(AllGroups(), "message adaptation service groups"); + FinalizeEach(AllRules(), "message adaptation access rules"); +} void -Adaptation::Config::freeClass() +Adaptation::Config::ParseServiceSet() { - // XXX: leaking Classes here? + ServiceSet *g = new ServiceSet(); + g->parse(); + AllGroups().push_back(g); } void -Adaptation::Config::dumpClass(StoreEntry *entry, const char *name) const +Adaptation::Config::FreeServiceSet() { - typedef Classes::iterator CI; - for (CI i = AllClasses().begin(); i != AllClasses().end(); ++i) - storeAppendPrintf(entry, "%s %s\n", name, (*i)->key.buf()); + while (!AllGroups().empty()) { + delete AllGroups().back(); + AllGroups().pop_back(); + } } void -Adaptation::Config::parseAccess(ConfigParser &parser) +Adaptation::Config::DumpServiceSet(StoreEntry *entry, const char *name) { - String aKey; - ConfigParser::ParseString(&aKey); - Class *c = FindClass(aKey); - - if (!c) - fatalf("Did not find class '%s' referenced on line %d\n", - aKey.buf(), config_lineno); + typedef Groups::iterator GI; + for (GI i = AllGroups().begin(); i != AllGroups().end(); ++i) + storeAppendPrintf(entry, "%s %s\n", name, (*i)->id.buf()); +} - aclParseAccessLine(parser, &c->accessList); -}; +void +Adaptation::Config::ParseAccess(ConfigParser &parser) +{ + AccessRule *r = new AccessRule; + r->parse(parser); + AllRules().push_back(r); +} void -Adaptation::Config::freeAccess() +Adaptation::Config::FreeAccess() { - (void) 0; + while (!AllRules().empty()) { + delete AllRules().back(); + AllRules().pop_back(); + } } void -Adaptation::Config::dumpAccess(StoreEntry *entry, const char *name) const +Adaptation::Config::DumpAccess(StoreEntry *entry, const char *name) { LOCAL_ARRAY(char, nom, 64); - typedef Classes::iterator CI; - for (CI i = AllClasses().begin(); i != AllClasses().end(); ++i) { - snprintf(nom, 64, "%s %s", name, (*i)->key.buf()); - dump_acl_access(entry, nom, (*i)->accessList); + typedef AccessRules::iterator CI; + for (CI i = AllRules().begin(); i != AllRules().end(); ++i) { + snprintf(nom, 64, "%s %s", name, (*i)->groupId.buf()); + dump_acl_access(entry, nom, (*i)->acl); } } @@ -479,18 +167,18 @@ Adaptation::Config::Config() // XXX: should we init members? } +// XXX: this is called for ICAP and eCAP configs, but deals mostly +// with global arrays shared by those individual configs Adaptation::Config::~Config() { + FreeAccess(); + FreeServiceSet(); // invalidate each service so that it can be deleted when refcount=0 - typedef Services::iterator SCI; - for (SCI i = AllServices().begin(); i != AllServices().end(); ++i) - (*i)->invalidate(); - - AllServices().clean(); - - while (!AllClasses().empty()) { - delete AllClasses().back(); - AllClasses().pop_back(); + while (!AllServices().empty()) { + AllServices().back()->invalidate(); + AllServices().pop_back(); } + + freeService(); } diff --git a/src/adaptation/Config.h b/src/adaptation/Config.h index 8f5f49b010..065ea0f4dc 100644 --- a/src/adaptation/Config.h +++ b/src/adaptation/Config.h @@ -19,70 +19,22 @@ class Class; typedef RefCount ServicePointer; -class Class -{ - -public: - String key; - acl_access *accessList; - - Vector services; - - Class(); - ~Class(); - - int prepare(); - void finalize(); - -private: - wordlist *service_names; -}; - -class AccessCheck: public virtual AsyncJob -{ - -public: - typedef void AccessCheckCallback(ServicePointer match, void *data); - AccessCheck(Method, VectPoint, HttpRequest *, HttpReply *, AccessCheckCallback *, void *); - ~AccessCheck(); - -private: - Method method; - VectPoint point; - HttpRequest *req; - HttpReply *rep; - AccessCheckCallback *callback; - void *callback_data; - ACLChecklist *acl_checklist; - Vector candidateClasses; - String matchedClass; - void do_callback(); - ServicePointer findBestService(Class *c, bool preferUp); - bool done; - -public: - void check(); - void checkCandidates(); - static void AccessCheckCallbackWrapper(int, void*); -#if 0 - static EVH AccessCheckCallbackEvent; -#endif -//AsyncJob virtual methods - virtual bool doneAll() const { return AsyncJob::doneAll() && done;} - -private: - CBDATA_CLASS2(AccessCheck); -}; +class ServiceGroup; +class AccessRule; class Config { public: - static ServicePointer FindService(const String &key); - static Class *FindClass(const String &key); - static void AddService(ServicePointer s); - static void AddClass(Class *c); static void Finalize(); + static void ParseServiceSet(void); + static void FreeServiceSet(void); + static void DumpServiceSet(StoreEntry *, const char *); + + static void ParseAccess(ConfigParser &parser); + static void FreeAccess(void); + static void DumpAccess(StoreEntry *, const char *); + friend class AccessCheck; public: @@ -104,23 +56,8 @@ public: ServicePointer findService(const String&); Class * findClass(const String& key); - void parseClass(void); - void freeClass(void); - void dumpClass(StoreEntry *, const char *) const; - - void parseAccess(ConfigParser &parser); - void freeAccess(void); - void dumpAccess(StoreEntry *, const char *) const; - void finalize(); -protected: - // TODO: use std::hash_map instead - typedef Vector Services; - typedef Vector Classes; - static Services &AllServices(); - static Classes &AllClasses(); - private: Config(const Config &); // unsupported Config &operator =(const Config &); // unsupported diff --git a/src/adaptation/Makefile.am b/src/adaptation/Makefile.am index cc7692f248..05bddb5a83 100644 --- a/src/adaptation/Makefile.am +++ b/src/adaptation/Makefile.am @@ -9,6 +9,10 @@ INCLUDES = \ noinst_LTLIBRARIES = libadaptation.la libadaptation_la_SOURCES = \ + AccessCheck.cc \ + AccessCheck.h \ + AccessRule.cc \ + AccessRule.h \ Config.cc \ Config.h \ Elements.cc \ @@ -22,8 +26,8 @@ libadaptation_la_SOURCES = \ Service.h \ ServiceConfig.cc \ ServiceConfig.h \ - Xaction.cc \ - Xaction.h + ServiceGroups.cc \ + ServiceGroups.h check_PROGRAMS = testHeaders diff --git a/src/adaptation/Service.cc b/src/adaptation/Service.cc index 3d44f171f7..a299bc24c0 100644 --- a/src/adaptation/Service.cc +++ b/src/adaptation/Service.cc @@ -11,8 +11,25 @@ Adaptation::Service::Service(const ServiceConfig &aConfig): theConfig(aConfig) Adaptation::Service::~Service() {} -bool +void Adaptation::Service::finalize() { - return true; +} + +Adaptation::Services & +Adaptation::AllServices() +{ + static Services TheServices; + return TheServices; +} + +Adaptation::ServicePointer +Adaptation::FindService(const Service::Id& key) +{ + typedef Services::iterator SI; + for (SI i = AllServices().begin(); i != AllServices().end(); ++i) { + if ((*i)->cfg().key == key) + return *i; + } + return NULL; } diff --git a/src/adaptation/Service.h b/src/adaptation/Service.h index 2e1d3af53d..9d6303ed38 100644 --- a/src/adaptation/Service.h +++ b/src/adaptation/Service.h @@ -20,6 +20,7 @@ class Service: public RefCountable { public: typedef RefCount Pointer; + typedef String Id; public: Service(const ServiceConfig &aConfig); @@ -45,8 +46,9 @@ public: const ServiceConfig &cfg() const { return theConfig; } + virtual void finalize(); // called after creation + protected: - bool finalize(); // called after creation ServiceConfig &writeableCfg() { return theConfig; } private: @@ -55,6 +57,10 @@ private: typedef Service::Pointer ServicePointer; +typedef Vector Services; +extern Services &AllServices(); +extern ServicePointer FindService(const Service::Id &key); + } // namespace Adaptation #endif /* SQUID_ADAPTATION__SERVICE_H */ diff --git a/src/adaptation/ServiceGroups.cc b/src/adaptation/ServiceGroups.cc new file mode 100644 index 0000000000..bbe11e760a --- /dev/null +++ b/src/adaptation/ServiceGroups.cc @@ -0,0 +1,84 @@ +#include "squid.h" + +#include "ConfigParser.h" +#include "Array.h" // really Vector +#include "adaptation/Config.h" +#include "adaptation/AccessRule.h" +#include "adaptation/Service.h" +#include "adaptation/ServiceGroups.h" + + +Adaptation::ServiceGroup::ServiceGroup(const String &aKind): kind(aKind) +{ +} + +Adaptation::ServiceGroup::~ServiceGroup() +{ +} + +void +Adaptation::ServiceGroup::parse() +{ + ConfigParser::ParseString(&id); + + wordlist *names = NULL; + ConfigParser::ParseWordList(&names); + for (wordlist *i = names; i; i = i->next) + services.push_back(i->key); + wordlistDestroy(&names); +} + +void +Adaptation::ServiceGroup::finalize() +{ + for (iterator i = services.begin(); i != services.end(); ++i) { + const String &id = *i; + // TODO: fail on failures + if (!FindService(id)) + debugs(93,0, "ERROR: Unknown adaptation name: " << id); + } +} + +/* ServiceSet */ + +Adaptation::ServiceSet::ServiceSet(): ServiceGroup("adaptation set") +{ +} + +Adaptation::ServiceGroup::Loop Adaptation::ServiceSet::initialServices() +{ + return Loop(services.begin(), services.end()); +} + +#if FUTURE_OPTIMIZATION +void +Adaptation::ServiceSet::finalize() +{ + ServiceGroup::finalize(); + + for (wordlist *iter = service_names; iter; iter = iter->next) { + ServicePointer match = Config::FindService(iter->id); + if (match != NULL) + services += match; + } +} +#endif + +Adaptation::Groups & +Adaptation::AllGroups() +{ + static Groups TheGroups; + return TheGroups; +} + +Adaptation::ServiceGroup * +Adaptation::FindGroup(const ServiceGroup::Id &id) +{ + typedef Groups::iterator GI; + for (GI i = AllGroups().begin(); i != AllGroups().end(); ++i) { + if ((*i)->id == id) + return *i; + } + + return NULL; +} diff --git a/src/adaptation/ServiceGroups.h b/src/adaptation/ServiceGroups.h new file mode 100644 index 0000000000..e76f1474f1 --- /dev/null +++ b/src/adaptation/ServiceGroups.h @@ -0,0 +1,70 @@ +#ifndef SQUID_ADAPTATION__SERVICE_GROUPS_H +#define SQUID_ADAPTATION__SERVICE_GROUPS_H + +#include "adaptation/forward.h" + +namespace Adaptation { + +// Interface for grouping adaptation services together. +// Specific groups differ in how the first and the next services are selected +class ServiceGroup +{ +public: + typedef Vector Store; + typedef Store::iterator iterator; + typedef String Id; + + // Information sufficient to iterate services stored in the group, + // grouped together to simplify initial/sequentialServices interfaces. + // The iterators point back to + struct Loop { + Loop(const iterator &b, const iterator &e): begin(b), end(e) {} + iterator begin; + iterator end; + }; + +public: + ServiceGroup(const String &aKind); + virtual ~ServiceGroup(); + + virtual void parse(); + virtual void finalize(); // called after all are parsed + + virtual Loop initialServices() = 0; + // TODO: virtual Loop sequentialServices() = 0; + +public: + String kind; + Id id; + Store services; +}; + +// a group of equivalent services; one service per set is usually used +class ServiceSet: public ServiceGroup +{ +public: + ServiceSet(); + virtual Loop initialServices(); +}; + +// corner case: a group consisting of one service +class SingleService: public ServiceGroup +{ +public: + SingleService(const String &aServiceKey); + virtual Loop initialServices(); +}; + +// TODO: a group of services that must be used one after another +// class ServiceChain: public ServiceGroup + + +typedef Vector Groups; +extern Groups &AllGroups(); +extern ServiceGroup *FindGroup(const ServiceGroup::Id &id); + + +} // namespace Adaptation + +#endif /* SQUID_ADAPTATION__SERVICE_GROUPS_H */ + diff --git a/src/adaptation/forward.h b/src/adaptation/forward.h index 09446f4b68..3c7ecb1b90 100644 --- a/src/adaptation/forward.h +++ b/src/adaptation/forward.h @@ -6,6 +6,11 @@ template class RefCount; +// For various collections such as AllServices +// TODO: use std::hash_map<> instead +template +class Vector; + namespace Adaptation { class Service; @@ -14,6 +19,8 @@ class Class; class Initiate; class Initiator; class AccessCheck; +class AccessRule; +class ServiceGroup; typedef RefCount ServicePointer; diff --git a/src/cache_cf.cc b/src/cache_cf.cc index a5ea59007f..6e7eef95df 100644 --- a/src/cache_cf.cc +++ b/src/cache_cf.cc @@ -59,6 +59,17 @@ #include "ESIParser.h" #endif +#if USE_ADAPTATION +#include "adaptation/Config.h" + +static void parse_adaptation_service_set_type(); + +static void parse_adaptation_access_type(); +static void dump_adaptation_access_type(StoreEntry *, const char *); +static void free_adaptation_access_type(); + +#endif + #if ICAP_CLIENT #include "ICAP/ICAPConfig.h" @@ -3421,6 +3432,35 @@ free_access_log(customlog ** definitions) } } +#if USE_ADAPTATION + +static void +parse_adaptation_service_set_type() +{ + Adaptation::Config::ParseServiceSet(); +} + +static void +parse_adaptation_access_type() +{ + Adaptation::Config::ParseAccess(LegacyParser); +} + +static void +free_adaptation_access_type() +{ + Adaptation::Config::FreeAccess(); +} + +static void +dump_adaptation_access_type(StoreEntry * entry, const char *name) +{ + Adaptation::Config::DumpAccess(entry, name); +} + +#endif /* USE_ADAPTATION */ + + #if ICAP_CLIENT static void @@ -3442,39 +3482,43 @@ dump_icap_service_type(StoreEntry * entry, const char *name, const ICAPConfig &c } static void -parse_icap_class_type(ICAPConfig * cfg) +parse_icap_class_type(ICAPConfig *) { - cfg->parseClass(); + debugs(93, 0, "WARNING: 'icap_class' is depricated. " << + "Use 'adaptation_service_set' instead"); + Adaptation::Config::ParseServiceSet(); } static void -free_icap_class_type(ICAPConfig * cfg) +free_icap_class_type(ICAPConfig *) { - cfg->freeClass(); + Adaptation::Config::FreeServiceSet(); } static void -dump_icap_class_type(StoreEntry * entry, const char *name, const ICAPConfig &cfg) +dump_icap_class_type(StoreEntry * entry, const char *name, const ICAPConfig &) { - cfg.dumpClass(entry, name); + Adaptation::Config::DumpServiceSet(entry, name); } static void -parse_icap_access_type(ICAPConfig * cfg) +parse_icap_access_type(ICAPConfig *) { - cfg->parseAccess(LegacyParser); + debugs(93, 0, "WARNING: 'icap_access' is depricated. " << + "Use 'adaptation_access' instead"); + parse_adaptation_access_type(); } static void -free_icap_access_type(ICAPConfig * cfg) +free_icap_access_type(ICAPConfig *) { - cfg->freeAccess(); + free_adaptation_access_type(); } static void -dump_icap_access_type(StoreEntry * entry, const char *name, const ICAPConfig &cfg) +dump_icap_access_type(StoreEntry * entry, const char *name, const ICAPConfig &) { - cfg.dumpAccess(entry, name); + dump_adaptation_access_type(entry, name); } #endif diff --git a/src/cf.data.depend b/src/cf.data.depend index 389f5107f3..5c48bc19dc 100644 --- a/src/cf.data.depend +++ b/src/cf.data.depend @@ -25,6 +25,8 @@ http_header_access http_header_replace http_port_list https_port_list +adaptation_access_type adaptation_service_set acl icap_service icap_class +adaptation_service_set_type icap_service icap_access_type icap_class acl icap_class_type icap_service icap_service_type diff --git a/src/cf.data.pre b/src/cf.data.pre index 86d01b6b0f..e5daa5a7e7 100644 --- a/src/cf.data.pre +++ b/src/cf.data.pre @@ -5007,16 +5007,15 @@ IFDEF: ICAP_CLIENT LOC: TheICAPConfig DEFAULT: none DOC_START - Defines an ICAP service chain. Eventually, multiple services per - vectoring point will be supported. For now, please specify a single - service per class: + This depricated option was documented to define an ICAP service + chain, even though it actually defined a set of similar, redundant + services, and the chains were not supported. - icap_class classname servicename + To define a set of redundant services, please use the + adaptation_service_set directive. -Example: -icap_class class_1 service_1 -icap class class_2 service_1 -icap class class_3 service_3 + If you need adaptation service chains, patches or sponsorship + is welcome. DOC_END NAME: icap_access @@ -5025,21 +5024,9 @@ IFDEF: ICAP_CLIENT LOC: TheICAPConfig DEFAULT: none DOC_START - Redirects a request through an ICAP service class, depending - on given acls - - icap_access classname allow|deny [!]aclname... - - The icap_access statements are processed in the order they appear in - this configuration file. If an access list matches, the processing stops. - For an "allow" rule, the specified class is used for the request. A "deny" - rule simply stops processing without using the class. You can also use the - special classname "None". - - For backward compatibility, it is also possible to use services - directly here. -Example: -icap_access class_1 allow all + This option is depricated. Please use adaptation_access, which + has the same ICAP functionality, but comes with better + documentation, and eCAP support. DOC_END COMMENT_START @@ -5059,6 +5046,75 @@ Example: loadable_modules @DEFAULT_PREFIX@/lib/MinimalAdapter.so DOC_END +COMMENT_START + MESSAGE ADAPTATION OPTIONS + ----------------------------------------------------------------------------- +COMMENT_END + +NAME: adaptation_service_set +TYPE: adaptation_service_set_type +IFDEF: USE_ADAPTATION +LOC: none +DEFAULT: none +DOC_START + + Defines a named adaptation service set. The set is populated in + the order of adaptation_service_set directives in this file. + When adaptation ACLs are processed, the first and only the first + applicable adaptation service from the set will be used. Thus, + the set should group similar, redundant services, rather than a + chain of complementary services. + + If you have a single adaptation service, you do not need to + define a set containing it because adaptation_access accepts + service names. + + See also: adaptation_access + +Example: +adaptation_service_set svcBlocker urlFilterPrimary urlFilterBackup +adaptation service_set svcLogger loggerLocal loggerRemote +DOC_END + +NAME: adaptation_access +TYPE: adaptation_access_type +IFDEF: USE_ADAPTATION +LOC: none +DEFAULT: none +DOC_START + Sends an HTTP transaction to an ICAP or eCAP adaptation service. + + adaptation_access service_name allow|deny [!]aclname... + adaptation_access set_name allow|deny [!]aclname... + + At each supported vectoring point, the adaptation_access + statements are processed in the order they appear in this + configuration file. Statements pointing to the following services + are ignored (i.e., skipped without checking their ACL): + + - services serving different vectoring points + - "broken-but-bypassable" services + - "up" services configured to ignore such transactions + (e.g., based on the ICAP Transfer-Ignore header). + + When a set_name is used, all services in the set are checked + using the same rules, to find the first applicable one. See + adaptation_service_set for details. + + If an access list is checked and there is a match, the + processing stops: For an "allow" rule, the corresponding + adaptation service is used for the transaction. For a "deny" + rule, no adaptation service is activated. + + It is currently not possible to apply more than one adaptation + service at the same vectoring point to the same HTTP transaction. + + See also: icap_service and ecap_service + +Example: +adaptation_access service_1 allow all +DOC_END + COMMENT_START DNS OPTIONS ----------------------------------------------------------------------------- diff --git a/src/client_side_request.cc b/src/client_side_request.cc index e083285df6..1beec35392 100644 --- a/src/client_side_request.cc +++ b/src/client_side_request.cc @@ -60,6 +60,7 @@ #include "wordlist.h" #if USE_ADAPTATION +#include "adaptation/AccessCheck.h" #include "ICAP/ICAPConfig.h" /* XXX: replace with generic adaptation config */ static void adaptationAclCheckDoneWrapper(Adaptation::ServicePointer service, void *data); #endif