]> git.ipfire.org Git - thirdparty/squid.git/blobdiff - src/adaptation/ecap/ServiceRep.cc
SourceLayout: C++11 upgrade for class YesNoNone
[thirdparty/squid.git] / src / adaptation / ecap / ServiceRep.cc
index be8027aafdb7ab453d05daaed8ae10927a119fdb..7577c1b213c79f9c59c64273dc7b84f98a1a4530 100644 (file)
+/*
+ * Copyright (C) 1996-2016 The Squid Software Foundation and contributors
+ *
+ * Squid software is distributed under GPLv2+ license and includes
+ * contributions from numerous individuals and organizations.
+ * Please see the COPYING and CONTRIBUTORS files for details.
+ */
+
+/* DEBUG: section 93    eCAP Interface */
+
 #include "squid.h"
-#include <libecap/adapter/service.h>
-#include "TextException.h"
+#include "adaptation/ecap/Config.h"
+#include "adaptation/ecap/Host.h"
 #include "adaptation/ecap/ServiceRep.h"
 #include "adaptation/ecap/XactionRep.h"
+#include "AsyncEngine.h"
+#include "base/TextException.h"
+#include "Debug.h"
+#include "EventLoop.h"
+
+#include <libecap/adapter/service.h>
+#include <libecap/common/options.h>
+#include <libecap/common/name.h>
+#include <libecap/common/named_values.h>
+#include <limits>
+#include <map>
+
+/// libecap::adapter::services indexed by their URI
+typedef std::map<std::string, Adaptation::Ecap::ServiceRep::AdapterService> AdapterServices;
+/// all loaded services
+static AdapterServices TheServices;
+/// configured services producing async transactions
+static AdapterServices AsyncServices;
+
+namespace Adaptation
+{
+namespace Ecap
+{
+
+/// wraps Adaptation::Ecap::ServiceConfig to allow eCAP visitors
+class ConfigRep: public libecap::Options
+{
+public:
+    typedef Adaptation::Ecap::ServiceConfig Master;
+    typedef libecap::Name Name;
+    typedef libecap::Area Area;
+
+    ConfigRep(const Master &aMaster);
 
-Adaptation::Ecap::ServiceRep::ServiceRep(const Adaptation::ServiceConfig &cfg):
-        /*AsyncJob("Adaptation::Ecap::ServiceRep"),*/ Adaptation::Service(cfg)
+    // libecap::Options API
+    virtual const libecap::Area option(const libecap::Name &name) const;
+    virtual void visitEachOption(libecap::NamedValueVisitor &visitor) const;
+
+    const Master &master; ///< the configuration being wrapped
+};
+
+/// manages async eCAP transactions
+class Engine: public AsyncEngine
+{
+public:
+    /* AsyncEngine API */
+    virtual int checkEvents(int timeout);
+
+private:
+    void kickAsyncServices(timeval &timeout);
+};
+
+} // namespace Ecap
+} // namespace Adaptation
+
+Adaptation::Ecap::ConfigRep::ConfigRep(const Master &aMaster): master(aMaster)
 {
 }
 
-Adaptation::Ecap::ServiceRep::~ServiceRep()
+const libecap::Area
+Adaptation::Ecap::ConfigRep::option(const libecap::Name &name) const
 {
+    // we may supply the params we know about, but only when names have host ID
+    if (name == metaBypassable)
+        return Area(master.bypass ? "1" : "0", 1);
+
+    // TODO: We could build a by-name index, but is it worth it? Good adapters
+    // should use visitEachOption() instead, to check for name typos/errors.
+    typedef Master::Extensions::const_iterator MECI;
+    for (MECI i = master.extensions.begin(); i != master.extensions.end(); ++i) {
+        if (name == i->first)
+            return Area(i->second.data(), i->second.size());
+    }
+
+    return Area();
 }
 
-void Adaptation::Ecap::ServiceRep::noteService(const AdapterService &s)
+void
+Adaptation::Ecap::ConfigRep::visitEachOption(libecap::NamedValueVisitor &visitor) const
 {
-    Must(s != NULL);
-    theService = s;
-    debugs(93,7, HERE << "matched loaded and configured eCAP services: " <<
-           s->uri() << ' ' << cfg().key << "\n");
+    // we may supply the params we know about too, but only if we set host ID
+    visitor.visit(metaBypassable, Area(master.bypass ? "1" : "0", 1));
+
+    // visit adapter-specific options (i.e., those not recognized by Squid)
+    typedef Master::Extensions::const_iterator MECI;
+    for (MECI i = master.extensions.begin(); i != master.extensions.end(); ++i)
+        visitor.visit(Name(i->first), Area::FromTempString(i->second));
 }
 
