Implemented PATCH for EthernetInterface VLAN and HostName fields
Implemented PATCH request handling in EthernetInterface schema. Currently
only VLAN and HostName patching is implemented - IP changes will be introduced
in further patchsets.
This code does not change existing functionality - only expands it.
Tested on real hardware and x86 VM. Works fine, passes RedfishSchemaValidator.
Change-Id: I8de4ee5e859218823b07cc11845f7ef6782d7171
Signed-off-by: Kowalski, Kamil <kamil.kowalski@intel.com>
diff --git a/redfish-core/lib/ethernet.hpp b/redfish-core/lib/ethernet.hpp
index 6c91cef..5d5d4ea 100644
--- a/redfish-core/lib/ethernet.hpp
+++ b/redfish-core/lib/ethernet.hpp
@@ -15,6 +15,8 @@
*/
#pragma once
+#include <error_messages.hpp>
+#include <utils/json_utils.hpp>
#include "node.hpp"
#include <boost/container/flat_map.hpp>
@@ -182,7 +184,7 @@
for (auto &objpath : dbus_data) {
// Check if proper patter for object path appears
if (boost::starts_with(
- static_cast<const std::string&>(objpath.first),
+ static_cast<const std::string &>(objpath.first),
"/xyz/openbmc_project/network/" + ethiface_id + "/ipv4/")) {
// and get approrpiate interface
const auto &interface =
@@ -283,6 +285,78 @@
};
/**
+ * @brief Creates VLAN for given interface with given Id through D-Bus
+ *
+ * @param[in] ifaceId Id of interface for which VLAN will be created
+ * @param[in] inputVlanId ID of the new VLAN
+ * @param[in] callback Function that will be called after the operation
+ *
+ * @return None.
+ */
+ template <typename CallbackFunc>
+ void createVlan(const std::string &ifaceId, const uint64_t &inputVlanId,
+ CallbackFunc &&callback) {
+ crow::connections::system_bus->async_method_call(
+ callback, "xyz.openbmc_project.Network", "/xyz/openbmc_project/network",
+ "xyz.openbmc_project.Network.VLAN.Create", "VLAN", ifaceId,
+ static_cast<uint32_t>(inputVlanId));
+ };
+
+ /**
+ * @brief Sets given Id on the given VLAN interface through D-Bus
+ *
+ * @param[in] ifaceId Id of VLAN interface that should be modified
+ * @param[in] inputVlanId New ID of the VLAN
+ * @param[in] callback Function that will be called after the operation
+ *
+ * @return None.
+ */
+ template <typename CallbackFunc>
+ void changeVlanId(const std::string &ifaceId, const uint32_t &inputVlanId,
+ CallbackFunc &&callback) {
+ crow::connections::system_bus->async_method_call(
+ callback, "xyz.openbmc_project.Network",
+ std::string("/xyz/openbmc_project/network/") + ifaceId,
+ "org.freedesktop.DBus.Properties", "Set",
+ "xyz.openbmc_project.Network.VLAN", "Id",
+ sdbusplus::message::variant<uint32_t>(inputVlanId));
+ };
+
+ /**
+ * @brief Disables VLAN with given ifaceId
+ *
+ * @param[in] ifaceId Id of VLAN interface that should be disabled
+ * @param[in] callback Function that will be called after the operation
+ *
+ * @return None.
+ */
+ template <typename CallbackFunc>
+ void disableVlan(const std::string &ifaceId, CallbackFunc &&callback) {
+ crow::connections::system_bus->async_method_call(
+ callback, "xyz.openbmc_project.Network",
+ std::string("/xyz/openbmc_project/network/") + ifaceId,
+ "xyz.openbmc_project.Object.Delete", "Delete");
+ };
+
+ /**
+ * @brief Sets given HostName of the machine through D-Bus
+ *
+ * @param[in] newHostname New name that HostName will be changed to
+ * @param[in] callback Function that will be called after the operation
+ *
+ * @return None.
+ */
+ template <typename CallbackFunc>
+ void setHostName(const std::string &newHostname, CallbackFunc &&callback) {
+ crow::connections::system_bus->async_method_call(
+ callback, "xyz.openbmc_project.Network",
+ "/xyz/openbmc_project/network/config",
+ "org.freedesktop.DBus.Properties", "Set",
+ "xyz.openbmc_project.Network.SystemConfiguration", "HostName",
+ sdbusplus::message::variant<std::string>(newHostname));
+ };
+
+ /**
* Function that retrieves all Ethernet Interfaces available through Network
* Manager
* @param callback a function that shall be called to convert Dbus output into
@@ -316,8 +390,8 @@
if (interface.first ==
"xyz.openbmc_project.Network.EthernetInterface") {
// Cut out everyting until last "/", ...
- const std::string& iface_id =
- static_cast<const std::string&>(objpath.first);
+ const std::string &iface_id =
+ static_cast<const std::string &>(objpath.first);
std::size_t last_pos = iface_id.rfind("/");
if (last_pos != std::string::npos) {
// and put it into output vector.
@@ -355,12 +429,13 @@
Node::json["Description"] =
"Collection of EthernetInterfaces for this Manager";
- entityPrivileges = {{boost::beast::http::verb::get, {{"Login"}}},
- {boost::beast::http::verb::head, {{"Login"}}},
- {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
- {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
- {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
- {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
+ entityPrivileges = {
+ {boost::beast::http::verb::get, {{"Login"}}},
+ {boost::beast::http::verb::head, {{"Login"}}},
+ {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
+ {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
+ {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
+ {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
}
private:
@@ -423,15 +498,203 @@
Node::json["Name"] = "Manager Ethernet Interface";
Node::json["Description"] = "Management Network Interface";
- entityPrivileges = {{boost::beast::http::verb::get, {{"Login"}}},
- {boost::beast::http::verb::head, {{"Login"}}},
- {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
- {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
- {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
- {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
+ entityPrivileges = {
+ {boost::beast::http::verb::get, {{"Login"}}},
+ {boost::beast::http::verb::head, {{"Login"}}},
+ {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
+ {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
+ {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
+ {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
}
private:
+ void handleVlanPatch(const std::string &ifaceId, const nlohmann::json &input,
+ const EthernetInterfaceData ð_data,
+ const std::shared_ptr<AsyncResp> &asyncResp) {
+ if (!input.is_object()) {
+ messages::addMessageToJson(
+ asyncResp->res.json_value,
+ messages::propertyValueTypeError(input.dump(), "VLAN"), "/VLAN");
+ return;
+ }
+
+ bool inputVlanEnabled;
+ uint64_t inputVlanId;
+ json_util::Result inputVlanEnabledState = json_util::getBool(
+ "VLANEnable", input, inputVlanEnabled,
+ static_cast<int>(json_util::MessageSetting::TYPE_ERROR),
+ asyncResp->res.json_value, std::string("/VLAN/VLANEnable"));
+ json_util::Result inputVlanIdState = json_util::getUnsigned(
+ "VLANId", input, inputVlanId,
+ static_cast<int>(json_util::MessageSetting::TYPE_ERROR),
+ asyncResp->res.json_value, std::string("/VLAN/VLANId"));
+ bool inputInvalid = false;
+
+ // Do not proceed if fields in VLAN object were of wrong type
+ if (inputVlanEnabledState == json_util::Result::WRONG_TYPE ||
+ inputVlanIdState == json_util::Result::WRONG_TYPE) {
+ return;
+ }
+
+ // Verify input
+ if (eth_data.vlan_id == nullptr) {
+ // VLAN is currently disabled. User can only create/enable it. Change of
+ // VLANId is prohibited, and disable request (VLANEnabled == false) will
+ // not have any effect.
+ if (inputVlanEnabledState == json_util::Result::SUCCESS &&
+ inputVlanEnabled == true) {
+ // Creation requested, user should also provide ID for new VLAN
+ if (inputVlanIdState != json_util::Result::SUCCESS) {
+ messages::addMessageToJson(asyncResp->res.json_value,
+ messages::propertyMissing("VLANId"),
+ "/VLAN");
+ inputInvalid = true;
+ }
+ } else if (inputVlanIdState == json_util::Result::SUCCESS) {
+ // VLAN is disabled, but user requested modification. This is not valid.
+ messages::addMessageToJson(
+ asyncResp->res.json_value,
+ messages::actionParameterNotSupported("VLANId", "change VLAN Id"),
+ "/VLAN");
+
+ messages::addMessageToJson(asyncResp->res.json_value,
+ messages::propertyMissing("VLANEnable"),
+ "/VLAN");
+
+ inputInvalid = true;
+ }
+ } else {
+ // Load actual data into field values if they were not provided
+ if (inputVlanEnabledState == json_util::Result::NOT_EXIST) {
+ inputVlanEnabled = true;
+ }
+
+ if (inputVlanIdState == json_util::Result::NOT_EXIST) {
+ inputVlanId = *eth_data.vlan_id;
+ }
+ }
+
+ // Do not proceed if input has not been valid
+ if (inputInvalid) {
+ return;
+ }
+
+ auto vlanEnabledAfterOperation =
+ [asyncResp](const boost::system::error_code ec) {
+ if (ec) {
+ messages::addMessageToJson(asyncResp->res.json_value,
+ messages::internalError(), "/VLAN");
+ } else {
+ asyncResp->res.json_value["VLAN"]["VLANEnable"] = true;
+ }
+ };
+
+ if (eth_data.vlan_id == nullptr) {
+ if (inputVlanEnabled == true) {
+ ethernet_provider.createVlan(ifaceId, inputVlanId,
+ std::move(vlanEnabledAfterOperation));
+ asyncResp->res.json_value["VLAN"]["VLANId"] = inputVlanId;
+ }
+ } else {
+ // VLAN is configured on the interface
+ if (inputVlanEnabled == true && inputVlanId != *eth_data.vlan_id) {
+ // Change VLAN Id
+ asyncResp->res.json_value["VLAN"]["VLANId"] = inputVlanId;
+ ethernet_provider.changeVlanId(ifaceId,
+ static_cast<uint32_t>(inputVlanId),
+ std::move(vlanEnabledAfterOperation));
+ } else if (inputVlanEnabled == false) {
+ // Disable VLAN
+ ethernet_provider.disableVlan(
+ ifaceId, [asyncResp](const boost::system::error_code ec) {
+ if (ec) {
+ messages::addMessageToJson(asyncResp->res.json_value,
+ messages::internalError(), "/VLAN");
+ } else {
+ asyncResp->res.json_value["VLAN"]["VLANEnable"] = false;
+ }
+ });
+ }
+ }
+ }
+
+ void handleHostnamePatch(const nlohmann::json &input,
+ const EthernetInterfaceData ð_data,
+ const std::shared_ptr<AsyncResp> &asyncResp) {
+ if (input.is_string()) {
+ std::string newHostname = input.get<std::string>();
+
+ if (eth_data.hostname == nullptr || newHostname != *eth_data.hostname) {
+ // Change hostname
+ ethernet_provider.setHostName(
+ newHostname,
+ [asyncResp, newHostname](const boost::system::error_code ec) {
+ if (ec) {
+ messages::addMessageToJson(asyncResp->res.json_value,
+ messages::internalError(),
+ "/HostName");
+ } else {
+ asyncResp->res.json_value["HostName"] = newHostname;
+ }
+ });
+ }
+ } else {
+ messages::addMessageToJson(
+ asyncResp->res.json_value,
+ messages::propertyValueTypeError(input.dump(), "HostName"),
+ "/HostName");
+ }
+ }
+
+ nlohmann::json parseInterfaceData(
+ const std::string &iface_id, const EthernetInterfaceData ð_data,
+ const std::vector<IPv4AddressData> &ipv4_data) {
+ // Copy JSON object to avoid race condition
+ nlohmann::json json_response(Node::json);
+
+ // Fill out obvious data...
+ json_response["Id"] = iface_id;
+ json_response["@odata.id"] =
+ "/redfish/v1/Managers/openbmc/EthernetInterfaces/" + iface_id;
+
+ // ... then the one from DBus, regarding eth iface...
+ if (eth_data.speed != nullptr) json_response["SpeedMbps"] = *eth_data.speed;
+
+ if (eth_data.mac_address != nullptr)
+ json_response["MACAddress"] = *eth_data.mac_address;
+
+ if (eth_data.hostname != nullptr)
+ json_response["HostName"] = *eth_data.hostname;
+
+ if (eth_data.vlan_id != nullptr) {
+ nlohmann::json &vlanObj = json_response["VLAN"];
+ vlanObj["VLANEnable"] = true;
+ vlanObj["VLANId"] = *eth_data.vlan_id;
+ }
+
+ // ... at last, check if there are IPv4 data and prepare appropriate
+ // collection
+ if (ipv4_data.size() > 0) {
+ nlohmann::json ipv4_array = nlohmann::json::array();
+ for (auto &ipv4_config : ipv4_data) {
+ nlohmann::json json_ipv4;
+ if (ipv4_config.address != nullptr) {
+ json_ipv4["Address"] = *ipv4_config.address;
+ if (ipv4_config.gateway != nullptr)
+ json_ipv4["Gateway"] = *ipv4_config.gateway;
+
+ json_ipv4["AddressOrigin"] = ipv4_config.origin;
+ json_ipv4["SubnetMask"] = ipv4_config.netmask;
+
+ ipv4_array.push_back(std::move(json_ipv4));
+ }
+ }
+ json_response["IPv4Addresses"] = std::move(ipv4_array);
+ }
+
+ return json_response;
+ }
+
/**
* Functions triggers appropriate requests on DBus
*/
@@ -456,49 +719,7 @@
const EthernetInterfaceData ð_data,
const std::vector<IPv4AddressData> &ipv4_data) {
if (success) {
- // Copy JSON object to avoid race condition
- nlohmann::json json_response(Node::json);
-
- // Fill out obvious data...
- json_response["Id"] = iface_id;
- json_response["@odata.id"] =
- "/redfish/v1/Managers/openbmc/EthernetInterfaces/" + iface_id;
-
- // ... then the one from DBus, regarding eth iface...
- if (eth_data.speed != nullptr)
- json_response["SpeedMbps"] = *eth_data.speed;
-
- if (eth_data.mac_address != nullptr)
- json_response["MACAddress"] = *eth_data.mac_address;
-
- if (eth_data.hostname != nullptr)
- json_response["HostName"] = *eth_data.hostname;
-
- if (eth_data.vlan_id != nullptr) {
- json_response["VLAN"]["VLANEnable"] = true;
- json_response["VLAN"]["VLANId"] = *eth_data.vlan_id;
- }
-
- // ... at last, check if there are IPv4 data and prepare appropriate
- // collection
- if (ipv4_data.size() > 0) {
- nlohmann::json ipv4_array = nlohmann::json::array();
- for (auto &ipv4_config : ipv4_data) {
- nlohmann::json json_ipv4;
- if (ipv4_config.address != nullptr) {
- json_ipv4["Address"] = *ipv4_config.address;
- if (ipv4_config.gateway != nullptr)
- json_ipv4["Gateway"] = *ipv4_config.gateway;
-
- json_ipv4["AddressOrigin"] = ipv4_config.origin;
- json_ipv4["SubnetMask"] = ipv4_config.netmask;
-
- ipv4_array.push_back(json_ipv4);
- }
- }
- json_response["IPv4Addresses"] = ipv4_array;
- }
- res.json_value = std::move(json_response);
+ res.json_value = parseInterfaceData(iface_id, eth_data, ipv4_data);
} else {
// ... otherwise return error
// TODO(Pawel)consider distinguish between non existing object, and
@@ -509,6 +730,83 @@
});
}
+ void doPatch(crow::response &res, const crow::request &req,
+ const std::vector<std::string> ¶ms) override {
+ // TODO(Pawel) this shall be parametrized call (two params) to get
+ // EthernetInterfaces for any Manager, not only hardcoded 'openbmc'.
+ // Check if there is required param, truly entering this shall be
+ // impossible.
+ if (params.size() != 1) {
+ res.result(boost::beast::http::status::internal_server_error);
+ res.end();
+ return;
+ }
+
+ const std::string &iface_id = params[0];
+
+ nlohmann::json patchReq = nlohmann::json::parse(req.body, nullptr, false);
+
+ if (patchReq.is_discarded()) {
+ messages::addMessageToErrorJson(res.json_value,
+ messages::malformedJSON());
+
+ res.result(boost::beast::http::status::bad_request);
+ res.end();
+
+ return;
+ }
+
+ // Get single eth interface data, and call the below callback for JSON
+ // preparation
+ ethernet_provider.getEthernetIfaceData(
+ iface_id,
+ [&, iface_id, patchReq = std::move(patchReq) ](
+ const bool &success, const EthernetInterfaceData ð_data,
+ const std::vector<IPv4AddressData> &ipv4_data) {
+ if (!success) {
+ // ... otherwise return error
+ // TODO(Pawel)consider distinguish between non existing object, and
+ // other errors
+ res.result(boost::beast::http::status::not_found);
+ res.end();
+
+ return;
+ }
+
+ res.json_value = parseInterfaceData(iface_id, eth_data, ipv4_data);
+
+ std::shared_ptr<AsyncResp> asyncResp =
+ std::make_shared<AsyncResp>(res);
+
+ for (auto propertyIt = patchReq.begin(); propertyIt != patchReq.end();
+ ++propertyIt) {
+ if (propertyIt.key() == "VLAN") {
+ handleVlanPatch(iface_id, propertyIt.value(), eth_data,
+ asyncResp);
+ } else if (propertyIt.key() == "HostName") {
+ handleHostnamePatch(propertyIt.value(), eth_data, asyncResp);
+ /* TODO(kkowalsk) Implement it in further patchset
+ } else if (propertyIt.key() == "IPv4Addresses" || propertyIt.key()
+ == "IPv6Addresses") {*/
+ } else {
+ auto fieldInJsonIt = res.json_value.find(propertyIt.key());
+
+ if (fieldInJsonIt == res.json_value.end()) {
+ // Field not in scope of defined fields
+ messages::addMessageToJsonRoot(
+ res.json_value,
+ messages::propertyUnknown(propertyIt.key()));
+ } else if (*fieldInJsonIt != *propertyIt) {
+ // User attempted to modify non-writable field
+ messages::addMessageToJsonRoot(
+ res.json_value,
+ messages::propertyNotWritable(propertyIt.key()));
+ }
+ }
+ }
+ });
+ }
+
// Ethernet Provider object
// TODO(Pawel) consider move it to singleton
OnDemandEthernetProvider ethernet_provider;