Add VirtualMedia schema to Redfish
This change adds VirtualMedia scheme to Redfish.
Implementation is based on input from virtual-media module
and nbd proxy which is a bmcweb part. The code is used
only in case ndb-proxy is supported in bmcweb
(BMCWEB_ENABLE_VM_NBDPROXY compilation flag).
Tested:
* Manual tests together with nbd proxy and virtual media app
- For requests: Postman and/or HTTPie, started with logs
enabled and Valgrind
- Manual result validation
* Tests ran:
- GET on collection with manual validation
- PUT/POST/DELETE on collection
- GET on item/nonexistent item
- PUT/POST/DELETE on item
* Redfish Service Validator tested, no new issues found.
Signed-off-by: Przemyslaw Czarnowski <przemyslaw.hawrylewicz.czarnowski@intel.com>
Change-Id: I5415dc0ffe52069fd35bc614b0378bbc4ad41ff6
diff --git a/redfish-core/include/redfish.hpp b/redfish-core/include/redfish.hpp
index 715b4a4..c498a19 100644
--- a/redfish-core/include/redfish.hpp
+++ b/redfish-core/include/redfish.hpp
@@ -35,6 +35,9 @@
#include "../lib/systems.hpp"
#include "../lib/thermal.hpp"
#include "../lib/update_service.hpp"
+#ifdef BMCWEB_ENABLE_VM_NBDPROXY
+#include "../lib/virtual_media.hpp"
+#endif // BMCWEB_ENABLE_VM_NBDPROXY
#include "webserver_common.hpp"
namespace redfish
@@ -124,6 +127,10 @@
nodes.emplace_back(std::make_unique<SystemActionsReset>(app));
nodes.emplace_back(std::make_unique<BiosService>(app));
nodes.emplace_back(std::make_unique<BiosReset>(app));
+#ifdef BMCWEB_ENABLE_VM_NBDPROXY
+ nodes.emplace_back(std::make_unique<VirtualMedia>(app));
+ nodes.emplace_back(std::make_unique<VirtualMediaCollection>(app));
+#endif // BMCWEB_ENABLE_VM_NBDPROXY
#ifdef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES
nodes.emplace_back(std::make_unique<DBusLogServiceActionsClear>(app));
nodes.emplace_back(std::make_unique<DBusEventLogEntryCollection>(app));
diff --git a/redfish-core/lib/account_service.hpp b/redfish-core/lib/account_service.hpp
index 9f066e3..017a83d 100644
--- a/redfish-core/lib/account_service.hpp
+++ b/redfish-core/lib/account_service.hpp
@@ -64,11 +64,14 @@
std::vector<std::pair<std::string, LDAPRoleMapData>> groupRoleList;
};
-using ManagedObjectType = std::vector<std::pair<
- sdbusplus::message::object_path,
- boost::container::flat_map<
- std::string, boost::container::flat_map<
- std::string, std::variant<bool, std::string>>>>>;
+using DbusVariantType = sdbusplus::message::variant<bool, int32_t, std::string>;
+
+using DbusInterfaceType = boost::container::flat_map<
+ std::string, boost::container::flat_map<std::string, DbusVariantType>>;
+
+using ManagedObjectType =
+ std::vector<std::pair<sdbusplus::message::object_path, DbusInterfaceType>>;
+
using GetObjectType =
std::vector<std::pair<std::string, std::vector<std::string>>>;
diff --git a/redfish-core/lib/managers.hpp b/redfish-core/lib/managers.hpp
index 1dab258..dca5933 100644
--- a/redfish-core/lib/managers.hpp
+++ b/redfish-core/lib/managers.hpp
@@ -1560,6 +1560,12 @@
res.jsonValue["EthernetInterfaces"] = {
{"@odata.id", "/redfish/v1/Managers/bmc/EthernetInterfaces"}};
+
+#ifdef BMCWEB_ENABLE_VM_NBDPROXY
+ res.jsonValue["VirtualMedia"] = {
+ {"@odata.id", "/redfish/v1/Managers/bmc/VirtualMedia"}};
+#endif // BMCWEB_ENABLE_VM_NBDPROXY
+
// default oem data
nlohmann::json& oem = res.jsonValue["Oem"];
nlohmann::json& oemOpenbmc = oem["OpenBmc"];
diff --git a/redfish-core/lib/virtual_media.hpp b/redfish-core/lib/virtual_media.hpp
new file mode 100644
index 0000000..c12da70
--- /dev/null
+++ b/redfish-core/lib/virtual_media.hpp
@@ -0,0 +1,373 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+#pragma once
+
+#include <boost/container/flat_map.hpp>
+#include <node.hpp>
+#include <utils/json_utils.hpp>
+// for GetObjectType and ManagedObjectType
+#include <../lib/account_service.hpp>
+
+namespace redfish
+
+{
+
+/**
+ * @brief Read all known properties from VM object interfaces
+ */
+static void vmParseInterfaceObject(const DbusInterfaceType &interface,
+ std::shared_ptr<AsyncResp> aResp)
+{
+ const auto mountPointIface =
+ interface.find("xyz.openbmc_project.VirtualMedia.MountPoint");
+ if (mountPointIface == interface.cend())
+ {
+ BMCWEB_LOG_DEBUG << "Interface MountPoint not found";
+ return;
+ }
+
+ const auto processIface =
+ interface.find("xyz.openbmc_project.VirtualMedia.Process");
+ if (processIface == interface.cend())
+ {
+ BMCWEB_LOG_DEBUG << "Interface Process not found";
+ return;
+ }
+
+ const auto endpointIdProperty = mountPointIface->second.find("EndpointId");
+ if (endpointIdProperty == mountPointIface->second.cend())
+ {
+ BMCWEB_LOG_DEBUG << "Property EndpointId not found";
+ return;
+ }
+
+ const auto activeProperty = processIface->second.find("Active");
+ if (activeProperty == processIface->second.cend())
+ {
+ BMCWEB_LOG_DEBUG << "Property Active not found";
+ return;
+ }
+
+ const bool *activeValue = std::get_if<bool>(&activeProperty->second);
+ if (!activeValue)
+ {
+ BMCWEB_LOG_DEBUG << "Value Active not found";
+ return;
+ }
+
+ const std::string *endpointIdValue =
+ std::get_if<std::string>(&endpointIdProperty->second);
+ if (endpointIdValue)
+ {
+ if (!endpointIdValue->empty())
+ {
+ // Proxy mode
+ aResp->res.jsonValue["Oem"]["WebSocketEndpoint"] = *endpointIdValue;
+ aResp->res.jsonValue["TransferProtocolType"] = "OEM";
+ aResp->res.jsonValue["Inserted"] = *activeValue;
+ if (*activeValue == true)
+ {
+ aResp->res.jsonValue["ConnectedVia"] = "Applet";
+ }
+ }
+ else
+ {
+ // Legacy mode
+ const auto imageUrlProperty =
+ mountPointIface->second.find("ImageURL");
+ if (imageUrlProperty != processIface->second.cend())
+ {
+ const std::string *imageUrlValue =
+ std::get_if<std::string>(&imageUrlProperty->second);
+ if (imageUrlValue && !imageUrlValue->empty())
+ {
+ aResp->res.jsonValue["ImageName"] = *imageUrlValue;
+ aResp->res.jsonValue["Inserted"] = *activeValue;
+ if (*activeValue == true)
+ {
+ aResp->res.jsonValue["ConnectedVia"] = "URI";
+ }
+ }
+ }
+ }
+ }
+}
+
+/**
+ * @brief Fill template for Virtual Media Item.
+ */
+static nlohmann::json vmItemTemplate(const std::string &name,
+ const std::string &resName)
+{
+ nlohmann::json item;
+ item["@odata.id"] =
+ "/redfish/v1/Managers/" + name + "/VirtualMedia/" + resName;
+ item["@odata.type"] = "#VirtualMedia.v1_1_0.VirtualMedia";
+ item["@odata.context"] = "/redfish/v1/$metadata#VirtualMedia.VirtualMedia";
+ item["Name"] = "Virtual Removable Media";
+ item["Id"] = resName;
+ item["Image"] = nullptr;
+ item["Inserted"] = nullptr;
+ item["ImageName"] = nullptr;
+ item["WriteProtected"] = true;
+ item["ConnectedVia"] = "NotConnected";
+ item["MediaTypes"] = {"CD", "USBStick"};
+ item["TransferMethod"] = "Stream";
+ item["TransferProtocolType"] = nullptr;
+ item["Oem"]["WebSocketEndpoint"] = nullptr;
+
+ return item;
+}
+
+/**
+ * @brief Fills collection data
+ */
+static void getVmResourceList(std::shared_ptr<AsyncResp> aResp,
+ const std::string &service,
+ const std::string &name)
+{
+ BMCWEB_LOG_DEBUG << "Get available Virtual Media resources.";
+ crow::connections::systemBus->async_method_call(
+ [name, aResp{std::move(aResp)}](const boost::system::error_code ec,
+ ManagedObjectType &subtree) {
+ if (ec)
+ {
+ BMCWEB_LOG_DEBUG << "DBUS response error";
+ return;
+ }
+ nlohmann::json &members = aResp->res.jsonValue["Members"];
+ members = nlohmann::json::array();
+
+ for (const auto &object : subtree)
+ {
+ nlohmann::json item;
+ const std::string &path =
+ static_cast<const std::string &>(object.first);
+ std::size_t lastIndex = path.rfind("/");
+ if (lastIndex == std::string::npos)
+ {
+ continue;
+ }
+
+ lastIndex += 1;
+
+ item["@odata.id"] = "/redfish/v1/Managers/" + name +
+ "/VirtualMedia/" + path.substr(lastIndex);
+
+ members.emplace_back(std::move(item));
+ }
+ aResp->res.jsonValue["Members@odata.count"] = members.size();
+ },
+ service, "/xyz/openbmc_project/VirtualMedia",
+ "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
+}
+
+/**
+ * @brief Fills data for specific resource
+ */
+static void getVmData(std::shared_ptr<AsyncResp> aResp,
+ const std::string &service, const std::string &name,
+ const std::string &resName)
+{
+ BMCWEB_LOG_DEBUG << "Get Virtual Media resource data.";
+
+ crow::connections::systemBus->async_method_call(
+ [resName, name, aResp](const boost::system::error_code ec,
+ ManagedObjectType &subtree) {
+ if (ec)
+ {
+ BMCWEB_LOG_DEBUG << "DBUS response error";
+ return;
+ }
+
+ for (auto &item : subtree)
+ {
+ const std::string &path =
+ static_cast<const std::string &>(item.first);
+
+ std::size_t lastItem = path.rfind("/");
+ if (lastItem == std::string::npos)
+ {
+ continue;
+ }
+
+ if (path.substr(lastItem + 1) != resName)
+ {
+ continue;
+ }
+
+ aResp->res.jsonValue = vmItemTemplate(name, resName);
+
+ vmParseInterfaceObject(item.second, aResp);
+
+ return;
+ }
+
+ messages::resourceNotFound(
+ aResp->res, "#VirtualMedia.v1_1_0.VirtualMedia", resName);
+ },
+ service, "/xyz/openbmc_project/VirtualMedia",
+ "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
+}
+
+class VirtualMediaCollection : public Node
+{
+ public:
+ /*
+ * Default Constructor
+ */
+ VirtualMediaCollection(CrowApp &app) :
+ Node(app, "/redfish/v1/Managers/<str>/VirtualMedia/", std::string())
+ {
+ entityPrivileges = {
+ {boost::beast::http::verb::get, {{"Login"}}},
+ {boost::beast::http::verb::head, {{"Login"}}},
+ {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
+ {boost::beast::http::verb::put, {{"ConfigureManager"}}},
+ {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
+ {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
+ }
+
+ private:
+ /**
+ * Functions triggers appropriate requests on DBus
+ */
+ void doGet(crow::Response &res, const crow::Request &req,
+ const std::vector<std::string> ¶ms) override
+ {
+ auto asyncResp = std::make_shared<AsyncResp>(res);
+
+ // Check if there is required param, truly entering this shall be
+ // impossible
+ if (params.size() != 1)
+ {
+ messages::internalError(res);
+
+ return;
+ }
+
+ const std::string &name = params[0];
+
+ if (name != "bmc")
+ {
+ messages::resourceNotFound(asyncResp->res, "VirtualMedia", name);
+
+ return;
+ }
+
+ res.jsonValue["@odata.type"] =
+ "#VirtualMediaCollection.VirtualMediaCollection";
+ res.jsonValue["Name"] = "Virtual Media Services";
+ res.jsonValue["@odata.context"] =
+ "/redfish/v1/"
+ "$metadata#VirtualMediaCollection.VirtualMediaCollection";
+ res.jsonValue["@odata.id"] =
+ "/redfish/v1/Managers/" + name + "/VirtualMedia/";
+
+ crow::connections::systemBus->async_method_call(
+ [asyncResp, name](const boost::system::error_code ec,
+ const GetObjectType &getObjectType) {
+ if (ec)
+ {
+ BMCWEB_LOG_ERROR << "ObjectMapper::GetObject call failed: "
+ << ec;
+ messages::internalError(asyncResp->res);
+
+ return;
+ }
+ std::string service = getObjectType.begin()->first;
+ BMCWEB_LOG_DEBUG << "GetObjectType: " << service;
+
+ getVmResourceList(asyncResp, service, name);
+ },
+ "xyz.openbmc_project.ObjectMapper",
+ "/xyz/openbmc_project/object_mapper",
+ "xyz.openbmc_project.ObjectMapper", "GetObject",
+ "/xyz/openbmc_project/VirtualMedia", std::array<const char *, 0>());
+ }
+};
+
+class VirtualMedia : public Node
+{
+ public:
+ /*
+ * Default Constructor
+ */
+ VirtualMedia(CrowApp &app) :
+ Node(app, "/redfish/v1/Managers/<str>/VirtualMedia/<str>/",
+ std::string(), std::string())
+ {
+ entityPrivileges = {
+ {boost::beast::http::verb::get, {{"Login"}}},
+ {boost::beast::http::verb::head, {{"Login"}}},
+ {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
+ {boost::beast::http::verb::put, {{"ConfigureManager"}}},
+ {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
+ {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
+ }
+
+ private:
+ /**
+ * Functions triggers appropriate requests on DBus
+ */
+ void doGet(crow::Response &res, const crow::Request &req,
+ const std::vector<std::string> ¶ms) override
+ {
+ // Check if there is required param, truly entering this shall be
+ // impossible
+ if (params.size() != 2)
+ {
+ messages::internalError(res);
+
+ res.end();
+ return;
+ }
+ const std::string &name = params[0];
+ const std::string &resName = params[1];
+
+ auto asyncResp = std::make_shared<AsyncResp>(res);
+
+ if (name != "bmc")
+ {
+ messages::resourceNotFound(asyncResp->res, "VirtualMedia", resName);
+
+ return;
+ }
+
+ crow::connections::systemBus->async_method_call(
+ [asyncResp, name, resName](const boost::system::error_code ec,
+ const GetObjectType &getObjectType) {
+ if (ec)
+ {
+ BMCWEB_LOG_ERROR << "ObjectMapper::GetObject call failed: "
+ << ec;
+ messages::internalError(asyncResp->res);
+
+ return;
+ }
+ std::string service = getObjectType.begin()->first;
+ BMCWEB_LOG_DEBUG << "GetObjectType: " << service;
+
+ getVmData(asyncResp, service, name, resName);
+ },
+ "xyz.openbmc_project.ObjectMapper",
+ "/xyz/openbmc_project/object_mapper",
+ "xyz.openbmc_project.ObjectMapper", "GetObject",
+ "/xyz/openbmc_project/VirtualMedia", std::array<const char *, 0>());
+ }
+};
+
+} // namespace redfish