-void Adaptation::Ecap::ServiceRep::invalidate()
+/* Adaptation::Ecap::Engine */
+
+int
+Adaptation::Ecap::Engine::checkEvents(int)
+{
+    // Start with the default I/O loop timeout, convert from milliseconds.
+    static const struct timeval maxTimeout = {
+        EVENT_LOOP_TIMEOUT/1000, // seconds
+        (EVENT_LOOP_TIMEOUT % 1000)*1000
+    }; // microseconds
+    struct timeval timeout = maxTimeout;
+
+    kickAsyncServices(timeout);
+    if (timeout.tv_sec == maxTimeout.tv_sec && timeout.tv_usec == maxTimeout.tv_usec)
+        return EVENT_IDLE;
+
+    debugs(93, 7, "timeout: " << timeout.tv_sec << "s+" << timeout.tv_usec << "us");
+
+    // convert back to milliseconds, avoiding int overflows
+    if (timeout.tv_sec >= std::numeric_limits<int>::max()/1000 - 1000)
+        return std::numeric_limits<int>::max();
+    else
+        return timeout.tv_sec*1000 + timeout.tv_usec/1000;
+}
+
+/// resumes async transactions (if any) and returns true if they set a timeout
+void
+Adaptation::Ecap::Engine::kickAsyncServices(timeval &timeout)
+{
+    if (AsyncServices.empty())
+        return;
+
+    debugs(93, 3, "async services: " << AsyncServices.size());
+
+    // Activate waiting async transactions, if any.
+    typedef AdapterServices::iterator ASI;
+    for (ASI s = AsyncServices.begin(); s != AsyncServices.end(); ++s) {
+        assert(s->second);
+        s->second->resume(); // may call Ecap::Xaction::resume()
+    }
+
+    // Give services a chance to decrease the default timeout.
+    for (ASI s = AsyncServices.begin(); s != AsyncServices.end(); ++s) {
+        s->second->suspend(timeout);
+    }
+}
+
+/* Adaptation::Ecap::ServiceRep */
+
+Adaptation::Ecap::ServiceRep::ServiceRep(const ServiceConfigPointer &cfg):
+/*AsyncJob("Adaptation::Ecap::ServiceRep"),*/ Adaptation::Service(cfg),
+    isDetached(false)
+{
+}
+
+Adaptation::Ecap::ServiceRep::~ServiceRep()
 {
-    theService->retire();
-    theService.reset();
 }
 
 void Adaptation::Ecap::ServiceRep::noteFailure()
@@ -36,15 +170,65 @@ void
 Adaptation::Ecap::ServiceRep::finalize()
 {
     Adaptation::Service::finalize();
+    if (!cfg().connectionEncryption.configured())
+        writeableCfg().connectionEncryption.defaultTo(true);
+    theService = FindAdapterService(cfg().uri);
     if (theService) {
-        debugs(93,3, HERE << "starting eCAP service: " << theService->uri());
-        theService->start();
+        try {
+            tryConfigureAndStart();
+            Must(up());
+        } catch (const std::exception &e) { // standardized exceptions
+            if (!handleFinalizeFailure(e.what()))
+                throw; // rethrow for upper layers to handle
+        } catch (...) { // all other exceptions
+            if (!handleFinalizeFailure("unrecognized exception"))
+                throw; // rethrow for upper layers to handle
+        }
+        return; // success or handled exception
     } else {
-        debugs(93,1, "Warning: configured ecap_service was not loaded: " <<
-               cfg().uri);
+        debugs(93,DBG_IMPORTANT, "WARNING: configured ecap_service was not loaded: " << cfg().uri);
     }
 }
 
