blob: ab04e064b60919cf1b08be1a57b458e14dcbae29 [file] [log] [blame]
#ifndef PRODUCTION_SUSHID_SAFEPOWER_AGENT_BMC_MOCK_SERVER_H_
#define PRODUCTION_SUSHID_SAFEPOWER_AGENT_BMC_MOCK_SERVER_H_
#include <chrono> // NOLINT(build/c++11)
#include <cstdint>
#include <cstdlib>
#include <ctime>
#include <iostream>
#include <memory>
#include <string>
#include <utility>
// NOLINTBEGIN(readability/boost)
#include "absl/strings/string_view.h"
#include "boost/asio/io_context.hpp"
#include "boost/beast/core.hpp"
#include "boost/beast/http.hpp"
#include "boost/beast/version.hpp"
// NOLINTEND(readability/boost)
namespace safepower_agent {
namespace mock_server_test {
// boost ref server
// https://www.boost.org/doc/libs/1_85_0/libs/beast/example/http/server/small/http_server_small.cpp
//
// using a boost http server to test the http client used in gpowerd
namespace beast = boost::beast; // from <boost/beast.hpp>
namespace http = beast::http; // from <boost/beast/http.hpp>
namespace net = boost::asio; // from <boost/asio.hpp>
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
constexpr absl::string_view goodJson = R"(
{
"@Message.ExtendedInfo": [
{
"@odata.type": "#Message.v1_1_1.Message",
"Message": "The request completed successfully.",
"MessageArgs": [],
"MessageId": "Base.1.13.0.Success",
"MessageSeverity": "OK",
"Resolution": "None"
}
]
})";
constexpr absl::string_view testingPowerStateJson = R"(
{
"@odata.id": "/redfish/v1/Systems/system",
"@odata.type": "#ComputerSystem.v1_16_0.ComputerSystem",
"Actions": {
"#ComputerSystem.Reset": {
"@Redfish.ActionInfo": "/redfish/v1/Systems/system/ResetActionInfo",
"target": "/redfish/v1/Systems/system/Actions/ComputerSystem.Reset"
}
},
"PowerState": "On",
"PowerState_On": "On",
"PowerState_Off": "Off",
"PowerState_Paused": "Paused",
"PowerState_PoweringOn": "PoweringOn",
"PowerState_PoweringOff": "PoweringOff",
"PowerState_UNSPECIFIED_1": "",
"PowerState_UNSPECIFIED_2": "a"
})";
constexpr absl::string_view malformedJson = R"(
"@Message.ExtendedInfo":
"@odata.type": "#Message.v1_1_1.Message",
"Message": "The request completed successfully.",
"MessageArgs": ],
"MessageId": "Base.1.13.0.Success",
"MessageSeverity": "OK",
"Resolution": "None"
}
]
})";
constexpr absl::string_view goodSystemGet = R"(
{
"@odata.id": "/redfish/v1/Chassis",
"@odata.type": "#ChassisCollection.ChassisCollection",
"Members": [
{
"@odata.id": "/redfish/v1/Chassis/A"
},
{
"@odata.id": "/redfish/v1/Chassis/B"
},
{
"@odata.id": "/redfish/v1/Chassis/D"
},
{
"@odata.id": "/redfish/v1/Chassis/i0"
},
{
"@odata.id": "/redfish/v1/Chassis/i1"
},
{
"@odata.id": "/redfish/v1/Chassis/i2"
},
{
"@odata.id": "/redfish/v1/Chassis/w1"
},
{
"@odata.id": "/redfish/v1/Chassis/w2"
},
{
"@odata.id": "/redfish/v1/Chassis/w3"
}
],
"Members@odata.count": 9,
"Name": "Chassis Collection"
})";
class http_connection : public std::enable_shared_from_this<http_connection> {
public:
explicit http_connection(tcp::socket socket)
: socket_(std::move(socket)), skip_write_(false) {
std::cerr << "Test server connection made" << std::endl;
}
// Initiate the asynchronous operations associated with the connection.
void start() {
read_request();
check_deadline();
}
private:
// The socket for the currently connected client.
tcp::socket socket_;
// The buffer for performing reads.
beast::flat_buffer buffer_{8192};
// The request message.
http::request<http::dynamic_body> request_;
// The response message.
http::response<http::dynamic_body> response_;
// The timer for putting a deadline on connection processing.
net::steady_timer deadline_{socket_.get_executor(), std::chrono::seconds(60)};
// Asynchronously receive a complete request message.
bool skip_write_;
void read_request() {
auto self = shared_from_this();
http::async_read(
socket_, buffer_, request_,
[self](beast::error_code ec, std::size_t bytes_transferred) {
boost::ignore_unused(bytes_transferred);
if (!ec) {
self->process_request();
}
});
}
void process_request() {
response_.version(request_.version());
response_.keep_alive(false);
switch (request_.method()) {
case http::verb::post:
response_.result(http::status::ok);
response_.set(http::field::server, "Beast");
response_.set(http::field::content_type, "text/plain");
create_post_response();
break;
case http::verb::get:
response_.result(http::status::ok);
response_.set(http::field::server, "Beast");
create_get_response();
break;
default:
// We return responses indicating an error if
// we do not recognize the request method.
response_.result(http::status::bad_request);
response_.set(http::field::content_type, "text/plain");
beast::ostream(response_.body())
<< "Invalid request-method '"
<< std::string(request_.method_string()) << "'";
break;
}
if (!skip_write_) {
write_response();
}
}
// Construct a response message based on the program state.
void create_post_response() {
response_.set(http::field::content_type, "text/html");
if (request_.target() == "/count") {
beast::ostream(response_.body()) << "count_answer";
} else if (request_.target() == "/time") {
beast::ostream(response_.body()) << "time_answer";
} else if (request_.target() ==
"/redfish/v1/Systems/system/Actions/Power.Reset") {
beast::ostream(response_.body()) << goodJson;
} else if (request_.target() ==
"/redfish/v1/Systems/system/Actions/Power.Reset_never_write") {
skip_write_ = true;
} else if (request_.target() ==
"/redfish/v1/Systems/system/Actions/Power.Reset_malformed") {
beast::ostream(response_.body()) << malformedJson;
} else {
std::cerr << "request_.target():" << request_.target() << std::endl;
response_.result(http::status::not_found);
response_.set(http::field::content_type, "text/plain");
beast::ostream(response_.body()) << "File not found\r\n";
}
}
void create_get_response() {
if (request_.target() == "/count") {
response_.set(http::field::content_type, "text/html");
beast::ostream(response_.body()) << "count_answer";
} else if (request_.target() == "/redfish/v1/Systems/system_get") {
response_.set(http::field::content_type, "text/html");
beast::ostream(response_.body()) << goodSystemGet;
} else if (request_.target() ==
"/redfish/v1/Systems/system/_testingPowerState") {
beast::ostream(response_.body()) << testingPowerStateJson;
} else {
std::cerr << "request_.target():" << request_.target() << std::endl;
response_.result(http::status::not_found);
response_.set(http::field::content_type, "text/plain");
beast::ostream(response_.body()) << "File not found\r\n";
}
}
// Asynchronously transmit the response message.
void write_response() {
auto self = shared_from_this();
response_.content_length(response_.body().size());
http::async_write(socket_, response_,
[self](beast::error_code ec, std::size_t) {
self->socket_.shutdown(tcp::socket::shutdown_send, ec);
self->deadline_.cancel();
});
}
// Check whether we have spent enough time on this connection.
void check_deadline() {
auto self = shared_from_this();
deadline_.async_wait([self](beast::error_code ec) {
if (!ec) {
// Close socket to cancel any outstanding operation.
self->socket_.close(ec);
}
});
}
};
inline void http_server(tcp::acceptor& acceptor, tcp::socket& socket) {
acceptor.async_accept(socket, [&](beast::error_code ec) {
if (!ec) {
std::make_shared<http_connection>(std::move(socket))->start();
} else {
std::cerr << "http_server error:" << ec.what() << std::endl;
}
http_server(acceptor, socket);
});
}
class TestBoostServer {
public:
std::shared_ptr<tcp::acceptor> acceptor_;
std::shared_ptr<tcp::socket> socket_;
void Start(boost::asio::io_context& io_ctx) {
auto const address = net::ip::make_address("0.0.0.0");
uint16_t const port = 80;
auto const doc_root = std::make_shared<std::string>("/redfish/v1/");
boost::asio::ip::tcp::endpoint ep(address, port);
socket_ = std::make_shared<tcp::socket>(io_ctx);
acceptor_ = std::make_shared<tcp::acceptor>(io_ctx, ep);
http_server(*acceptor_, *socket_);
}
~TestBoostServer() {
acceptor_->cancel();
socket_->close();
}
};
} // namespace mock_server_test
} // namespace safepower_agent
#endif // PRODUCTION_SUSHID_SAFEPOWER_AGENT_BMC_MOCK_SERVER_H_