]> git.ipfire.org Git - thirdparty/squid.git/blob - src/adaptation/ServiceGroups.cc
Source Format Enforcement (#1234)
[thirdparty/squid.git] / src / adaptation / ServiceGroups.cc
1 /*
2 * Copyright (C) 1996-2023 The Squid Software Foundation and contributors
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.
7 */
8
9 #include "squid.h"
10 #include "adaptation/AccessRule.h"
11 #include "adaptation/Config.h"
12 #include "adaptation/DynamicGroupCfg.h"
13 #include "adaptation/Service.h"
14 #include "adaptation/ServiceFilter.h"
15 #include "adaptation/ServiceGroups.h"
16 #include "ConfigParser.h"
17 #include "debug/Stream.h"
18 #include "StrList.h"
19 #include "wordlist.h"
20
21 Adaptation::ServiceGroup::ServiceGroup(const String &aKind, bool allSame):
22 kind(aKind), method(methodNone), point(pointNone),
23 allServicesSame(allSame)
24 {
25 }
26
27 Adaptation::ServiceGroup::~ServiceGroup()
28 {
29 }
30
31 void
32 Adaptation::ServiceGroup::parse()
33 {
34 id = ConfigParser::NextToken();
35
36 wordlist *names = nullptr;
37 ConfigParser::ParseWordList(&names);
38 for (wordlist *i = names; i; i = i->next)
39 services.push_back(i->key);
40 wordlistDestroy(&names);
41 }
42
43 // Note: configuration code aside, this method is called by DynamicServiceChain
44 void
45 Adaptation::ServiceGroup::finalize()
46 {
47 // 1) warn if services have different methods or vectoring point
48 // 2) warn if all-same services have different bypass status
49 // 3) warn if there are seemingly identical services in the group
50 // TODO: optimize by remembering ServicePointers rather than IDs
51 if (!removedServices.empty()) {
52 String s;
53 for (Store::iterator it = removedServices.begin(); it != removedServices.end(); ++it) {
54 s.append(*it);
55 s.append(',');
56 }
57 s.cut(s.size() - 1);
58 debugs(93, DBG_IMPORTANT, "Adaptation group '" << id << "' contains disabled member(s) after reconfiguration: " << s);
59 removedServices.clear();
60 }
61
62 String baselineKey;
63 bool baselineBypass = false;
64 for (Pos pos = 0; has(pos); ++pos) {
65 // TODO: quit on all errors
66 const String &serviceId = services[pos];
67 ServicePointer service = at(pos);
68 if (service != nullptr) {
69 if (method == methodNone) {
70 // optimization: cache values that should be the same
71 method = service->cfg().method;
72 point = service->cfg().point;
73 } else {
74 if (method != service->cfg().method)
75 finalizeMsg("Inconsistent service method for", serviceId, true);
76 if (point != service->cfg().point)
77 finalizeMsg("Inconsistent vectoring point for", serviceId, true);
78 }
79
80 checkUniqueness(pos);
81
82 if (allServicesSame) {
83 if (!baselineKey.size()) {
84 baselineKey = service->cfg().key;
85 baselineBypass = service->cfg().bypass;
86 } else if (baselineBypass != service->cfg().bypass) {
87 debugs(93, DBG_CRITICAL, "WARNING: Inconsistent bypass in " << kind <<
88 ' ' << id << " may produce surprising results: " <<
89 baselineKey << " vs. " << serviceId);
90 }
91 }
92 } else {
93 finalizeMsg("ERROR: Unknown adaptation name", serviceId, true);
94 }
95 }
96 debugs(93,7, "finalized " << kind << ": " << id);
97 }
98
99 /// checks that the service name or URI is not repeated later in the group
100 void
101 Adaptation::ServiceGroup::checkUniqueness(const Pos checkedPos) const
102 {
103 ServicePointer checkedService = at(checkedPos);
104 if (!checkedService) // should not happen but be robust
105 return;
106
107 for (Pos p = checkedPos + 1; has(p); ++p) {
108 ServicePointer s = at(p);
109 if (s != nullptr && s->cfg().key == checkedService->cfg().key)
110 finalizeMsg("duplicate service name", s->cfg().key, false);
111 else if (s != nullptr && s->cfg().uri == checkedService->cfg().uri)
112 finalizeMsg("duplicate service URI", s->cfg().uri, false);
113 }
114 }
115
116 /// emits a formatted warning or error message at the appropriate dbg level
117 void
118 Adaptation::ServiceGroup::finalizeMsg(const char *msg, const String &culprit,
119 bool error) const
120 {
121 const int level = error ? DBG_CRITICAL :DBG_IMPORTANT;
122 const char *pfx = error ? "ERROR: " : "WARNING: ";
123 debugs(93,level, pfx << msg << ' ' << culprit << " in " << kind << " '" <<
124 id << "'");
125 }
126
127 Adaptation::ServicePointer Adaptation::ServiceGroup::at(const Pos pos) const
128 {
129 return FindService(services[pos]);
130 }
131
132 // TODO: optimize to cut search short instead of looking for the best svc
133 bool
134 Adaptation::ServiceGroup::wants(const ServiceFilter &filter) const
135 {
136 Pos pos = 0;
137 return findService(filter, pos);
138 }
139
140 bool
141 Adaptation::ServiceGroup::findService(const ServiceFilter &filter, Pos &pos) const
142 {
143 if (method != filter.method || point != filter.point) {
144 debugs(93,5, id << " serves another location");
145 return false; // assume other services have the same wrong location
146 }
147
148 // find the next interested service, skipping problematic ones if possible
149 bool foundEssential = false;
150 Pos essPos = 0;
151 for (; has(pos); ++pos) {
152 debugs(93,9, id << " checks service at " << pos);
153 ServicePointer service = at(pos);
154
155 if (!service)
156 continue; // the service was lost due to reconfiguration
157
158 if (!service->wants(filter))
159 continue; // the service is not interested
160
161 if (service->up() || !service->probed()) {
162 debugs(93,9, id << " has matching service at " << pos);
163 return true;
164 }
165
166 if (service->cfg().bypass) { // we can safely ignore bypassable downers
167 debugs(93,9, id << " has bypassable service at " << pos);
168 continue;
169 }
170
171 if (!allServicesSame) { // cannot skip (i.e., find best) service
172 debugs(93,9, id << " has essential service at " << pos);
173 return true;
174 }
175
176 if (!foundEssential) {
177 debugs(93,9, id << " searches for best essential service from " << pos);
178 foundEssential = true;
179 essPos = pos;
180 }
181 }
182
183 if (foundEssential) {
184 debugs(93,9, id << " has best essential service at " << essPos);
185 pos = essPos;
186 return true;
187 }
188
189 debugs(93,5, id << " has no matching services");
190 return false;
191 }
192
193 bool
194 Adaptation::ServiceGroup::findReplacement(const ServiceFilter &filter, Pos &pos) const
195 {
196 return allServicesSame && findService(filter, pos);
197 }
198
199 bool
200 Adaptation::ServiceGroup::findLink(const ServiceFilter &filter, Pos &pos) const
201 {
202 return !allServicesSame && findService(filter, pos);
203 }
204
205 /* ServiceSet */
206
207 Adaptation::ServiceSet::ServiceSet(): ServiceGroup("adaptation set", true)
208 {
209 }
210
211 /* SingleService */
212
213 Adaptation::SingleService::SingleService(const String &aServiceId):
214 ServiceGroup("single-service group", false)
215 {
216 id = aServiceId;
217 services.push_back(aServiceId);
218 }
219
220 /* ServiceChain */
221
222 Adaptation::ServiceChain::ServiceChain(): ServiceGroup("adaptation chain", false)
223 {
224 }
225
226 /* DynamicServiceChain */
227
228 Adaptation::DynamicServiceChain::DynamicServiceChain(
229 const DynamicGroupCfg &cfg, const ServiceFilter &filter)
230 {
231 kind = "dynamic adaptation chain"; // TODO: optimize by using String const
232 id = cfg.id; // use services ids as the dynamic group ID
233 services = cfg.services;
234
235 // initialize cache to improve consistency checks in finalize()
236 method = filter.method;
237 point = filter.point;
238
239 finalize(); // will report [dynamic] config errors
240 }
241
242 void
243 Adaptation::DynamicServiceChain::Split(const ServiceFilter &filter,
244 const String &ids, DynamicGroupCfg &current,
245 DynamicGroupCfg &future)
246 {
247 // walk the list of services and split it into two parts:
248 // services that are applicable now and future services
249 bool doingCurrent = true;
250 const char *item = nullptr;
251 int ilen = 0;
252 const char *pos = nullptr;
253 while (strListGetItem(&ids, ',', &item, &ilen, &pos)) {
254 String id;
255 id.assign(item, ilen);
256 ServicePointer service = FindService(id);
257 if (doingCurrent) {
258 if (!service || // cannot tell or matches current location
259 (service->cfg().method == filter.method &&
260 service->cfg().point == filter.point)) {
261 current.add(id);
262 continue;
263 } else {
264 doingCurrent = false;
265 }
266 }
267
268 if (!doingCurrent)
269 future.add(id);
270 }
271 }
272
273 /* ServicePlan */
274
275 Adaptation::ServicePlan::ServicePlan(): pos(0), atEof(true)
276 {
277 }
278
279 Adaptation::ServicePlan::ServicePlan(const ServiceGroupPointer &g,
280 const ServiceFilter &filter):
281 group(g), pos(0), atEof(!g || !g->has(pos))
282 {
283 // this will find the first service because starting pos is zero
284 if (!atEof && !group->findService(filter, pos))
285 atEof = true;
286 }
287
288 Adaptation::ServicePointer
289 Adaptation::ServicePlan::current() const
290 {
291 // may return NULL even if not atEof
292 return atEof ? Adaptation::ServicePointer() : group->at(pos);
293 }
294
295 Adaptation::ServicePointer
296 Adaptation::ServicePlan::replacement(const ServiceFilter &filter)
297 {
298 if (!atEof && !group->findReplacement(filter, ++pos))
299 atEof = true;
300 return current();
301 }
302
303 Adaptation::ServicePointer
304 Adaptation::ServicePlan::next(const ServiceFilter &filter)
305 {
306 if (!atEof && !group->findLink(filter, ++pos))
307 atEof = true;
308 return current();
309 }
310
311 std::ostream &
312 Adaptation::ServicePlan::print(std::ostream &os) const
313 {
314 if (!group)
315 return os << "[nil]";
316
317 return os << group->id << '[' << pos << ".." << group->services.size() <<
318 (atEof ? ".]" : "]");
319 }
320
321 /* globals */
322
323 Adaptation::Groups &
324 Adaptation::AllGroups()
325 {
326 static Groups *TheGroups = new Groups;
327 return *TheGroups;
328 }
329
330 Adaptation::ServiceGroupPointer
331 Adaptation::FindGroup(const ServiceGroup::Id &id)
332 {
333 typedef Groups::iterator GI;
334 for (GI i = AllGroups().begin(); i != AllGroups().end(); ++i) {
335 if ((*i)->id == id)
336 return *i;
337 }
338
339 return nullptr;
340 }
341