+/// attempts to configure and start eCAP service; the caller handles exceptions
+void
+Adaptation::Ecap::ServiceRep::tryConfigureAndStart()
+{
+    debugs(93,2, HERE << "configuring eCAP service: " << theService->uri());
+    const ConfigRep cfgRep(dynamic_cast<const ServiceConfig&>(cfg()));
+    theService->configure(cfgRep);
+
+    debugs(93,DBG_IMPORTANT, "Starting eCAP service: " << theService->uri());
+    theService->start();
+
+    if (theService->makesAsyncXactions()) {
+        AsyncServices[theService->uri()] = theService;
+        debugs(93, 5, "asyncs: " << AsyncServices.size());
+    }
+}
+
+/// handles failures while configuring or starting an eCAP service;
+/// returns false if the error must be propagated to higher levels
+bool
+Adaptation::Ecap::ServiceRep::handleFinalizeFailure(const char *error)
+{
+    const bool salvage = cfg().bypass;
+    const int level = salvage ? DBG_IMPORTANT :DBG_CRITICAL;
+    const char *kind = salvage ? "optional" : "essential";
+    debugs(93, level, "ERROR: failed to start " << kind << " eCAP service: " <<
+           cfg().uri << ":\n" << error);
+
+    if (!salvage)
+        return false; // we cannot handle the problem; the caller may escalate
+
+    // make up() false, preventing new adaptation requests and enabling bypass
+    theService.reset();
+    debugs(93, level, "WARNING: " << kind << " eCAP service is " <<
+           "down after initialization failure: " << cfg().uri);
+
+    return true; // tell the caller to ignore the problem because we handled it
+}
+
 bool Adaptation::Ecap::ServiceRep::probed() const
 {
     return true; // we "probe" the adapter in finalize().
@@ -52,21 +236,33 @@ bool Adaptation::Ecap::ServiceRep::probed() const
 
 bool Adaptation::Ecap::ServiceRep::up() const
 {
-    return theService != NULL;
+    return theService;
 }
 
-bool Adaptation::Ecap::ServiceRep::wantsUrl(const String &urlPath) const
+bool Adaptation::Ecap::ServiceRep::wantsUrl(const SBuf &urlPath) const
 {
     Must(up());
-    return theService->wantsUrl(urlPath.termedBuf());
+    SBuf nonConstUrlPath = urlPath;
+    // c_str() reallocates and terminates for libecap API
+    return theService->wantsUrl(nonConstUrlPath.c_str());
 }
 
 Adaptation::Initiate *
-Adaptation::Ecap::ServiceRep::makeXactLauncher(Adaptation::Initiator *initiator,
-        HttpMsg *virgin, HttpRequest *cause)
+Adaptation::Ecap::ServiceRep::makeXactLauncher(HttpMsg *virgin,
+        HttpRequest *cause, AccessLogEntry::Pointer &alp)
 {
     Must(up());
-    XactionRep *rep = new XactionRep(initiator, virgin, cause, Pointer(this));
+
+    // register now because (a) we need EventLoop::Running and (b) we do not
+    // want to add more main loop overheads unless an async service is used.
+    static AsyncEngine *TheEngine = NULL;
+    if (AsyncServices.size() && !TheEngine && EventLoop::Running) {
+        TheEngine = new Engine;
+        EventLoop::Running->registerEngine(TheEngine);
+        debugs(93, 3, "asyncs: " << AsyncServices.size() << ' ' << TheEngine);
+    }
+
+    XactionRep *rep = new XactionRep(virgin, cause, alp, Pointer(this));
     XactionRep::AdapterXaction x(theService->makeXaction(rep));
     rep->master(x);
     return rep;
@@ -75,7 +271,80 @@ Adaptation::Ecap::ServiceRep::makeXactLauncher(Adaptation::Initiator *initiator,
 // returns a temporary string depicting service status, for debugging
 const char *Adaptation::Ecap::ServiceRep::status() const
 {
-    assert(false); // move generic stuff from ICAP to Adaptation
-    // add theService->status()?
-    return NULL;
+    // TODO: move generic stuff from eCAP and ICAP to Adaptation
+    static MemBuf buf;
+
+    buf.reset();
+    buf.append("[", 1);
+
+    if (up())
+        buf.append("up", 2);
+    else
+        buf.append("down", 4);
+
+    if (detached())
+        buf.append(",detached", 9);
+
+    buf.append("]", 1);
+    buf.terminate();
+
+    return buf.content();
+}
+
+void Adaptation::Ecap::ServiceRep::detach()
+{
+    isDetached = true;
+}
+
+bool Adaptation::Ecap::ServiceRep::detached() const
+{
+    return isDetached;
+}
+
+Adaptation::Ecap::ServiceRep::AdapterService
+Adaptation::Ecap::FindAdapterService(const String& serviceUri)
+{
+    AdapterServices::const_iterator pos = TheServices.find(serviceUri.termedBuf());
+    if (pos != TheServices.end()) {
+        Must(pos->second);
+        return pos->second;
+    }
+    return ServiceRep::AdapterService();
+}
+
+void
+Adaptation::Ecap::RegisterAdapterService(const Adaptation::Ecap::ServiceRep::AdapterService& adapterService)
+{
+    TheServices[adapterService->uri()] = adapterService; // may update old one
+    debugs(93, 3, "stored eCAP module service: " << adapterService->uri());
+    // We do not update AsyncServices here in case they are not configured.
 }
+
+void
+Adaptation::Ecap::UnregisterAdapterService(const String& serviceUri)
+{
+    if (TheServices.erase(serviceUri.termedBuf())) {
+        debugs(93, 3, "unregistered eCAP module service: " << serviceUri);
+        AsyncServices.erase(serviceUri.termedBuf()); // no-op for non-async
+        return;
+    }
+    debugs(93, 3, "failed to unregister eCAP module service: " << serviceUri);
+}
+
+void
+Adaptation::Ecap::CheckUnusedAdapterServices(const Adaptation::Services& cfgs)
+{
+    typedef AdapterServices::const_iterator ASCI;
+    for (ASCI loaded = TheServices.begin(); loaded != TheServices.end();
+            ++loaded) {
+        bool found = false;
+        for (Services::const_iterator cfged = cfgs.begin();
+                cfged != cfgs.end() && !found; ++cfged) {
+            found = (*cfged)->cfg().uri == loaded->second->uri().c_str();
+        }
+        if (!found)
+            debugs(93, DBG_IMPORTANT, "Warning: loaded eCAP service has no matching " <<
+                   "ecap_service config option: " << loaded->second->uri());
+    }
+}
+