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