Implement VlanNetworkInterface Schema DELETE and PATCH methods
Does not introduce regression. Tested on HW platform and x86 dev system.
Change-Id: I1e8894367e664de4900f7a528d1ce8ee4d66ac1c
Signed-off-by: Kowalski, Kamil <kamil.kowalski@intel.com>
diff --git a/redfish-core/lib/ethernet.hpp b/redfish-core/lib/ethernet.hpp
index a3b1701..6c2bf6c 100644
--- a/redfish-core/lib/ethernet.hpp
+++ b/redfish-core/lib/ethernet.hpp
@@ -286,8 +286,9 @@
* @return None.
*/
template <typename CallbackFunc>
- void changeVlanId(const std::string &ifaceId, const uint32_t &inputVlanId,
- CallbackFunc &&callback) {
+ static 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,
@@ -491,7 +492,7 @@
* @return None.
*/
template <typename CallbackFunc>
- void disableVlan(const std::string &ifaceId, CallbackFunc &&callback) {
+ static 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,
@@ -826,27 +827,36 @@
{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) {
+ // TODO(kkowalsk) Find a suitable class/namespace for this
+ static void handleVlanPatch(const std::string &ifaceId,
+ const nlohmann::json &input,
+ const EthernetInterfaceData ð_data,
+ const std::string &pathPrefix,
+ const std::shared_ptr<AsyncResp> &asyncResp) {
if (!input.is_object()) {
messages::addMessageToJson(
asyncResp->res.json_value,
- messages::propertyValueTypeError(input.dump(), "VLAN"), "/VLAN");
+ messages::propertyValueTypeError(input.dump(), "VLAN"), pathPrefix);
return;
}
+ const std::string pathStart = (pathPrefix == "/") ? "" : pathPrefix;
+ nlohmann::json ¶msJson =
+ (pathPrefix == "/")
+ ? asyncResp->res.json_value
+ : asyncResp->res.json_value[nlohmann::json_pointer<nlohmann::json>(
+ pathPrefix)];
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"));
+ asyncResp->res.json_value, std::string(pathStart + "/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"));
+ asyncResp->res.json_value, std::string(pathStart + "/VLANId"));
bool inputInvalid = false;
// Do not proceed if fields in VLAN object were of wrong type
@@ -857,31 +867,13 @@
// 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");
+ // This interface is not a VLAN. Cannot do anything with it
+ // TODO(kkowalsk) Change this message
+ messages::addMessageToJson(asyncResp->res.json_value,
+ messages::propertyMissing("VLANEnable"),
+ pathPrefix);
- messages::addMessageToJson(asyncResp->res.json_value,
- messages::propertyMissing("VLANEnable"),
- "/VLAN");
-
- inputInvalid = true;
- }
+ inputInvalid = true;
} else {
// Load actual data into field values if they were not provided
if (inputVlanEnabledState == json_util::Result::NOT_EXIST) {
@@ -898,45 +890,37 @@
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;
- }
- });
- }
+ // VLAN is configured on the interface
+ if (inputVlanEnabled == true && inputVlanId != *eth_data.vlan_id) {
+ // Change VLAN Id
+ paramsJson["VLANId"] = inputVlanId;
+ OnDemandEthernetProvider::changeVlanId(
+ ifaceId, static_cast<uint32_t>(inputVlanId),
+ [&, asyncResp, pathPrefx{std::move(pathPrefix)} ](
+ const boost::system::error_code ec) {
+ if (ec) {
+ messages::addMessageToJson(asyncResp->res.json_value,
+ messages::internalError(), pathPrefix);
+ } else {
+ paramsJson["VLANEnable"] = true;
+ }
+ });
+ } else if (inputVlanEnabled == false) {
+ // Disable VLAN
+ OnDemandEthernetProvider::disableVlan(
+ ifaceId, [&, asyncResp, pathPrefx{std::move(pathPrefix)} ](
+ const boost::system::error_code ec) {
+ if (ec) {
+ messages::addMessageToJson(asyncResp->res.json_value,
+ messages::internalError(), pathPrefix);
+ } else {
+ paramsJson["VLANEnable"] = false;
+ }
+ });
}
}
+ private:
void handleHostnamePatch(const nlohmann::json &input,
const EthernetInterfaceData ð_data,
const std::shared_ptr<AsyncResp> &asyncResp) {
@@ -1260,6 +1244,9 @@
// ... otherwise return error
// TODO(Pawel)consider distinguish between non existing object, and
// other errors
+ messages::addMessageToErrorJson(
+ res.json_value,
+ messages::resourceNotFound("EthernetInterface", iface_id));
res.result(boost::beast::http::status::not_found);
}
res.end();
@@ -1311,7 +1298,7 @@
for (auto propertyIt = patchReq.begin(); propertyIt != patchReq.end();
++propertyIt) {
if (propertyIt.key() == "VLAN") {
- handleVlanPatch(iface_id, propertyIt.value(), eth_data,
+ handleVlanPatch(iface_id, propertyIt.value(), eth_data, "/VLAN",
asyncResp);
} else if (propertyIt.key() == "HostName") {
handleHostnamePatch(propertyIt.value(), eth_data, asyncResp);
@@ -1347,4 +1334,424 @@
OnDemandEthernetProvider ethernet_provider;
};
+class VlanNetworkInterfaceCollection;
+
+/**
+ * VlanNetworkInterface derived class for delivering VLANNetworkInterface Schema
+ */
+class VlanNetworkInterface : public Node {
+ public:
+ /*
+ * Default Constructor
+ */
+ template <typename CrowApp>
+ // TODO(Pawel) Remove line from below, where we assume that there is only one
+ // manager called openbmc This shall be generic, but requires to update
+ // GetSubroutes method
+ VlanNetworkInterface(CrowApp &app)
+ : Node(app,
+ "/redfish/v1/Managers/openbmc/EthernetInterfaces/<str>/VLANs/"
+ "<str>",
+ std::string(), std::string()) {
+ Node::json["@odata.type"] =
+ "#VLanNetworkInterface.v1_1_0.VLanNetworkInterface";
+ Node::json["@odata.context"] =
+ "/redfish/v1/$metadata#VLanNetworkInterface.VLanNetworkInterface";
+ Node::json["Name"] = "VLAN 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"}}}};
+ }
+
+ private:
+ nlohmann::json parseInterfaceData(
+ const std::string &parent_iface_id, 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/" + parent_iface_id +
+ "/VLANs/" + iface_id;
+
+ json_response["VLANEnable"] = true;
+ json_response["VLANId"] = *eth_data.vlan_id;
+
+ return json_response;
+ }
+
+ bool verifyNames(crow::response &res, const std::string &parent,
+ const std::string &iface) {
+ if (!boost::starts_with(iface, parent + "_")) {
+ messages::addMessageToErrorJson(
+ res.json_value,
+ messages::resourceNotFound("VLAN Network Interface", iface));
+ res.result(boost::beast::http::status::bad_request);
+ res.end();
+
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Functions triggers appropriate requests on DBus
+ */
+ void doGet(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() != 2) {
+ res.result(boost::beast::http::status::internal_server_error);
+ res.end();
+ return;
+ }
+
+ const std::string &parent_iface_id = params[0];
+ const std::string &iface_id = params[1];
+
+ if (!verifyNames(res, parent_iface_id, iface_id)) {
+ return;
+ }
+
+ // Get single eth interface data, and call the below callback for JSON
+ // preparation
+ ethernet_provider.getEthernetIfaceData(
+ iface_id,
+ [&, parent_iface_id, iface_id](
+ const bool &success, const EthernetInterfaceData ð_data,
+ const std::vector<IPv4AddressData> &ipv4_data) {
+ if (success && eth_data.vlan_id != nullptr) {
+ res.json_value = parseInterfaceData(parent_iface_id, iface_id,
+ eth_data, ipv4_data);
+ } else {
+ // ... otherwise return error
+ // TODO(Pawel)consider distinguish between non existing object,
+ // and
+ // other errors
+ res.result(boost::beast::http::status::not_found);
+ }
+ res.end();
+ });
+ }
+
+ void doPatch(crow::response &res, const crow::request &req,
+ const std::vector<std::string> ¶ms) override {
+ if (params.size() != 2) {
+ res.result(boost::beast::http::status::internal_server_error);
+ res.end();
+ return;
+ }
+
+ const std::string &parent_iface_id = params[0];
+ const std::string &iface_id = params[1];
+
+ if (!verifyNames(res, parent_iface_id, iface_id)) {
+ return;
+ }
+
+ nlohmann::json patchReq;
+
+ if (!json_util::processJsonFromRequest(res, req, patchReq)) {
+ return;
+ }
+
+ // Get single eth interface data, and call the below callback for JSON
+ // preparation
+ ethernet_provider.getEthernetIfaceData(
+ iface_id,
+ [&, parent_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(parent_iface_id, 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() != "VLANEnable" &&
+ propertyIt.key() != "VLANId") {
+ 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()));
+ }
+ }
+ }
+
+ EthernetInterface::handleVlanPatch(iface_id, patchReq, eth_data, "/",
+ asyncResp);
+ });
+ }
+
+ void doDelete(crow::response &res, const crow::request &req,
+ const std::vector<std::string> ¶ms) override {
+ if (params.size() != 2) {
+ res.result(boost::beast::http::status::internal_server_error);
+ res.end();
+ return;
+ }
+
+ const std::string &parent_iface_id = params[0];
+ const std::string &iface_id = params[1];
+
+ if (!verifyNames(res, parent_iface_id, iface_id)) {
+ return;
+ }
+
+ // Get single eth interface data, and call the below callback for JSON
+ // preparation
+ ethernet_provider.getEthernetIfaceData(
+ iface_id,
+ [&, parent_iface_id, iface_id](
+ const bool &success, const EthernetInterfaceData ð_data,
+ const std::vector<IPv4AddressData> &ipv4_data) {
+ if (success && eth_data.vlan_id != nullptr) {
+ res.json_value = parseInterfaceData(parent_iface_id, iface_id,
+ eth_data, ipv4_data);
+
+ // Disable VLAN
+ OnDemandEthernetProvider::disableVlan(
+ iface_id, [&](const boost::system::error_code ec) {
+ if (ec) {
+ res.json_value = nlohmann::json::object();
+ messages::addMessageToErrorJson(res.json_value,
+ messages::internalError());
+ res.result(
+ boost::beast::http::status::internal_server_error);
+ }
+ res.end();
+ });
+ } else {
+ // ... otherwise return error
+ // TODO(Pawel)consider distinguish between non existing object,
+ // and
+ // other errors
+
+ res.result(boost::beast::http::status::not_found);
+ res.end();
+ }
+ });
+ }
+
+ /**
+ * This allows VlanNetworkInterfaceCollection to reuse this class' doGet
+ * method, to maintain consistency of returned data, as Collection's doPost
+ * should return data for created member which should match member's doGet
+ * result in 100%.
+ */
+ friend VlanNetworkInterfaceCollection;
+
+ // Ethernet Provider object
+ // TODO(Pawel) consider move it to singleton
+ OnDemandEthernetProvider ethernet_provider;
+};
+
+/**
+ * VlanNetworkInterfaceCollection derived class for delivering
+ * VLANNetworkInterface Collection Schema
+ */
+class VlanNetworkInterfaceCollection : public Node {
+ public:
+ template <typename CrowApp>
+ // TODO(Pawel) Remove line from below, where we assume that there is only one
+ // manager called openbmc This shall be generic, but requires to update
+ // GetSubroutes method
+ VlanNetworkInterfaceCollection(CrowApp &app)
+ : Node(app,
+ "/redfish/v1/Managers/openbmc/EthernetInterfaces/<str>/VLANs/",
+ std::string()),
+ memberVlan(app) {
+ Node::json["@odata.type"] =
+ "#VLanNetworkInterfaceCollection.VLanNetworkInterfaceCollection";
+ Node::json["@odata.context"] =
+ "/redfish/v1/$metadata"
+ "#VLanNetworkInterfaceCollection.VLanNetworkInterfaceCollection";
+ Node::json["Name"] = "VLAN Network Interface Collection";
+
+ 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:
+ /**
+ * Functions triggers appropriate requests on DBus
+ */
+ void doGet(crow::response &res, const crow::request &req,
+ const std::vector<std::string> ¶ms) override {
+ if (params.size() != 1) {
+ // This means there is a problem with the router
+ res.result(boost::beast::http::status::internal_server_error);
+ res.end();
+
+ return;
+ }
+
+ // TODO(Pawel) this shall be parametrized call to get EthernetInterfaces for
+ // any Manager, not only hardcoded 'openbmc'.
+ std::string manager_id = "openbmc";
+ std::string rootInterfaceName = params[0];
+
+ // Get eth interface list, and call the below callback for JSON preparation
+ ethernet_provider.getEthernetIfaceList([
+ &, manager_id{std::move(manager_id)},
+ rootInterfaceName{std::move(rootInterfaceName)}
+ ](const bool &success, const std::vector<std::string> &iface_list) {
+ if (success) {
+ bool rootInterfaceFound = false;
+ nlohmann::json iface_array = nlohmann::json::array();
+
+ for (const std::string &iface_item : iface_list) {
+ if (iface_item == rootInterfaceName) {
+ rootInterfaceFound = true;
+ } else if (boost::starts_with(iface_item, rootInterfaceName + "_")) {
+ iface_array.push_back(
+ {{"@odata.id", "/redfish/v1/Managers/" + manager_id +
+ "/EthernetInterfaces/" + rootInterfaceName +
+ "/VLANs/" + iface_item}});
+ }
+ }
+
+ if (rootInterfaceFound) {
+ Node::json["Members"] = iface_array;
+ Node::json["Members@odata.count"] = iface_array.size();
+ Node::json["@odata.id"] = "/redfish/v1/Managers/" + manager_id +
+ "/EthernetInterfaces/" + rootInterfaceName +
+ "/VLANs";
+ res.json_value = Node::json;
+ } else {
+ messages::addMessageToErrorJson(
+ res.json_value, messages::resourceNotFound("EthernetInterface",
+ rootInterfaceName));
+ res.result(boost::beast::http::status::not_found);
+ res.end();
+ }
+ } else {
+ // No success, best what we can do is return INTERNALL ERROR
+ res.result(boost::beast::http::status::internal_server_error);
+ }
+ res.end();
+ });
+ }
+
+ void doPost(crow::response &res, const crow::request &req,
+ const std::vector<std::string> ¶ms) override {
+ if (params.size() != 1) {
+ // This means there is a problem with the router
+ res.result(boost::beast::http::status::internal_server_error);
+ res.end();
+ return;
+ }
+
+ nlohmann::json postReq;
+
+ if (!json_util::processJsonFromRequest(res, req, postReq)) {
+ return;
+ }
+
+ // TODO(Pawel) this shall be parametrized call to get EthernetInterfaces for
+ // any Manager, not only hardcoded 'openbmc'.
+ std::string manager_id = "openbmc";
+ std::string rootInterfaceName = params[0];
+ uint64_t vlanId;
+ bool errorDetected;
+
+ if (json_util::getUnsigned(
+ "VLANId", postReq, vlanId,
+ static_cast<uint8_t>(json_util::MessageSetting::MISSING) |
+ static_cast<uint8_t>(json_util::MessageSetting::TYPE_ERROR),
+ res.json_value, "/VLANId") != json_util::Result::SUCCESS) {
+ res.end();
+ return;
+ }
+
+ // Get eth interface list, and call the below callback for JSON preparation
+ ethernet_provider.getEthernetIfaceList([
+ &, manager_id{std::move(manager_id)},
+ rootInterfaceName{std::move(rootInterfaceName)}
+ ](const bool &success, const std::vector<std::string> &iface_list) {
+ if (success) {
+ bool rootInterfaceFound = false;
+
+ for (const std::string &iface_item : iface_list) {
+ if (iface_item == rootInterfaceName) {
+ rootInterfaceFound = true;
+ break;
+ }
+ }
+
+ if (rootInterfaceFound) {
+ ethernet_provider.createVlan(
+ rootInterfaceName, vlanId,
+ [&, vlanId, rootInterfaceName,
+ req{std::move(req)} ](const boost::system::error_code ec) {
+ if (ec) {
+ messages::addMessageToErrorJson(res.json_value,
+ messages::internalError());
+ res.end();
+ } else {
+ memberVlan.doGet(
+ res, req,
+ {rootInterfaceName,
+ rootInterfaceName + "_" + std::to_string(vlanId)});
+ }
+ });
+ } else {
+ messages::addMessageToErrorJson(
+ res.json_value, messages::resourceNotFound("EthernetInterface",
+ rootInterfaceName));
+ res.result(boost::beast::http::status::not_found);
+ res.end();
+ }
+ } else {
+ // No success, best what we can do is return INTERNALL ERROR
+ res.result(boost::beast::http::status::internal_server_error);
+ res.end();
+ }
+ });
+ }
+
+ // Ethernet Provider object
+ // TODO(Pawel) consider move it to singleton
+ OnDemandEthernetProvider ethernet_provider;
+ VlanNetworkInterface memberVlan;
+};
+
} // namespace redfish