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