Rapkiewicz, Pawel | 9391bb9 | 2018-03-20 03:12:18 +0100 | [diff] [blame^] | 1 | /* |
| 2 | // Copyright (c) 2018 Intel Corporation |
| 3 | // |
| 4 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | // you may not use this file except in compliance with the License. |
| 6 | // You may obtain a copy of the License at |
| 7 | // |
| 8 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | // |
| 10 | // Unless required by applicable law or agreed to in writing, software |
| 11 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | // See the License for the specific language governing permissions and |
| 14 | // limitations under the License. |
| 15 | */ |
| 16 | #pragma once |
| 17 | |
| 18 | #include "node.hpp" |
| 19 | #include <boost/container/flat_map.hpp> |
| 20 | |
| 21 | namespace redfish { |
| 22 | |
| 23 | /** |
| 24 | * DBus types primitives for several generic DBus interfaces |
| 25 | * TODO(Pawel) consider move this to separate file into boost::dbus |
| 26 | */ |
| 27 | using PropertiesMapType = |
| 28 | boost::container::flat_map<std::string, dbus::dbus_variant>; |
| 29 | |
| 30 | using GetManagedObjectsType = boost::container::flat_map< |
| 31 | dbus::object_path, |
| 32 | boost::container::flat_map<std::string, PropertiesMapType>>; |
| 33 | |
| 34 | using GetAllPropertiesType = PropertiesMapType; |
| 35 | |
| 36 | /** |
| 37 | * Structure for keeping IPv4 data required by Redfish |
| 38 | * TODO(Pawel) consider change everything to ptr, or to non-ptr values. |
| 39 | */ |
| 40 | struct IPv4AddressData { |
| 41 | const std::string *address; |
| 42 | const std::string *domain; |
| 43 | const std::string *gateway; |
| 44 | std::string netmask; |
| 45 | std::string origin; |
| 46 | bool global; |
| 47 | }; |
| 48 | |
| 49 | /** |
| 50 | * Structure for keeping basic single Ethernet Interface information |
| 51 | * available from DBus |
| 52 | */ |
| 53 | struct EthernetInterfaceData { |
| 54 | const unsigned int *speed; |
| 55 | const bool *auto_neg; |
| 56 | const std::string *hostname; |
| 57 | const std::string *default_gateway; |
| 58 | const std::string *mac_address; |
| 59 | }; |
| 60 | |
| 61 | /** |
| 62 | * OnDemandEthernetProvider |
| 63 | * Ethernet provider class that retrieves data directly from dbus, before seting |
| 64 | * it into JSON output. This does not cache any data. |
| 65 | * |
| 66 | * TODO(Pawel) |
| 67 | * This perhaps shall be different file, which has to be chosen on compile time |
| 68 | * depending on OEM needs |
| 69 | */ |
| 70 | class OnDemandEthernetProvider { |
| 71 | private: |
| 72 | // Consts that may have influence on EthernetProvider performance/memory usage |
| 73 | const size_t MAX_IPV4_ADDRESSES_PER_INTERFACE = 10; |
| 74 | |
| 75 | // Helper function that allows to extract GetAllPropertiesType from |
| 76 | // GetManagedObjectsType, based on object path, and interface name |
| 77 | const PropertiesMapType *extractInterfaceProperties( |
| 78 | const dbus::object_path &objpath, const std::string &interface, |
| 79 | const GetManagedObjectsType &dbus_data) { |
| 80 | const auto &dbus_obj = dbus_data.find(objpath); |
| 81 | if (dbus_obj != dbus_data.end()) { |
| 82 | const auto &iface = dbus_obj->second.find(interface); |
| 83 | if (iface != dbus_obj->second.end()) { |
| 84 | return &iface->second; |
| 85 | } |
| 86 | } |
| 87 | return nullptr; |
| 88 | } |
| 89 | |
| 90 | // Helper Wrapper that does inline object_path conversion from string |
| 91 | // into dbus::object_path type |
| 92 | inline const PropertiesMapType *extractInterfaceProperties( |
| 93 | const std::string &objpath, const std::string &interface, |
| 94 | const GetManagedObjectsType &dbus_data) { |
| 95 | const auto &dbus_obj = dbus::object_path{objpath}; |
| 96 | return extractInterfaceProperties(dbus_obj, interface, dbus_data); |
| 97 | } |
| 98 | |
| 99 | // Helper function that allows to get pointer to the property from |
| 100 | // GetAllPropertiesType native, or extracted by GetAllPropertiesType |
| 101 | template <typename T> |
| 102 | inline const T *extractProperty(const PropertiesMapType &properties, |
| 103 | const std::string &name) { |
| 104 | const auto &property = properties.find(name); |
| 105 | if (property != properties.end()) { |
| 106 | return boost::get<T>(&property->second); |
| 107 | } |
| 108 | return nullptr; |
| 109 | } |
| 110 | // TODO(Pawel) Consider to move the above functions to dbus |
| 111 | // generic_interfaces.hpp |
| 112 | |
| 113 | // Helper function that extracts data from several dbus objects and several |
| 114 | // interfaces required by single ethernet interface instance |
| 115 | void extractEthernetInterfaceData(const std::string ðiface_id, |
| 116 | const GetManagedObjectsType &dbus_data, |
| 117 | EthernetInterfaceData ð_data) { |
| 118 | // Extract data that contains MAC Address |
| 119 | const PropertiesMapType *mac_properties = extractInterfaceProperties( |
| 120 | "/xyz/openbmc_project/network/" + ethiface_id, |
| 121 | "xyz.openbmc_project.Network.MACAddress", dbus_data); |
| 122 | |
| 123 | if (mac_properties != nullptr) { |
| 124 | eth_data.mac_address = |
| 125 | extractProperty<std::string>(*mac_properties, "MACAddress"); |
| 126 | } |
| 127 | |
| 128 | // Extract data that contains link information (auto negotiation and speed) |
| 129 | const PropertiesMapType *eth_properties = extractInterfaceProperties( |
| 130 | "/xyz/openbmc_project/network/" + ethiface_id, |
| 131 | "xyz.openbmc_project.Network.EthernetInterface", dbus_data); |
| 132 | |
| 133 | if (eth_properties != nullptr) { |
| 134 | eth_data.auto_neg = extractProperty<bool>(*eth_properties, "AutoNeg"); |
| 135 | eth_data.speed = extractProperty<unsigned int>(*eth_properties, "Speed"); |
| 136 | } |
| 137 | |
| 138 | // Extract data that contains network config (HostName and DefaultGW) |
| 139 | const PropertiesMapType *config_properties = extractInterfaceProperties( |
| 140 | "/xyz/openbmc_project/network/config", |
| 141 | "xyz.openbmc_project.Network.SystemConfiguration", dbus_data); |
| 142 | |
| 143 | if (config_properties != nullptr) { |
| 144 | eth_data.hostname = |
| 145 | extractProperty<std::string>(*config_properties, "HostName"); |
| 146 | eth_data.default_gateway = |
| 147 | extractProperty<std::string>(*config_properties, "DefaultGateway"); |
| 148 | } |
| 149 | } |
| 150 | |
| 151 | // Helper function that changes bits netmask notation (i.e. /24) |
| 152 | // into full dot notation |
| 153 | inline std::string getNetmask(unsigned int bits) { |
| 154 | uint32_t value = 0xffffffff << (32 - bits); |
| 155 | std::string netmask = std::to_string((value >> 24) & 0xff) + "." + |
| 156 | std::to_string((value >> 16) & 0xff) + "." + |
| 157 | std::to_string((value >> 8) & 0xff) + "." + |
| 158 | std::to_string(value & 0xff); |
| 159 | return netmask; |
| 160 | } |
| 161 | |
| 162 | // Helper function that extracts data for single ethernet ipv4 address |
| 163 | void extractIPv4Data(const std::string ðiface_id, |
| 164 | const GetManagedObjectsType &dbus_data, |
| 165 | std::vector<IPv4AddressData> &ipv4_config) { |
| 166 | // Since there might be several IPv4 configurations aligned with |
| 167 | // single ethernet interface, loop over all of them |
| 168 | for (auto &objpath : dbus_data) { |
| 169 | // Check if propper patter for object path appears |
| 170 | if (boost::starts_with( |
| 171 | objpath.first.value, |
| 172 | "/xyz/openbmc_project/network/" + ethiface_id + "/ipv4/")) { |
| 173 | // and get approrpiate interface |
| 174 | const auto &interface = |
| 175 | objpath.second.find("xyz.openbmc_project.Network.IP"); |
| 176 | if (interface != objpath.second.end()) { |
| 177 | // Make a properties 'shortcut', to make everything more readable |
| 178 | const PropertiesMapType &properties = interface->second; |
| 179 | // Instance IPv4AddressData structure, and set as appropriate |
| 180 | IPv4AddressData ipv4_address; |
| 181 | // IPv4 address |
| 182 | ipv4_address.address = |
| 183 | extractProperty<std::string>(properties, "Address"); |
| 184 | // IPv4 gateway |
| 185 | ipv4_address.gateway = |
| 186 | extractProperty<std::string>(properties, "Gateway"); |
| 187 | |
| 188 | // Origin is kind of DBus object so fetch pointer... |
| 189 | const std::string *origin = |
| 190 | extractProperty<std::string>(properties, "Origin"); |
| 191 | if (origin != nullptr) { |
| 192 | // ... and get everything after last dot |
| 193 | int last = origin->rfind("."); |
| 194 | if (last != std::string::npos) { |
| 195 | ipv4_address.origin = origin->substr(last + 1); |
| 196 | } |
| 197 | } |
| 198 | |
| 199 | // Netmask is presented as PrefixLength |
| 200 | const auto *mask = |
| 201 | extractProperty<uint8_t>(properties, "PrefixLength"); |
| 202 | if (mask != nullptr) { |
| 203 | // convert it to the string |
| 204 | ipv4_address.netmask = getNetmask(*mask); |
| 205 | } |
| 206 | |
| 207 | // Attach IPv4 only if address is present |
| 208 | if (ipv4_address.address != nullptr) { |
| 209 | // Check if given addres is local, or global |
| 210 | if (boost::starts_with(*ipv4_address.address, "169.254")) { |
| 211 | ipv4_address.global = false; |
| 212 | } else { |
| 213 | ipv4_address.global = true; |
| 214 | } |
| 215 | ipv4_config.emplace_back(std::move(ipv4_address)); |
| 216 | } |
| 217 | } |
| 218 | } |
| 219 | } |
| 220 | } |
| 221 | |
| 222 | public: |
| 223 | /** |
| 224 | * Function that retrieves all properties for given Ethernet Interface Object |
| 225 | * from EntityManager Network Manager |
| 226 | * @param ethiface_id a eth interface id to query on DBus |
| 227 | * @param callback a function that shall be called to convert Dbus output into |
| 228 | * JSON |
| 229 | */ |
| 230 | template <typename CallbackFunc> |
| 231 | void getEthernetIfaceData(const std::string ðiface_id, |
| 232 | CallbackFunc &&callback) { |
| 233 | crow::connections::system_bus->async_method_call( |
| 234 | [ |
| 235 | this, ethiface_id{std::move(ethiface_id)}, |
| 236 | callback{std::move(callback)} |
| 237 | ](const boost::system::error_code error_code, |
| 238 | const GetManagedObjectsType &resp) { |
| 239 | |
| 240 | EthernetInterfaceData eth_data; |
| 241 | std::vector<IPv4AddressData> ipv4_data; |
| 242 | ipv4_data.reserve(MAX_IPV4_ADDRESSES_PER_INTERFACE); |
| 243 | |
| 244 | if (error_code) { |
| 245 | // Something wrong on DBus, the error_code is not important at this |
| 246 | // moment, just return success=false, and empty output. Since size |
| 247 | // of vector may vary depending on information from Network Manager, |
| 248 | // and empty output could not be treated same way as error. |
| 249 | callback(false, eth_data, ipv4_data); |
| 250 | return; |
| 251 | } |
| 252 | |
| 253 | extractEthernetInterfaceData(ethiface_id, resp, eth_data); |
| 254 | extractIPv4Data(ethiface_id, resp, ipv4_data); |
| 255 | |
| 256 | // Fix global GW |
| 257 | for (IPv4AddressData &ipv4 : ipv4_data) { |
| 258 | if ((ipv4.global) && |
| 259 | ((ipv4.gateway == nullptr) || (*ipv4.gateway == "0.0.0.0"))) { |
| 260 | ipv4.gateway = eth_data.default_gateway; |
| 261 | } |
| 262 | } |
| 263 | |
| 264 | // Finally make a callback with usefull data |
| 265 | callback(true, eth_data, ipv4_data); |
| 266 | }, |
| 267 | {"xyz.openbmc_project.Network", "/xyz/openbmc_project/network", |
| 268 | "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"}); |
| 269 | }; |
| 270 | |
| 271 | /** |
| 272 | * Function that retrieves all Ethernet Interfaces available through Network |
| 273 | * Manager |
| 274 | * @param callback a function that shall be called to convert Dbus output into |
| 275 | * JSON. |
| 276 | */ |
| 277 | template <typename CallbackFunc> |
| 278 | void getEthernetIfaceList(CallbackFunc &&callback) { |
| 279 | crow::connections::system_bus->async_method_call( |
| 280 | [ this, callback{std::move(callback)} ]( |
| 281 | const boost::system::error_code error_code, |
| 282 | const GetManagedObjectsType &resp) { |
| 283 | // Callback requires vector<string> to retrieve all available ethernet |
| 284 | // interfaces |
| 285 | std::vector<std::string> iface_list; |
| 286 | iface_list.reserve(resp.size()); |
| 287 | if (error_code) { |
| 288 | // Something wrong on DBus, the error_code is not important at this |
| 289 | // moment, just return success=false, and empty output. Since size |
| 290 | // of vector may vary depending on information from Network Manager, |
| 291 | // and empty output could not be treated same way as error. |
| 292 | callback(false, iface_list); |
| 293 | return; |
| 294 | } |
| 295 | |
| 296 | // Iterate over all retrieved ObjectPaths. |
| 297 | for (auto &objpath : resp) { |
| 298 | // And all interfaces available for certain ObjectPath. |
| 299 | for (auto &interface : objpath.second) { |
| 300 | // If interface is xyz.openbmc_project.Network.EthernetInterface, |
| 301 | // this is what we're looking for. |
| 302 | if (interface.first == |
| 303 | "xyz.openbmc_project.Network.EthernetInterface") { |
| 304 | // Cut out everyting until last "/", ... |
| 305 | const std::string &iface_id = objpath.first.value; |
| 306 | std::size_t last_pos = iface_id.rfind("/"); |
| 307 | if (last_pos != std::string::npos) { |
| 308 | // and put it into output vector. |
| 309 | iface_list.emplace_back(iface_id.substr(last_pos + 1)); |
| 310 | } |
| 311 | } |
| 312 | } |
| 313 | } |
| 314 | // Finally make a callback with usefull data |
| 315 | callback(true, iface_list); |
| 316 | }, |
| 317 | {"xyz.openbmc_project.Network", "/xyz/openbmc_project/network", |
| 318 | "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"}); |
| 319 | }; |
| 320 | }; |
| 321 | |
| 322 | /** |
| 323 | * EthernetCollection derived class for delivering Ethernet Collection Schema |
| 324 | */ |
| 325 | class EthernetCollection : public Node { |
| 326 | public: |
| 327 | template <typename CrowApp> |
| 328 | // TODO(Pawel) Remove line from below, where we assume that there is only one |
| 329 | // manager called openbmc This shall be generic, but requires to update |
| 330 | // GetSubroutes method |
| 331 | EthernetCollection(CrowApp &app) |
| 332 | : Node(app, "/redfish/v1/Managers/openbmc/EthernetInterfaces/") { |
| 333 | Node::json["@odata.type"] = |
| 334 | "#EthernetInterfaceCollection.EthernetInterfaceCollection"; |
| 335 | Node::json["@odata.context"] = |
| 336 | "/redfish/v1/" |
| 337 | "$metadata#EthernetInterfaceCollection.EthernetInterfaceCollection"; |
| 338 | Node::json["@odata.id"] = "/redfish/v1/Managers/openbmc/EthernetInterfaces"; |
| 339 | Node::json["Name"] = "Ethernet Network Interface Collection"; |
| 340 | Node::json["Description"] = |
| 341 | "Collection of EthernetInterfaces for this Manager"; |
| 342 | |
| 343 | entityPrivileges = {{crow::HTTPMethod::GET, {{"Login"}}}, |
| 344 | {crow::HTTPMethod::HEAD, {{"Login"}}}, |
| 345 | {crow::HTTPMethod::PATCH, {{"ConfigureComponents"}}}, |
| 346 | {crow::HTTPMethod::PUT, {{"ConfigureComponents"}}}, |
| 347 | {crow::HTTPMethod::DELETE, {{"ConfigureComponents"}}}, |
| 348 | {crow::HTTPMethod::POST, {{"ConfigureComponents"}}}}; |
| 349 | } |
| 350 | |
| 351 | private: |
| 352 | /** |
| 353 | * Functions triggers appropriate requests on DBus |
| 354 | */ |
| 355 | void doGet(crow::response &res, const crow::request &req, |
| 356 | const std::vector<std::string> ¶ms) override { |
| 357 | // TODO(Pawel) this shall be parametrized call to get EthernetInterfaces for |
| 358 | // any Manager, not only hardcoded 'openbmc'. |
| 359 | std::string manager_id = "openbmc"; |
| 360 | |
| 361 | // Get eth interface list, and call the below callback for JSON preparation |
| 362 | ethernet_provider.getEthernetIfaceList( |
| 363 | [&, manager_id{std::move(manager_id)} ]( |
| 364 | const bool &success, const std::vector<std::string> &iface_list) { |
| 365 | if (success) { |
| 366 | nlohmann::json iface_array = nlohmann::json::array(); |
| 367 | for (const std::string &iface_item : iface_list) { |
| 368 | iface_array.push_back( |
| 369 | {{"@odata.id", "/redfish/v1/Managers/" + manager_id + |
| 370 | "/EthernetInterfaces/" + iface_item}}); |
| 371 | } |
| 372 | Node::json["Members"] = iface_array; |
| 373 | Node::json["Members@odata.count"] = iface_array.size(); |
| 374 | Node::json["@odata.id"] = |
| 375 | "/redfish/v1/Managers/" + manager_id + "/EthernetInterfaces"; |
| 376 | res.json_value = Node::json; |
| 377 | } else { |
| 378 | // No success, best what we can do is return INTERNALL ERROR |
| 379 | res.code = static_cast<int>(HttpRespCode::INTERNAL_ERROR); |
| 380 | } |
| 381 | res.end(); |
| 382 | }); |
| 383 | } |
| 384 | |
| 385 | // Ethernet Provider object |
| 386 | // TODO(Pawel) consider move it to singleton |
| 387 | OnDemandEthernetProvider ethernet_provider; |
| 388 | }; |
| 389 | |
| 390 | /** |
| 391 | * EthernetInterface derived class for delivering Ethernet Schema |
| 392 | */ |
| 393 | class EthernetInterface : public Node { |
| 394 | public: |
| 395 | /* |
| 396 | * Default Constructor |
| 397 | */ |
| 398 | template <typename CrowApp> |
| 399 | // TODO(Pawel) Remove line from below, where we assume that there is only one |
| 400 | // manager called openbmc This shall be generic, but requires to update |
| 401 | // GetSubroutes method |
| 402 | EthernetInterface(CrowApp &app) |
| 403 | : Node(app, "/redfish/v1/Managers/openbmc/EthernetInterfaces/<str>/", |
| 404 | std::string()) { |
| 405 | Node::json["@odata.type"] = "#EthernetInterface.v1_2_0.EthernetInterface"; |
| 406 | Node::json["@odata.context"] = |
| 407 | "/redfish/v1/$metadata#EthernetInterface.EthernetInterface"; |
| 408 | Node::json["Name"] = "Manager Ethernet Interface"; |
| 409 | Node::json["Description"] = "Management Network Interface"; |
| 410 | |
| 411 | entityPrivileges = {{crow::HTTPMethod::GET, {{"Login"}}}, |
| 412 | {crow::HTTPMethod::HEAD, {{"Login"}}}, |
| 413 | {crow::HTTPMethod::PATCH, {{"ConfigureComponents"}}}, |
| 414 | {crow::HTTPMethod::PUT, {{"ConfigureComponents"}}}, |
| 415 | {crow::HTTPMethod::DELETE, {{"ConfigureComponents"}}}, |
| 416 | {crow::HTTPMethod::POST, {{"ConfigureComponents"}}}}; |
| 417 | } |
| 418 | |
| 419 | private: |
| 420 | /** |
| 421 | * Functions triggers appropriate requests on DBus |
| 422 | */ |
| 423 | void doGet(crow::response &res, const crow::request &req, |
| 424 | const std::vector<std::string> ¶ms) override { |
| 425 | // TODO(Pawel) this shall be parametrized call (two params) to get |
| 426 | // EthernetInterfaces for any Manager, not only hardcoded 'openbmc'. |
| 427 | // Check if there is required param, truly entering this shall be |
| 428 | // impossible. |
| 429 | if (params.size() != 1) { |
| 430 | res.code = static_cast<int>(HttpRespCode::INTERNAL_ERROR); |
| 431 | res.end(); |
| 432 | return; |
| 433 | } |
| 434 | |
| 435 | const std::string &iface_id = params[0]; |
| 436 | |
| 437 | // Get single eth interface data, and call the below callback for JSON |
| 438 | // preparation |
| 439 | ethernet_provider.getEthernetIfaceData( |
| 440 | iface_id, [&, iface_id](const bool &success, |
| 441 | const EthernetInterfaceData ð_data, |
| 442 | const std::vector<IPv4AddressData> &ipv4_data) { |
| 443 | if (success) { |
| 444 | // Copy JSON object to avoid race condition |
| 445 | nlohmann::json json_response(Node::json); |
| 446 | |
| 447 | // Fill out obvious data... |
| 448 | json_response["Id"] = iface_id; |
| 449 | json_response["@odata.id"] = |
| 450 | "/redfish/v1/Managers/openbmc/EthernetInterfaces/" + iface_id; |
| 451 | |
| 452 | // ... then the one from DBus, regarding eth iface... |
| 453 | if (eth_data.speed != nullptr) |
| 454 | json_response["SpeedMbps"] = *eth_data.speed; |
| 455 | |
| 456 | if (eth_data.mac_address != nullptr) |
| 457 | json_response["MACAddress"] = *eth_data.mac_address; |
| 458 | |
| 459 | if (eth_data.hostname != nullptr) |
| 460 | json_response["HostName"] = *eth_data.hostname; |
| 461 | |
| 462 | // ... at last, check if there are IPv4 data and prepare appropriate |
| 463 | // collection |
| 464 | if (ipv4_data.size() > 0) { |
| 465 | nlohmann::json ipv4_array = nlohmann::json::array(); |
| 466 | for (auto &ipv4_config : ipv4_data) { |
| 467 | nlohmann::json json_ipv4; |
| 468 | if (ipv4_config.address != nullptr) { |
| 469 | json_ipv4["Address"] = *ipv4_config.address; |
| 470 | if (ipv4_config.gateway != nullptr) |
| 471 | json_ipv4["Gateway"] = *ipv4_config.gateway; |
| 472 | |
| 473 | json_ipv4["AddressOrigin"] = ipv4_config.origin; |
| 474 | json_ipv4["SubnetMask"] = ipv4_config.netmask; |
| 475 | |
| 476 | ipv4_array.push_back(json_ipv4); |
| 477 | } |
| 478 | } |
| 479 | json_response["IPv4Addresses"] = ipv4_array; |
| 480 | } |
| 481 | res.json_value = std::move(json_response); |
| 482 | } else { |
| 483 | // ... otherwise return error |
| 484 | // TODO(Pawel)consider distinguish between non existing object, and |
| 485 | // other errors |
| 486 | res.code = static_cast<int>(HttpRespCode::NOT_FOUND); |
| 487 | } |
| 488 | res.end(); |
| 489 | }); |
| 490 | } |
| 491 | |
| 492 | // Ethernet Provider object |
| 493 | // TODO(Pawel) consider move it to singleton |
| 494 | OnDemandEthernetProvider ethernet_provider; |
| 495 | }; |
| 496 | |
| 497 | } // namespace redfish |