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