xref: /openbmc/bmcweb/http/http_client.hpp (revision 852432ac)
1 /*
2 // Copyright (c) 2020 Intel Corporation
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 */
16 #pragma once
17 #include <boost/asio/io_context.hpp>
18 #include <boost/asio/ip/address.hpp>
19 #include <boost/asio/ip/basic_endpoint.hpp>
20 #include <boost/asio/ip/tcp.hpp>
21 #include <boost/asio/steady_timer.hpp>
22 #include <boost/beast/core/flat_buffer.hpp>
23 #include <boost/beast/core/flat_static_buffer.hpp>
24 #include <boost/beast/core/tcp_stream.hpp>
25 #include <boost/beast/http/message.hpp>
26 #include <boost/beast/http/parser.hpp>
27 #include <boost/beast/http/read.hpp>
28 #include <boost/beast/http/string_body.hpp>
29 #include <boost/beast/http/write.hpp>
30 #include <boost/beast/version.hpp>
31 #include <boost/container/devector.hpp>
32 #include <boost/system/error_code.hpp>
33 #include <http/http_response.hpp>
34 #include <include/async_resolve.hpp>
35 #include <logging.hpp>
36 
37 #include <cstdlib>
38 #include <functional>
39 #include <iostream>
40 #include <memory>
41 #include <queue>
42 #include <string>
43 
44 namespace crow
45 {
46 
47 // It is assumed that the BMC should be able to handle 4 parallel connections
48 constexpr uint8_t maxPoolSize = 4;
49 constexpr uint8_t maxRequestQueueSize = 50;
50 constexpr unsigned int httpReadBodyLimit = 16384;
51 constexpr unsigned int httpReadBufferSize = 4096;
52 
53 enum class ConnState
54 {
55     initialized,
56     resolveInProgress,
57     resolveFailed,
58     connectInProgress,
59     connectFailed,
60     connected,
61     sendInProgress,
62     sendFailed,
63     recvInProgress,
64     recvFailed,
65     idle,
66     closed,
67     suspended,
68     terminated,
69     abortConnection,
70     retry
71 };
72 
73 static inline boost::system::error_code
74     defaultRetryHandler(unsigned int respCode)
75 {
76     // As a default, assume 200X is alright
77     BMCWEB_LOG_DEBUG << "Using default check for response code validity";
78     if ((respCode < 200) || (respCode >= 300))
79     {
80         return boost::system::errc::make_error_code(
81             boost::system::errc::result_out_of_range);
82     }
83 
84     // Return 0 if the response code is valid
85     return boost::system::errc::make_error_code(boost::system::errc::success);
86 };
87 
88 // We need to allow retry information to be set before a message has been sent
89 // and a connection pool has been created
90 struct RetryPolicyData
91 {
92     uint32_t maxRetryAttempts = 5;
93     std::chrono::seconds retryIntervalSecs = std::chrono::seconds(0);
94     std::string retryPolicyAction = "TerminateAfterRetries";
95     std::function<boost::system::error_code(unsigned int respCode)>
96         invalidResp = defaultRetryHandler;
97 };
98 
99 struct PendingRequest
100 {
101     boost::beast::http::request<boost::beast::http::string_body> req;
102     std::function<void(bool, uint32_t, Response&)> callback;
103     RetryPolicyData retryPolicy;
104     PendingRequest(
105         boost::beast::http::request<boost::beast::http::string_body>&& reqIn,
106         const std::function<void(bool, uint32_t, Response&)>& callbackIn,
107         const RetryPolicyData& retryPolicyIn) :
108         req(std::move(reqIn)),
109         callback(callbackIn), retryPolicy(retryPolicyIn)
110     {}
111 };
112 
113 class ConnectionInfo : public std::enable_shared_from_this<ConnectionInfo>
114 {
115   private:
116     ConnState state = ConnState::initialized;
117     uint32_t retryCount = 0;
118     bool runningTimer = false;
119     std::string subId;
120     std::string host;
121     uint16_t port;
122     uint32_t connId;
123 
124     // Retry policy information
125     // This should be updated before each message is sent
126     RetryPolicyData retryPolicy;
127 
128     // Data buffers
129     boost::beast::http::request<boost::beast::http::string_body> req;
130     std::optional<
131         boost::beast::http::response_parser<boost::beast::http::string_body>>
132         parser;
133     boost::beast::flat_static_buffer<httpReadBufferSize> buffer;
134     Response res;
135 
136     // Ascync callables
137     std::function<void(bool, uint32_t, Response&)> callback;
138     crow::async_resolve::Resolver resolver;
139     boost::beast::tcp_stream conn;
140     boost::asio::steady_timer timer;
141 
142     friend class ConnectionPool;
143 
144     void doResolve()
145     {
146         state = ConnState::resolveInProgress;
147         BMCWEB_LOG_DEBUG << "Trying to resolve: " << host << ":"
148                          << std::to_string(port)
149                          << ", id: " << std::to_string(connId);
150 
151         auto respHandler =
152             [self(shared_from_this())](
153                 const boost::beast::error_code ec,
154                 const std::vector<boost::asio::ip::tcp::endpoint>&
155                     endpointList) {
156             if (ec || (endpointList.empty()))
157             {
158                 BMCWEB_LOG_ERROR << "Resolve failed: " << ec.message();
159                 self->state = ConnState::resolveFailed;
160                 self->waitAndRetry();
161                 return;
162             }
163             BMCWEB_LOG_DEBUG << "Resolved " << self->host << ":"
164                              << std::to_string(self->port)
165                              << ", id: " << std::to_string(self->connId);
166             self->doConnect(endpointList);
167         };
168 
169         resolver.asyncResolve(host, port, std::move(respHandler));
170     }
171 
172     void doConnect(
173         const std::vector<boost::asio::ip::tcp::endpoint>& endpointList)
174     {
175         state = ConnState::connectInProgress;
176 
177         BMCWEB_LOG_DEBUG << "Trying to connect to: " << host << ":"
178                          << std::to_string(port)
179                          << ", id: " << std::to_string(connId);
180 
181         conn.expires_after(std::chrono::seconds(30));
182         conn.async_connect(endpointList,
183                            [self(shared_from_this())](
184                                const boost::beast::error_code ec,
185                                const boost::asio::ip::tcp::endpoint& endpoint) {
186             if (ec)
187             {
188                 BMCWEB_LOG_ERROR << "Connect " << endpoint.address().to_string()
189                                  << ":" << std::to_string(endpoint.port())
190                                  << ", id: " << std::to_string(self->connId)
191                                  << " failed: " << ec.message();
192                 self->state = ConnState::connectFailed;
193                 self->waitAndRetry();
194                 return;
195             }
196             BMCWEB_LOG_DEBUG
197                 << "Connected to: " << endpoint.address().to_string() << ":"
198                 << std::to_string(endpoint.port())
199                 << ", id: " << std::to_string(self->connId);
200             self->state = ConnState::connected;
201             self->sendMessage();
202         });
203     }
204 
205     void sendMessage()
206     {
207         state = ConnState::sendInProgress;
208 
209         // Set a timeout on the operation
210         conn.expires_after(std::chrono::seconds(30));
211 
212         // Send the HTTP request to the remote host
213         boost::beast::http::async_write(
214             conn, req,
215             [self(shared_from_this())](const boost::beast::error_code& ec,
216                                        const std::size_t& bytesTransferred) {
217             if (ec)
218             {
219                 BMCWEB_LOG_ERROR << "sendMessage() failed: " << ec.message();
220                 self->state = ConnState::sendFailed;
221                 self->waitAndRetry();
222                 return;
223             }
224             BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: "
225                              << bytesTransferred;
226             boost::ignore_unused(bytesTransferred);
227 
228             self->recvMessage();
229             });
230     }
231 
232     void recvMessage()
233     {
234         state = ConnState::recvInProgress;
235 
236         parser.emplace(std::piecewise_construct, std::make_tuple());
237         parser->body_limit(httpReadBodyLimit);
238 
239         // Receive the HTTP response
240         boost::beast::http::async_read(
241             conn, buffer, *parser,
242             [self(shared_from_this())](const boost::beast::error_code& ec,
243                                        const std::size_t& bytesTransferred) {
244             if (ec)
245             {
246                 BMCWEB_LOG_ERROR << "recvMessage() failed: " << ec.message();
247                 self->state = ConnState::recvFailed;
248                 self->waitAndRetry();
249                 return;
250             }
251             BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: "
252                              << bytesTransferred;
253             BMCWEB_LOG_DEBUG << "recvMessage() data: "
254                              << self->parser->get().body();
255 
256             unsigned int respCode = self->parser->get().result_int();
257             BMCWEB_LOG_DEBUG << "recvMessage() Header Response Code: "
258                              << respCode;
259 
260             // Make sure the received response code is valid as defined by
261             // the associated retry policy
262             if (self->retryPolicy.invalidResp(respCode))
263             {
264                 // The listener failed to receive the Sent-Event
265                 BMCWEB_LOG_ERROR << "recvMessage() Listener Failed to "
266                                     "receive Sent-Event. Header Response Code: "
267                                  << respCode;
268                 self->state = ConnState::recvFailed;
269                 self->waitAndRetry();
270                 return;
271             }
272 
273             // Send is successful
274             // Reset the counter just in case this was after retrying
275             self->retryCount = 0;
276 
277             // Keep the connection alive if server supports it
278             // Else close the connection
279             BMCWEB_LOG_DEBUG << "recvMessage() keepalive : "
280                              << self->parser->keep_alive();
281 
282             // Copy the response into a Response object so that it can be
283             // processed by the callback function.
284             self->res.clear();
285             self->res.stringResponse = self->parser->release();
286             self->callback(self->parser->keep_alive(), self->connId, self->res);
287             });
288     }
289 
290     void waitAndRetry()
291     {
292         if (retryCount >= retryPolicy.maxRetryAttempts)
293         {
294             BMCWEB_LOG_ERROR << "Maximum number of retries reached.";
295             BMCWEB_LOG_DEBUG << "Retry policy: "
296                              << retryPolicy.retryPolicyAction;
297 
298             // We want to return a 502 to indicate there was an error with the
299             // external server
300             res.clear();
301             res.result(boost::beast::http::status::bad_gateway);
302 
303             if (retryPolicy.retryPolicyAction == "TerminateAfterRetries")
304             {
305                 // TODO: delete subscription
306                 state = ConnState::terminated;
307                 callback(false, connId, res);
308             }
309             if (retryPolicy.retryPolicyAction == "SuspendRetries")
310             {
311                 state = ConnState::suspended;
312                 callback(false, connId, res);
313             }
314             // Reset the retrycount to zero so that client can try connecting
315             // again if needed
316             retryCount = 0;
317             return;
318         }
319 
320         if (runningTimer)
321         {
322             BMCWEB_LOG_DEBUG << "Retry timer is already running.";
323             return;
324         }
325         runningTimer = true;
326 
327         retryCount++;
328 
329         BMCWEB_LOG_DEBUG << "Attempt retry after "
330                          << std::to_string(
331                                 retryPolicy.retryIntervalSecs.count())
332                          << " seconds. RetryCount = " << retryCount;
333         timer.expires_after(retryPolicy.retryIntervalSecs);
334         timer.async_wait(
335             [self(shared_from_this())](const boost::system::error_code ec) {
336             if (ec == boost::asio::error::operation_aborted)
337             {
338                 BMCWEB_LOG_DEBUG
339                     << "async_wait failed since the operation is aborted"
340                     << ec.message();
341             }
342             else if (ec)
343             {
344                 BMCWEB_LOG_ERROR << "async_wait failed: " << ec.message();
345                 // Ignore the error and continue the retry loop to attempt
346                 // sending the event as per the retry policy
347             }
348             self->runningTimer = false;
349 
350             // Let's close the connection and restart from resolve.
351             self->doCloseAndRetry();
352         });
353     }
354 
355     void doClose()
356     {
357         boost::beast::error_code ec;
358         conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
359         conn.close();
360 
361         // not_connected happens sometimes so don't bother reporting it.
362         if (ec && ec != boost::beast::errc::not_connected)
363         {
364             BMCWEB_LOG_ERROR << host << ":" << std::to_string(port)
365                              << ", id: " << std::to_string(connId)
366                              << "shutdown failed: " << ec.message();
367         }
368         else
369         {
370             BMCWEB_LOG_DEBUG << host << ":" << std::to_string(port)
371                              << ", id: " << std::to_string(connId)
372                              << " closed gracefully";
373         }
374 
375         state = ConnState::closed;
376     }
377 
378     void doCloseAndRetry()
379     {
380         boost::beast::error_code ec;
381         conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
382         conn.close();
383 
384         // not_connected happens sometimes so don't bother reporting it.
385         if (ec && ec != boost::beast::errc::not_connected)
386         {
387             BMCWEB_LOG_ERROR << host << ":" << std::to_string(port)
388                              << ", id: " << std::to_string(connId)
389                              << "shutdown failed: " << ec.message();
390         }
391         else
392         {
393             BMCWEB_LOG_DEBUG << host << ":" << std::to_string(port)
394                              << ", id: " << std::to_string(connId)
395                              << " closed gracefully";
396         }
397 
398         // Now let's try to resend the data
399         state = ConnState::retry;
400         doResolve();
401     }
402 
403   public:
404     explicit ConnectionInfo(boost::asio::io_context& ioc,
405                             const std::string& idIn, const std::string& destIP,
406                             const uint16_t destPort,
407                             const unsigned int connIdIn) :
408         subId(idIn),
409         host(destIP), port(destPort), connId(connIdIn), conn(ioc), timer(ioc)
410     {}
411 };
412 
413 class ConnectionPool : public std::enable_shared_from_this<ConnectionPool>
414 {
415   private:
416     boost::asio::io_context& ioc;
417     const std::string id;
418     const std::string destIP;
419     const uint16_t destPort;
420     std::vector<std::shared_ptr<ConnectionInfo>> connections;
421     boost::container::devector<PendingRequest> requestQueue;
422 
423     friend class HttpClient;
424 
425     // Configure a connections's request, callback, and retry info in
426     // preparation to begin sending the request
427     void setConnProps(ConnectionInfo& conn)
428     {
429         if (requestQueue.empty())
430         {
431             BMCWEB_LOG_ERROR
432                 << "setConnProps() should not have been called when requestQueue is empty";
433             return;
434         }
435 
436         auto nextReq = requestQueue.front();
437         conn.retryPolicy = std::move(nextReq.retryPolicy);
438         conn.req = std::move(nextReq.req);
439         conn.callback = std::move(nextReq.callback);
440 
441         BMCWEB_LOG_DEBUG << "Setting properties for connection " << conn.host
442                          << ":" << std::to_string(conn.port)
443                          << ", id: " << std::to_string(conn.connId);
444 
445         // We can remove the request from the queue at this point
446         requestQueue.pop_front();
447     }
448 
449     // Configures a connection to use the specific retry policy.
450     inline void setConnRetryPolicy(ConnectionInfo& conn,
451                                    const RetryPolicyData& retryPolicy)
452     {
453         BMCWEB_LOG_DEBUG << destIP << ":" << std::to_string(destPort)
454                          << ", id: " << std::to_string(conn.connId);
455 
456         conn.retryPolicy = retryPolicy;
457     }
458 
459     // Gets called as part of callback after request is sent
460     // Reuses the connection if there are any requests waiting to be sent
461     // Otherwise closes the connection if it is not a keep-alive
462     void sendNext(bool keepAlive, uint32_t connId)
463     {
464         auto conn = connections[connId];
465         // Reuse the connection to send the next request in the queue
466         if (!requestQueue.empty())
467         {
468             BMCWEB_LOG_DEBUG << std::to_string(requestQueue.size())
469                              << " requests remaining in queue for " << destIP
470                              << ":" << std::to_string(destPort)
471                              << ", reusing connnection "
472                              << std::to_string(connId);
473 
474             setConnProps(*conn);
475 
476             if (keepAlive)
477             {
478                 conn->sendMessage();
479             }
480             else
481             {
482                 // Server is not keep-alive enabled so we need to close the
483                 // connection and then start over from resolve
484                 conn->doClose();
485                 conn->doResolve();
486             }
487             return;
488         }
489 
490         // No more messages to send so close the connection if necessary
491         if (keepAlive)
492         {
493             conn->state = ConnState::idle;
494         }
495         else
496         {
497             // Abort the connection since server is not keep-alive enabled
498             conn->state = ConnState::abortConnection;
499             conn->doClose();
500         }
501     }
502 
503     void sendData(std::string& data, const std::string& destUri,
504                   const boost::beast::http::fields& httpHeader,
505                   const boost::beast::http::verb verb,
506                   const RetryPolicyData& retryPolicy,
507                   const std::function<void(Response&)>& resHandler)
508     {
509         std::weak_ptr<ConnectionPool> weakSelf = weak_from_this();
510 
511         // Callback to be called once the request has been sent
512         auto cb = [weakSelf, resHandler](bool keepAlive, uint32_t connId,
513                                          Response& res) {
514             // Allow provided callback to perform additional processing of the
515             // request
516             resHandler(res);
517 
518             // If requests remain in the queue then we want to reuse this
519             // connection to send the next request
520             std::shared_ptr<ConnectionPool> self = weakSelf.lock();
521             if (!self)
522             {
523                 BMCWEB_LOG_CRITICAL << self << " Failed to capture connection";
524                 return;
525             }
526 
527             self->sendNext(keepAlive, connId);
528         };
529 
530         // Construct the request to be sent
531         boost::beast::http::request<boost::beast::http::string_body> thisReq(
532             verb, destUri, 11, "", httpHeader);
533         thisReq.set(boost::beast::http::field::host, destIP);
534         thisReq.keep_alive(true);
535         thisReq.body() = std::move(data);
536         thisReq.prepare_payload();
537 
538         // Reuse an existing connection if one is available
539         for (unsigned int i = 0; i < connections.size(); i++)
540         {
541             auto conn = connections[i];
542             if ((conn->state == ConnState::idle) ||
543                 (conn->state == ConnState::initialized) ||
544                 (conn->state == ConnState::closed))
545             {
546                 conn->req = std::move(thisReq);
547                 conn->callback = std::move(cb);
548                 setConnRetryPolicy(*conn, retryPolicy);
549                 std::string commonMsg = std::to_string(i) + " from pool " +
550                                         destIP + ":" + std::to_string(destPort);
551 
552                 if (conn->state == ConnState::idle)
553                 {
554                     BMCWEB_LOG_DEBUG << "Grabbing idle connection "
555                                      << commonMsg;
556                     conn->sendMessage();
557                 }
558                 else
559                 {
560                     BMCWEB_LOG_DEBUG << "Reusing existing connection "
561                                      << commonMsg;
562                     conn->doResolve();
563                 }
564                 return;
565             }
566         }
567 
568         // All connections in use so create a new connection or add request to
569         // the queue
570         if (connections.size() < maxPoolSize)
571         {
572             BMCWEB_LOG_DEBUG << "Adding new connection to pool " << destIP
573                              << ":" << std::to_string(destPort);
574             auto conn = addConnection();
575             conn->req = std::move(thisReq);
576             conn->callback = std::move(cb);
577             setConnRetryPolicy(*conn, retryPolicy);
578             conn->doResolve();
579         }
580         else if (requestQueue.size() < maxRequestQueueSize)
581         {
582             BMCWEB_LOG_ERROR << "Max pool size reached. Adding data to queue.";
583             requestQueue.emplace_back(std::move(thisReq), std::move(cb),
584                                       retryPolicy);
585         }
586         else
587         {
588             BMCWEB_LOG_ERROR << destIP << ":" << std::to_string(destPort)
589                              << " request queue full.  Dropping request.";
590         }
591     }
592 
593     std::shared_ptr<ConnectionInfo>& addConnection()
594     {
595         unsigned int newId = static_cast<unsigned int>(connections.size());
596 
597         auto& ret = connections.emplace_back(
598             std::make_shared<ConnectionInfo>(ioc, id, destIP, destPort, newId));
599 
600         BMCWEB_LOG_DEBUG << "Added connection "
601                          << std::to_string(connections.size() - 1)
602                          << " to pool " << destIP << ":"
603                          << std::to_string(destPort);
604 
605         return ret;
606     }
607 
608   public:
609     explicit ConnectionPool(boost::asio::io_context& iocIn,
610                             const std::string& idIn,
611                             const std::string& destIPIn,
612                             const uint16_t destPortIn) :
613         ioc(iocIn),
614         id(idIn), destIP(destIPIn), destPort(destPortIn)
615     {
616         BMCWEB_LOG_DEBUG << "Initializing connection pool for " << destIP << ":"
617                          << std::to_string(destPort);
618 
619         // Initialize the pool with a single connection
620         addConnection();
621     }
622 };
623 
624 class HttpClient
625 {
626   private:
627     std::unordered_map<std::string, std::shared_ptr<ConnectionPool>>
628         connectionPools;
629     boost::asio::io_context& ioc =
630         crow::connections::systemBus->get_io_context();
631     std::unordered_map<std::string, RetryPolicyData> retryInfo;
632     HttpClient() = default;
633 
634     // Used as a dummy callback by sendData() in order to call
635     // sendDataWithCallback()
636     static void genericResHandler(const Response& res)
637     {
638         BMCWEB_LOG_DEBUG << "Response handled with return code: "
639                          << std::to_string(res.resultInt());
640     }
641 
642   public:
643     HttpClient(const HttpClient&) = delete;
644     HttpClient& operator=(const HttpClient&) = delete;
645     HttpClient(HttpClient&&) = delete;
646     HttpClient& operator=(HttpClient&&) = delete;
647     ~HttpClient() = default;
648 
649     static HttpClient& getInstance()
650     {
651         static HttpClient handler;
652         return handler;
653     }
654 
655     // Send a request to destIP:destPort where additional processing of the
656     // result is not required
657     void sendData(std::string& data, const std::string& id,
658                   const std::string& destIP, const uint16_t destPort,
659                   const std::string& destUri,
660                   const boost::beast::http::fields& httpHeader,
661                   const boost::beast::http::verb verb,
662                   const std::string& retryPolicyName)
663     {
664         const std::function<void(const Response&)> cb = genericResHandler;
665         sendDataWithCallback(data, id, destIP, destPort, destUri, httpHeader,
666                              verb, retryPolicyName, cb);
667     }
668 
669     // Send request to destIP:destPort and use the provided callback to
670     // handle the response
671     void sendDataWithCallback(std::string& data, const std::string& id,
672                               const std::string& destIP,
673                               const uint16_t destPort,
674                               const std::string& destUri,
675                               const boost::beast::http::fields& httpHeader,
676                               const boost::beast::http::verb verb,
677                               const std::string& retryPolicyName,
678                               const std::function<void(Response&)>& resHandler)
679     {
680         std::string clientKey = destIP + ":" + std::to_string(destPort);
681         // Use nullptr to avoid creating a ConnectionPool each time
682         auto result = connectionPools.try_emplace(clientKey, nullptr);
683         if (result.second)
684         {
685             // Now actually create the ConnectionPool shared_ptr since it does
686             // not already exist
687             result.first->second =
688                 std::make_shared<ConnectionPool>(ioc, id, destIP, destPort);
689             BMCWEB_LOG_DEBUG << "Created connection pool for " << clientKey;
690         }
691         else
692         {
693             BMCWEB_LOG_DEBUG << "Using existing connection pool for "
694                              << clientKey;
695         }
696 
697         // Get the associated retry policy
698         auto policy = retryInfo.try_emplace(retryPolicyName);
699         if (policy.second)
700         {
701             BMCWEB_LOG_DEBUG << "Creating retry policy \"" << retryPolicyName
702                              << "\" with default values";
703         }
704 
705         // Send the data using either the existing connection pool or the newly
706         // created connection pool
707         result.first->second->sendData(data, destUri, httpHeader, verb,
708                                        policy.first->second, resHandler);
709     }
710 
711     void setRetryConfig(
712         const uint32_t retryAttempts, const uint32_t retryTimeoutInterval,
713         const std::function<boost::system::error_code(unsigned int respCode)>&
714             invalidResp,
715         const std::string& retryPolicyName)
716     {
717         // We need to create the retry policy if one does not already exist for
718         // the given retryPolicyName
719         auto result = retryInfo.try_emplace(retryPolicyName);
720         if (result.second)
721         {
722             BMCWEB_LOG_DEBUG << "setRetryConfig(): Creating new retry policy \""
723                              << retryPolicyName << "\"";
724         }
725         else
726         {
727             BMCWEB_LOG_DEBUG << "setRetryConfig(): Updating retry info for \""
728                              << retryPolicyName << "\"";
729         }
730 
731         result.first->second.maxRetryAttempts = retryAttempts;
732         result.first->second.retryIntervalSecs =
733             std::chrono::seconds(retryTimeoutInterval);
734         result.first->second.invalidResp = invalidResp;
735     }
736 
737     void setRetryPolicy(const std::string& retryPolicy,
738                         const std::string& retryPolicyName)
739     {
740         // We need to create the retry policy if one does not already exist for
741         // the given retryPolicyName
742         auto result = retryInfo.try_emplace(retryPolicyName);
743         if (result.second)
744         {
745             BMCWEB_LOG_DEBUG << "setRetryPolicy(): Creating new retry policy \""
746                              << retryPolicyName << "\"";
747         }
748         else
749         {
750             BMCWEB_LOG_DEBUG << "setRetryPolicy(): Updating retry policy for \""
751                              << retryPolicyName << "\"";
752         }
753 
754         result.first->second.retryPolicyAction = retryPolicy;
755     }
756 };
757 } // namespace crow
758