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