]>
Commit | Line | Data |
---|---|---|
b510f3a1 | 1 | /* |
b8ae064d | 2 | * Copyright (C) 1996-2023 The Squid Software Foundation and contributors |
bbc27441 AJ |
3 | * |
4 | * Squid software is distributed under GPLv2+ license and includes | |
5 | * contributions from numerous individuals and organizations. | |
6 | * Please see the COPYING and CONTRIBUTORS files for details. | |
b510f3a1 | 7 | */ |
bbc27441 AJ |
8 | |
9 | /* DEBUG: section 93 eCAP Interface */ | |
10 | ||
582c2af2 | 11 | #include "squid.h" |
e1e90d26 | 12 | #include "adaptation/ecap/Config.h" |
22fff3bf | 13 | #include "adaptation/ecap/Host.h" |
1f3c65fc AR |
14 | #include "adaptation/ecap/ServiceRep.h" |
15 | #include "adaptation/ecap/XactionRep.h" | |
0a720258 | 16 | #include "AsyncEngine.h" |
3d93a84d | 17 | #include "base/TextException.h" |
675b8408 | 18 | #include "debug/Stream.h" |
86c63190 | 19 | #include "EventLoop.h" |
fdc96a39 | 20 | |
8800478b | 21 | #if HAVE_LIBECAP_ADAPTER_SERVICE_H |
0a720258 | 22 | #include <libecap/adapter/service.h> |
8800478b AJ |
23 | #endif |
24 | #if HAVE_LIBECAP_COMMON_OPTIONS_H | |
0a720258 | 25 | #include <libecap/common/options.h> |
8800478b AJ |
26 | #endif |
27 | #if HAVE_LIBECAP_COMMON_NAME_H | |
0a720258 | 28 | #include <libecap/common/name.h> |
8800478b AJ |
29 | #endif |
30 | #if HAVE_LIBECAP_COMMON_NAMED_VALUES_H | |
0a720258 | 31 | #include <libecap/common/named_values.h> |
8800478b AJ |
32 | #endif |
33 | ||
0a720258 | 34 | #include <limits> |
0a720258 AR |
35 | #include <map> |
36 | ||
37 | /// libecap::adapter::services indexed by their URI | |
38 | typedef std::map<std::string, Adaptation::Ecap::ServiceRep::AdapterService> AdapterServices; | |
39 | /// all loaded services | |
40 | static AdapterServices TheServices; | |
41 | /// configured services producing async transactions | |
42 | static AdapterServices AsyncServices; | |
76fc7e57 | 43 | |
22fff3bf AR |
44 | namespace Adaptation |
45 | { | |
46 | namespace Ecap | |
47 | { | |
48 | ||
e1e90d26 | 49 | /// wraps Adaptation::Ecap::ServiceConfig to allow eCAP visitors |
22fff3bf | 50 | class ConfigRep: public libecap::Options |
e1e90d26 AR |
51 | { |
52 | public: | |
53 | typedef Adaptation::Ecap::ServiceConfig Master; | |
54 | typedef libecap::Name Name; | |
55 | typedef libecap::Area Area; | |
56 | ||
22fff3bf | 57 | ConfigRep(const Master &aMaster); |
e1e90d26 | 58 | |
22fff3bf | 59 | // libecap::Options API |
337b9aa4 AR |
60 | const libecap::Area option(const libecap::Name &name) const override; |
61 | void visitEachOption(libecap::NamedValueVisitor &visitor) const override; | |
e1e90d26 AR |
62 | |
63 | const Master &master; ///< the configuration being wrapped | |
64 | }; | |
65 | ||
0a720258 AR |
66 | /// manages async eCAP transactions |
67 | class Engine: public AsyncEngine | |
68 | { | |
69 | public: | |
70 | /* AsyncEngine API */ | |
337b9aa4 | 71 | int checkEvents(int timeout) override; |
0a720258 AR |
72 | |
73 | private: | |
74 | void kickAsyncServices(timeval &timeout); | |
75 | }; | |
76 | ||
22fff3bf AR |
77 | } // namespace Ecap |
78 | } // namespace Adaptation | |
79 | ||
22fff3bf AR |
80 | Adaptation::Ecap::ConfigRep::ConfigRep(const Master &aMaster): master(aMaster) |
81 | { | |
82 | } | |
83 | ||
84 | const libecap::Area | |
85 | Adaptation::Ecap::ConfigRep::option(const libecap::Name &name) const | |
86 | { | |
87 | // we may supply the params we know about, but only when names have host ID | |
88 | if (name == metaBypassable) | |
89 | return Area(master.bypass ? "1" : "0", 1); | |
90 | ||
91 | // TODO: We could build a by-name index, but is it worth it? Good adapters | |
92 | // should use visitEachOption() instead, to check for name typos/errors. | |
93 | typedef Master::Extensions::const_iterator MECI; | |
94 | for (MECI i = master.extensions.begin(); i != master.extensions.end(); ++i) { | |
95 | if (name == i->first) | |
96 | return Area(i->second.data(), i->second.size()); | |
97 | } | |
98 | ||
99 | return Area(); | |
100 | } | |
101 | ||
e1e90d26 | 102 | void |
22fff3bf | 103 | Adaptation::Ecap::ConfigRep::visitEachOption(libecap::NamedValueVisitor &visitor) const |
e1e90d26 AR |
104 | { |
105 | // we may supply the params we know about too, but only if we set host ID | |
22fff3bf | 106 | visitor.visit(metaBypassable, Area(master.bypass ? "1" : "0", 1)); |
e1e90d26 AR |
107 | |
108 | // visit adapter-specific options (i.e., those not recognized by Squid) | |
109 | typedef Master::Extensions::const_iterator MECI; | |
110 | for (MECI i = master.extensions.begin(); i != master.extensions.end(); ++i) | |
111 | visitor.visit(Name(i->first), Area::FromTempString(i->second)); | |
112 | } | |
113 | ||
0a720258 AR |
114 | /* Adaptation::Ecap::Engine */ |
115 | ||
116 | int | |
117 | Adaptation::Ecap::Engine::checkEvents(int) | |
118 | { | |
119 | // Start with the default I/O loop timeout, convert from milliseconds. | |
fd3f5eac | 120 | static const struct timeval maxTimeout = { |
0a720258 | 121 | EVENT_LOOP_TIMEOUT/1000, // seconds |
86c63190 | 122 | (EVENT_LOOP_TIMEOUT % 1000)*1000 |
0a720258 AR |
123 | }; // microseconds |
124 | struct timeval timeout = maxTimeout; | |
125 | ||
126 | kickAsyncServices(timeout); | |
127 | if (timeout.tv_sec == maxTimeout.tv_sec && timeout.tv_usec == maxTimeout.tv_usec) | |
128 | return EVENT_IDLE; | |
129 | ||
130 | debugs(93, 7, "timeout: " << timeout.tv_sec << "s+" << timeout.tv_usec << "us"); | |
131 | ||
132 | // convert back to milliseconds, avoiding int overflows | |
133 | if (timeout.tv_sec >= std::numeric_limits<int>::max()/1000 - 1000) | |
134 | return std::numeric_limits<int>::max(); | |
135 | else | |
136 | return timeout.tv_sec*1000 + timeout.tv_usec/1000; | |
137 | } | |
138 | ||
139 | /// resumes async transactions (if any) and returns true if they set a timeout | |
140 | void | |
141 | Adaptation::Ecap::Engine::kickAsyncServices(timeval &timeout) | |
142 | { | |
143 | if (AsyncServices.empty()) | |
144 | return; | |
145 | ||
146 | debugs(93, 3, "async services: " << AsyncServices.size()); | |
147 | ||
148 | // Activate waiting async transactions, if any. | |
149 | typedef AdapterServices::iterator ASI; | |
150 | for (ASI s = AsyncServices.begin(); s != AsyncServices.end(); ++s) { | |
151 | assert(s->second); | |
152 | s->second->resume(); // may call Ecap::Xaction::resume() | |
153 | } | |
154 | ||
155 | // Give services a chance to decrease the default timeout. | |
156 | for (ASI s = AsyncServices.begin(); s != AsyncServices.end(); ++s) { | |
157 | s->second->suspend(timeout); | |
158 | } | |
159 | } | |
160 | ||
161 | /* Adaptation::Ecap::ServiceRep */ | |
162 | ||
6666da11 | 163 | Adaptation::Ecap::ServiceRep::ServiceRep(const ServiceConfigPointer &cfg): |
b6388dfd | 164 | /*AsyncJob("Adaptation::Ecap::ServiceRep"),*/ Adaptation::Service(cfg), |
f53969cc | 165 | isDetached(false) |
fdc96a39 AR |
166 | { |
167 | } | |
168 | ||
574b508c | 169 | Adaptation::Ecap::ServiceRep::~ServiceRep() |
fdc96a39 AR |
170 | { |
171 | } | |
172 | ||
574b508c | 173 | void Adaptation::Ecap::ServiceRep::noteFailure() |
26ac0430 AJ |
174 | { |
175 | assert(false); // XXX: should this be ICAP-specific? | |
fdc96a39 AR |
176 | } |
177 | ||
178 | void | |
574b508c | 179 | Adaptation::Ecap::ServiceRep::finalize() |
fdc96a39 | 180 | { |
26ac0430 | 181 | Adaptation::Service::finalize(); |
88df846b | 182 | if (!cfg().connectionEncryption.configured()) |
c50b35b5 | 183 | writeableCfg().connectionEncryption.defaultTo(true); |
76fc7e57 | 184 | theService = FindAdapterService(cfg().uri); |
e1d1bd27 | 185 | if (theService) { |
45d2da8b AR |
186 | try { |
187 | tryConfigureAndStart(); | |
188 | Must(up()); | |
aa420fc9 | 189 | } catch (const std::exception &e) { // standardized exceptions |
45d2da8b AR |
190 | if (!handleFinalizeFailure(e.what())) |
191 | throw; // rethrow for upper layers to handle | |
aa420fc9 | 192 | } catch (...) { // all other exceptions |
45d2da8b AR |
193 | if (!handleFinalizeFailure("unrecognized exception")) |
194 | throw; // rethrow for upper layers to handle | |
195 | } | |
196 | return; // success or handled exception | |
e1d1bd27 | 197 | } else { |
1159e19b | 198 | debugs(93,DBG_IMPORTANT, "WARNING: configured ecap_service was not loaded: " << cfg().uri); |
26ac0430 | 199 | } |
fdc96a39 AR |
200 | } |
201 | ||
45d2da8b AR |
202 | /// attempts to configure and start eCAP service; the caller handles exceptions |
203 | void | |
204 | Adaptation::Ecap::ServiceRep::tryConfigureAndStart() | |
205 | { | |
bf95c10a | 206 | debugs(93,2, "configuring eCAP service: " << theService->uri()); |
45d2da8b AR |
207 | const ConfigRep cfgRep(dynamic_cast<const ServiceConfig&>(cfg())); |
208 | theService->configure(cfgRep); | |
209 | ||
210 | debugs(93,DBG_IMPORTANT, "Starting eCAP service: " << theService->uri()); | |
211 | theService->start(); | |
0a720258 AR |
212 | |
213 | if (theService->makesAsyncXactions()) { | |
214 | AsyncServices[theService->uri()] = theService; | |
215 | debugs(93, 5, "asyncs: " << AsyncServices.size()); | |
216 | } | |
45d2da8b AR |
217 | } |
218 | ||
219 | /// handles failures while configuring or starting an eCAP service; | |
220 | /// returns false if the error must be propagated to higher levels | |
221 | bool | |
222 | Adaptation::Ecap::ServiceRep::handleFinalizeFailure(const char *error) | |
223 | { | |
224 | const bool salvage = cfg().bypass; | |
aa420fc9 | 225 | const int level = salvage ? DBG_IMPORTANT :DBG_CRITICAL; |
45d2da8b AR |
226 | const char *kind = salvage ? "optional" : "essential"; |
227 | debugs(93, level, "ERROR: failed to start " << kind << " eCAP service: " << | |
228 | cfg().uri << ":\n" << error); | |
229 | ||
230 | if (!salvage) | |
231 | return false; // we cannot handle the problem; the caller may escalate | |
232 | ||
233 | // make up() false, preventing new adaptation requests and enabling bypass | |
234 | theService.reset(); | |
235 | debugs(93, level, "WARNING: " << kind << " eCAP service is " << | |
236 | "down after initialization failure: " << cfg().uri); | |
237 | ||
aa420fc9 | 238 | return true; // tell the caller to ignore the problem because we handled it |
45d2da8b AR |
239 | } |
240 | ||
574b508c | 241 | bool Adaptation::Ecap::ServiceRep::probed() const |
fdc96a39 AR |
242 | { |
243 | return true; // we "probe" the adapter in finalize(). | |
244 | } | |
245 | ||
574b508c | 246 | bool Adaptation::Ecap::ServiceRep::up() const |
fdc96a39 | 247 | { |
eaba85cb | 248 | return bool(theService); |
fdc96a39 AR |
249 | } |
250 | ||
51b5dcf5 | 251 | bool Adaptation::Ecap::ServiceRep::wantsUrl(const SBuf &urlPath) const |
fdc96a39 AR |
252 | { |
253 | Must(up()); | |
51b5dcf5 AJ |
254 | SBuf nonConstUrlPath = urlPath; |
255 | // c_str() reallocates and terminates for libecap API | |
256 | return theService->wantsUrl(nonConstUrlPath.c_str()); | |
fdc96a39 AR |
257 | } |
258 | ||
259 | Adaptation::Initiate * | |
63df1d28 | 260 | Adaptation::Ecap::ServiceRep::makeXactLauncher(Http::Message *virgin, |
af0ded40 | 261 | HttpRequest *cause, AccessLogEntry::Pointer &alp) |
fdc96a39 | 262 | { |
26ac0430 | 263 | Must(up()); |
0a720258 AR |
264 | |
265 | // register now because (a) we need EventLoop::Running and (b) we do not | |
266 | // want to add more main loop overheads unless an async service is used. | |
aee3523a | 267 | static AsyncEngine *TheEngine = nullptr; |
0a720258 AR |
268 | if (AsyncServices.size() && !TheEngine && EventLoop::Running) { |
269 | TheEngine = new Engine; | |
270 | EventLoop::Running->registerEngine(TheEngine); | |
271 | debugs(93, 3, "asyncs: " << AsyncServices.size() << ' ' << TheEngine); | |
272 | } | |
273 | ||
af0ded40 | 274 | XactionRep *rep = new XactionRep(virgin, cause, alp, Pointer(this)); |
26ac0430 | 275 | XactionRep::AdapterXaction x(theService->makeXaction(rep)); |
fdc96a39 AR |
276 | rep->master(x); |
277 | return rep; | |
278 | } | |
279 | ||
280 | // returns a temporary string depicting service status, for debugging | |
574b508c | 281 | const char *Adaptation::Ecap::ServiceRep::status() const |
fdc96a39 | 282 | { |
76fc7e57 AJ |
283 | // TODO: move generic stuff from eCAP and ICAP to Adaptation |
284 | static MemBuf buf; | |
285 | ||
286 | buf.reset(); | |
287 | buf.append("[", 1); | |
288 | ||
289 | if (up()) | |
290 | buf.append("up", 2); | |
291 | else | |
292 | buf.append("down", 4); | |
293 | ||
294 | if (detached()) | |
295 | buf.append(",detached", 9); | |
296 | ||
297 | buf.append("]", 1); | |
298 | buf.terminate(); | |
299 | ||
300 | return buf.content(); | |
301 | } | |
302 | ||
303 | void Adaptation::Ecap::ServiceRep::detach() | |
304 | { | |
305 | isDetached = true; | |
306 | } | |
d090e020 | 307 | |
76fc7e57 AJ |
308 | bool Adaptation::Ecap::ServiceRep::detached() const |
309 | { | |
310 | return isDetached; | |
311 | } | |
312 | ||
313 | Adaptation::Ecap::ServiceRep::AdapterService | |
314 | Adaptation::Ecap::FindAdapterService(const String& serviceUri) | |
315 | { | |
0a720258 AR |
316 | AdapterServices::const_iterator pos = TheServices.find(serviceUri.termedBuf()); |
317 | if (pos != TheServices.end()) { | |
318 | Must(pos->second); | |
319 | return pos->second; | |
76fc7e57 AJ |
320 | } |
321 | return ServiceRep::AdapterService(); | |
322 | } | |
323 | ||
324 | void | |
325 | Adaptation::Ecap::RegisterAdapterService(const Adaptation::Ecap::ServiceRep::AdapterService& adapterService) | |
326 | { | |
0a720258 AR |
327 | TheServices[adapterService->uri()] = adapterService; // may update old one |
328 | debugs(93, 3, "stored eCAP module service: " << adapterService->uri()); | |
329 | // We do not update AsyncServices here in case they are not configured. | |
76fc7e57 AJ |
330 | } |
331 | ||
332 | void | |
333 | Adaptation::Ecap::UnregisterAdapterService(const String& serviceUri) | |
334 | { | |
0a720258 AR |
335 | if (TheServices.erase(serviceUri.termedBuf())) { |
336 | debugs(93, 3, "unregistered eCAP module service: " << serviceUri); | |
337 | AsyncServices.erase(serviceUri.termedBuf()); // no-op for non-async | |
338 | return; | |
76fc7e57 AJ |
339 | } |
340 | debugs(93, 3, "failed to unregister eCAP module service: " << serviceUri); | |
341 | } | |
342 | ||
343 | void | |
344 | Adaptation::Ecap::CheckUnusedAdapterServices(const Adaptation::Services& cfgs) | |
345 | { | |
0a720258 | 346 | typedef AdapterServices::const_iterator ASCI; |
76fc7e57 | 347 | for (ASCI loaded = TheServices.begin(); loaded != TheServices.end(); |
d090e020 | 348 | ++loaded) { |
76fc7e57 AJ |
349 | bool found = false; |
350 | for (Services::const_iterator cfged = cfgs.begin(); | |
d090e020 | 351 | cfged != cfgs.end() && !found; ++cfged) { |
0a720258 | 352 | found = (*cfged)->cfg().uri == loaded->second->uri().c_str(); |
76fc7e57 AJ |
353 | } |
354 | if (!found) | |
d816f28d | 355 | debugs(93, DBG_IMPORTANT, "WARNING: loaded eCAP service has no matching " << |
0a720258 | 356 | "ecap_service config option: " << loaded->second->uri()); |
76fc7e57 | 357 | } |
fdc96a39 | 358 | } |
f53969cc | 359 |