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