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