Allow POST on aggregation sources
It would be useful for both testing and production if one could create
aggregation sources dynamically, rather than using detected hardware.
Redfish sources might exist outside of the physical chassis in a way
that's detectable, so giving DC software a way to set up a non-trivial
aggregation topology make this more extensible.
Note, that as documented in AGGREGATION.md, only a single satellite
is supported by this feature. This patch does not change that
behavior, and implementing an entity-manager satellite will override
a POST created AggregationSource.
Tested:
Example commands succeed, and return the expected results.
```
curl -vvvv -k --http1.1 --user "root:0penBmc" --request DELETE https://192.168.7.2/redfish/v1/AggregationService/AggregationSources/
curl -vvvv -k --http1.1 --user "root:0penBmc" https://192.168.7.2/redfish/v1/AggregationService/AggregationSources -H "Content-Type: application/json" --request POST --data '{"HostName":"https://localhost:8000"}'
```
Redfish service validator passes.
Change-Id: I15711c3075645291b5f2555eefb80bb80418e7e5
Signed-off-by: Ed Tanous <ed@tanous.net>
diff --git a/redfish-core/include/query.hpp b/redfish-core/include/query.hpp
index d38da62..a810198 100644
--- a/redfish-core/include/query.hpp
+++ b/redfish-core/include/query.hpp
@@ -167,7 +167,7 @@
if constexpr (BMCWEB_REDFISH_AGGREGATION)
{
needToCallHandlers =
- RedfishAggregator::beginAggregation(req, asyncResp) ==
+ RedfishAggregator::getInstance().beginAggregation(req, asyncResp) ==
Result::LocalHandle;
// If the request should be forwarded to a satellite BMC then we don't
diff --git a/redfish-core/include/redfish_aggregator.hpp b/redfish-core/include/redfish_aggregator.hpp
index 00c6e55..4ed93b0 100644
--- a/redfish-core/include/redfish_aggregator.hpp
+++ b/redfish-core/include/redfish_aggregator.hpp
@@ -587,9 +587,9 @@
Resource,
};
- static void startAggregation(
+ void startAggregation(
AggregationType aggType, const crow::Request& thisReq,
- const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
+ const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) const
{
if (thisReq.method() != boost::beast::http::verb::get)
{
@@ -889,22 +889,29 @@
return handler;
}
+ // Aggregation sources from AggregationCollection
+ std::unordered_map<std::string, boost::urls::url> currentAggregationSources;
+
// Polls D-Bus to get all available satellite config information
// Expects a handler which interacts with the returned configs
- static void getSatelliteConfigs(
+ void getSatelliteConfigs(
std::function<
void(const boost::system::error_code&,
const std::unordered_map<std::string, boost::urls::url>&)>
- handler)
+ handler) const
{
BMCWEB_LOG_DEBUG("Gathering satellite configs");
+
+ std::unordered_map<std::string, boost::urls::url> satelliteInfo(
+ currentAggregationSources);
+
sdbusplus::message::object_path path("/xyz/openbmc_project/inventory");
dbus::utility::getManagedObjects(
"xyz.openbmc_project.EntityManager", path,
- [handler{std::move(handler)}](
+ [handler{std::move(handler)},
+ satelliteInfo = std::move(satelliteInfo)](
const boost::system::error_code& ec,
- const dbus::utility::ManagedObjectType& objects) {
- std::unordered_map<std::string, boost::urls::url> satelliteInfo;
+ const dbus::utility::ManagedObjectType& objects) mutable {
if (ec)
{
BMCWEB_LOG_ERROR("DBUS response error {}, {}", ec.value(),
@@ -1261,9 +1268,8 @@
// Entry point to Redfish Aggregation
// Returns Result stating whether or not we still need to locally handle the
// request
- static Result beginAggregation(
- const crow::Request& thisReq,
- const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
+ Result beginAggregation(const crow::Request& thisReq,
+ const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
using crow::utility::OrMorePaths;
using crow::utility::readUrlSegments;
diff --git a/redfish-core/lib/aggregation_service.hpp b/redfish-core/lib/aggregation_service.hpp
index f745f6b..cc86405 100644
--- a/redfish-core/lib/aggregation_service.hpp
+++ b/redfish-core/lib/aggregation_service.hpp
@@ -8,16 +8,22 @@
#include "http_request.hpp"
#include "http_response.hpp"
#include "logging.hpp"
+#include "ossl_random.hpp"
#include "query.hpp"
#include "redfish_aggregator.hpp"
#include "registries/privilege_registry.hpp"
+#include "utility.hpp"
+#include "utils/json_utils.hpp"
#include <boost/beast/http/field.hpp>
#include <boost/beast/http/verb.hpp>
+#include <boost/system/result.hpp>
#include <boost/url/format.hpp>
+#include <boost/url/parse.hpp>
#include <boost/url/url.hpp>
#include <nlohmann/json.hpp>
+#include <cstddef>
#include <functional>
#include <memory>
#include <unordered_map>
@@ -114,7 +120,7 @@
json["Name"] = "Aggregation Source Collection";
// Query D-Bus for satellite configs and add them to the Members array
- RedfishAggregator::getSatelliteConfigs(
+ RedfishAggregator::getInstance().getSatelliteConfigs(
std::bind_front(populateAggregationSourceCollection, asyncResp));
}
@@ -201,7 +207,7 @@
// Query D-Bus for satellite config corresponding to the specified
// AggregationSource
- RedfishAggregator::getSatelliteConfigs(std::bind_front(
+ RedfishAggregator::getInstance().getSatelliteConfigs(std::bind_front(
populateAggregationSource, aggregationSourceId, asyncResp));
}
@@ -223,6 +229,73 @@
aggregationSourceId);
}
+inline void handleAggregationSourceCollectionPost(
+ App& app, const crow::Request& req,
+ const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
+{
+ if (!redfish::setUpRedfishRoute(app, req, asyncResp))
+ {
+ return;
+ }
+ std::string hostname;
+ if (!json_util::readJsonPatch(req, asyncResp->res, "HostName", hostname))
+ {
+ return;
+ }
+
+ boost::system::result<boost::urls::url> url =
+ boost::urls::parse_absolute_uri(hostname);
+ if (!url)
+ {
+ messages::propertyValueIncorrect(asyncResp->res, hostname, "HostName");
+ return;
+ }
+ url->normalize();
+ if (url->scheme() != "http" && url->scheme() != "https")
+ {
+ messages::propertyValueIncorrect(asyncResp->res, hostname, "HostName");
+ return;
+ }
+ crow::utility::setPortDefaults(*url);
+
+ std::string prefix = bmcweb::getRandomIdOfLength(8);
+ RedfishAggregator::getInstance().currentAggregationSources.emplace(
+ prefix, *url);
+
+ asyncResp->res.addHeader(
+ boost::beast::http::field::location,
+ boost::urls::format("/redfish/v1/AggregationSources/{}", prefix)
+ .buffer());
+
+ messages::created(asyncResp->res);
+}
+
+inline void handleAggregationSourceDelete(
+ App& app, const crow::Request& req,
+ const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+ const std::string& aggregationSourceId)
+{
+ if (!redfish::setUpRedfishRoute(app, req, asyncResp))
+ {
+ return;
+ }
+ asyncResp->res.addHeader(
+ boost::beast::http::field::link,
+ "</redfish/v1/JsonSchemas/AggregationService/AggregationSource.json>; rel=describedby");
+
+ size_t deleted =
+ RedfishAggregator::getInstance().currentAggregationSources.erase(
+ aggregationSourceId);
+ if (deleted == 0)
+ {
+ messages::resourceNotFound(asyncResp->res, "AggregationSource",
+ aggregationSourceId);
+ return;
+ }
+
+ messages::success(asyncResp->res);
+}
+
inline void requestRoutesAggregationSource(App& app)
{
BMCWEB_ROUTE(app,
@@ -236,6 +309,23 @@
.privileges(redfish::privileges::getAggregationSource)
.methods(boost::beast::http::verb::get)(
std::bind_front(handleAggregationSourceGet, std::ref(app)));
+
+ BMCWEB_ROUTE(app,
+ "/redfish/v1/AggregationService/AggregationSources/<str>/")
+ .privileges(redfish::privileges::deleteAggregationSource)
+ .methods(boost::beast::http::verb::delete_)(
+ std::bind_front(handleAggregationSourceDelete, std::ref(app)));
+
+ BMCWEB_ROUTE(app,
+ "/redfish/v1/AggregationService/AggregationSources/<str>/")
+ .privileges(redfish::privileges::headAggregationSource)
+ .methods(boost::beast::http::verb::head)(
+ std::bind_front(handleAggregationSourceHead, std::ref(app)));
+
+ BMCWEB_ROUTE(app, "/redfish/v1/AggregationService/AggregationSources/")
+ .privileges(redfish::privileges::postAggregationSourceCollection)
+ .methods(boost::beast::http::verb::post)(std::bind_front(
+ handleAggregationSourceCollectionPost, std::ref(app)));
}
} // namespace redfish