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