blob: ea465fbda62db296a7ae90b3ba8ccf570b486336 [file] [log] [blame]
Rapkiewicz, Pawel9391bb92018-03-20 03:12:18 +01001/*
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
21namespace 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 */
Ed Tanousaa2e59c2018-04-12 12:17:20 -070027using PropertiesMapType = boost::container::flat_map<
28 std::string,
29 sdbusplus::message::variant<std::string, bool, uint8_t, int16_t, uint16_t,
30 int32_t, uint32_t, int64_t, uint64_t, double>>;
Rapkiewicz, Pawel9391bb92018-03-20 03:12:18 +010031
32using GetManagedObjectsType = boost::container::flat_map<
Ed Tanousaa2e59c2018-04-12 12:17:20 -070033 sdbusplus::message::object_path,
34 boost::container::flat_map<
35 std::string,
36 boost::container::flat_map<
37 std::string, sdbusplus::message::variant<
38 std::string, bool, uint8_t, int16_t, uint16_t,
39 int32_t, uint32_t, int64_t, uint64_t, double>>>>;
Rapkiewicz, Pawel9391bb92018-03-20 03:12:18 +010040
41/**
42 * Structure for keeping IPv4 data required by Redfish
43 * TODO(Pawel) consider change everything to ptr, or to non-ptr values.
44 */
45struct IPv4AddressData {
46 const std::string *address;
47 const std::string *domain;
48 const std::string *gateway;
49 std::string netmask;
50 std::string origin;
51 bool global;
52};
53
54/**
55 * Structure for keeping basic single Ethernet Interface information
56 * available from DBus
57 */
58struct EthernetInterfaceData {
59 const unsigned int *speed;
60 const bool *auto_neg;
61 const std::string *hostname;
62 const std::string *default_gateway;
63 const std::string *mac_address;
Kowalski, Kamilc7070ac2018-03-27 15:01:02 +020064 const unsigned int *vlan_id;
Rapkiewicz, Pawel9391bb92018-03-20 03:12:18 +010065};
66
67/**
68 * OnDemandEthernetProvider
Gunnar Mills274fad52018-06-13 15:45:36 -050069 * Ethernet provider class that retrieves data directly from dbus, before
70 * setting it into JSON output. This does not cache any data.
Rapkiewicz, Pawel9391bb92018-03-20 03:12:18 +010071 *
72 * TODO(Pawel)
73 * This perhaps shall be different file, which has to be chosen on compile time
74 * depending on OEM needs
75 */
76class OnDemandEthernetProvider {
77 private:
78 // Consts that may have influence on EthernetProvider performance/memory usage
79 const size_t MAX_IPV4_ADDRESSES_PER_INTERFACE = 10;
80
81 // Helper function that allows to extract GetAllPropertiesType from
82 // GetManagedObjectsType, based on object path, and interface name
83 const PropertiesMapType *extractInterfaceProperties(
Ed Tanousaa2e59c2018-04-12 12:17:20 -070084 const sdbusplus::message::object_path &objpath,
85 const std::string &interface, const GetManagedObjectsType &dbus_data) {
Rapkiewicz, Pawel9391bb92018-03-20 03:12:18 +010086 const auto &dbus_obj = dbus_data.find(objpath);
87 if (dbus_obj != dbus_data.end()) {
88 const auto &iface = dbus_obj->second.find(interface);
89 if (iface != dbus_obj->second.end()) {
90 return &iface->second;
91 }
92 }
93 return nullptr;
94 }
95
96 // Helper Wrapper that does inline object_path conversion from string
Ed Tanousaa2e59c2018-04-12 12:17:20 -070097 // into sdbusplus::message::object_path type
Rapkiewicz, Pawel9391bb92018-03-20 03:12:18 +010098 inline const PropertiesMapType *extractInterfaceProperties(
99 const std::string &objpath, const std::string &interface,
100 const GetManagedObjectsType &dbus_data) {
Ed Tanousaa2e59c2018-04-12 12:17:20 -0700101 const auto &dbus_obj = sdbusplus::message::object_path{objpath};
Rapkiewicz, Pawel9391bb92018-03-20 03:12:18 +0100102 return extractInterfaceProperties(dbus_obj, interface, dbus_data);
103 }
104
105 // Helper function that allows to get pointer to the property from
106 // GetAllPropertiesType native, or extracted by GetAllPropertiesType
107 template <typename T>
Ed Tanousaa2e59c2018-04-12 12:17:20 -0700108 inline T const *const extractProperty(const PropertiesMapType &properties,
109 const std::string &name) {
Rapkiewicz, Pawel9391bb92018-03-20 03:12:18 +0100110 const auto &property = properties.find(name);
111 if (property != properties.end()) {
Ed Tanousaa2e59c2018-04-12 12:17:20 -0700112 return mapbox::get_ptr<const T>(property->second);
Rapkiewicz, Pawel9391bb92018-03-20 03:12:18 +0100113 }
114 return nullptr;
115 }
116 // TODO(Pawel) Consider to move the above functions to dbus
117 // generic_interfaces.hpp
118
119 // Helper function that extracts data from several dbus objects and several
120 // interfaces required by single ethernet interface instance
121 void extractEthernetInterfaceData(const std::string &ethiface_id,
122 const GetManagedObjectsType &dbus_data,
123 EthernetInterfaceData &eth_data) {
124 // Extract data that contains MAC Address
125 const PropertiesMapType *mac_properties = extractInterfaceProperties(
126 "/xyz/openbmc_project/network/" + ethiface_id,
127 "xyz.openbmc_project.Network.MACAddress", dbus_data);
128
129 if (mac_properties != nullptr) {
130 eth_data.mac_address =
131 extractProperty<std::string>(*mac_properties, "MACAddress");
132 }
133
Kowalski, Kamilc7070ac2018-03-27 15:01:02 +0200134 const PropertiesMapType *vlan_properties = extractInterfaceProperties(
135 "/xyz/openbmc_project/network/" + ethiface_id,
136 "xyz.openbmc_project.Network.VLAN", dbus_data);
137
138 if (vlan_properties != nullptr) {
139 eth_data.vlan_id = extractProperty<unsigned int>(*vlan_properties, "Id");
140 }
141
Rapkiewicz, Pawel9391bb92018-03-20 03:12:18 +0100142 // Extract data that contains link information (auto negotiation and speed)
143 const PropertiesMapType *eth_properties = extractInterfaceProperties(
144 "/xyz/openbmc_project/network/" + ethiface_id,
145 "xyz.openbmc_project.Network.EthernetInterface", dbus_data);
146
147 if (eth_properties != nullptr) {
148 eth_data.auto_neg = extractProperty<bool>(*eth_properties, "AutoNeg");
149 eth_data.speed = extractProperty<unsigned int>(*eth_properties, "Speed");
150 }
151
152 // Extract data that contains network config (HostName and DefaultGW)
153 const PropertiesMapType *config_properties = extractInterfaceProperties(
154 "/xyz/openbmc_project/network/config",
155 "xyz.openbmc_project.Network.SystemConfiguration", dbus_data);
156
157 if (config_properties != nullptr) {
158 eth_data.hostname =
159 extractProperty<std::string>(*config_properties, "HostName");
160 eth_data.default_gateway =
161 extractProperty<std::string>(*config_properties, "DefaultGateway");
162 }
163 }
164
165 // Helper function that changes bits netmask notation (i.e. /24)
166 // into full dot notation
167 inline std::string getNetmask(unsigned int bits) {
168 uint32_t value = 0xffffffff << (32 - bits);
169 std::string netmask = std::to_string((value >> 24) & 0xff) + "." +
170 std::to_string((value >> 16) & 0xff) + "." +
171 std::to_string((value >> 8) & 0xff) + "." +
172 std::to_string(value & 0xff);
173 return netmask;
174 }
175
176 // Helper function that extracts data for single ethernet ipv4 address
177 void extractIPv4Data(const std::string &ethiface_id,
178 const GetManagedObjectsType &dbus_data,
179 std::vector<IPv4AddressData> &ipv4_config) {
180 // Since there might be several IPv4 configurations aligned with
181 // single ethernet interface, loop over all of them
182 for (auto &objpath : dbus_data) {
Gunnar Mills274fad52018-06-13 15:45:36 -0500183 // Check if proper patter for object path appears
Rapkiewicz, Pawel9391bb92018-03-20 03:12:18 +0100184 if (boost::starts_with(
Ed Tanousaa2e59c2018-04-12 12:17:20 -0700185 static_cast<std::string>(objpath.first),
Rapkiewicz, Pawel9391bb92018-03-20 03:12:18 +0100186 "/xyz/openbmc_project/network/" + ethiface_id + "/ipv4/")) {
187 // and get approrpiate interface
188 const auto &interface =
189 objpath.second.find("xyz.openbmc_project.Network.IP");
190 if (interface != objpath.second.end()) {
191 // Make a properties 'shortcut', to make everything more readable
192 const PropertiesMapType &properties = interface->second;
193 // Instance IPv4AddressData structure, and set as appropriate
194 IPv4AddressData ipv4_address;
195 // IPv4 address
196 ipv4_address.address =
197 extractProperty<std::string>(properties, "Address");
198 // IPv4 gateway
199 ipv4_address.gateway =
200 extractProperty<std::string>(properties, "Gateway");
201
202 // Origin is kind of DBus object so fetch pointer...
203 const std::string *origin =
204 extractProperty<std::string>(properties, "Origin");
205 if (origin != nullptr) {
206 // ... and get everything after last dot
207 int last = origin->rfind(".");
208 if (last != std::string::npos) {
209 ipv4_address.origin = origin->substr(last + 1);
210 }
211 }
212
213 // Netmask is presented as PrefixLength
214 const auto *mask =
215 extractProperty<uint8_t>(properties, "PrefixLength");
216 if (mask != nullptr) {
217 // convert it to the string
218 ipv4_address.netmask = getNetmask(*mask);
219 }
220
221 // Attach IPv4 only if address is present
222 if (ipv4_address.address != nullptr) {
Gunnar Mills274fad52018-06-13 15:45:36 -0500223 // Check if given address is local, or global
Rapkiewicz, Pawel9391bb92018-03-20 03:12:18 +0100224 if (boost::starts_with(*ipv4_address.address, "169.254")) {
225 ipv4_address.global = false;
226 } else {
227 ipv4_address.global = true;
228 }
229 ipv4_config.emplace_back(std::move(ipv4_address));
230 }
231 }
232 }
233 }
234 }
235
236 public:
237 /**
238 * Function that retrieves all properties for given Ethernet Interface Object
239 * from EntityManager Network Manager
240 * @param ethiface_id a eth interface id to query on DBus
241 * @param callback a function that shall be called to convert Dbus output into
242 * JSON
243 */
244 template <typename CallbackFunc>
245 void getEthernetIfaceData(const std::string &ethiface_id,
246 CallbackFunc &&callback) {
247 crow::connections::system_bus->async_method_call(
248 [
249 this, ethiface_id{std::move(ethiface_id)},
250 callback{std::move(callback)}
251 ](const boost::system::error_code error_code,
Ed Tanousaa2e59c2018-04-12 12:17:20 -0700252 GetManagedObjectsType &resp) {
Rapkiewicz, Pawel9391bb92018-03-20 03:12:18 +0100253
Kowalski, Kamilc7070ac2018-03-27 15:01:02 +0200254 EthernetInterfaceData eth_data{};
Rapkiewicz, Pawel9391bb92018-03-20 03:12:18 +0100255 std::vector<IPv4AddressData> ipv4_data;
256 ipv4_data.reserve(MAX_IPV4_ADDRESSES_PER_INTERFACE);
257
258 if (error_code) {
259 // Something wrong on DBus, the error_code is not important at this
260 // moment, just return success=false, and empty output. Since size
261 // of vector may vary depending on information from Network Manager,
262 // and empty output could not be treated same way as error.
263 callback(false, eth_data, ipv4_data);
264 return;
265 }
266
267 extractEthernetInterfaceData(ethiface_id, resp, eth_data);
268 extractIPv4Data(ethiface_id, resp, ipv4_data);
269
270 // Fix global GW
271 for (IPv4AddressData &ipv4 : ipv4_data) {
272 if ((ipv4.global) &&
273 ((ipv4.gateway == nullptr) || (*ipv4.gateway == "0.0.0.0"))) {
274 ipv4.gateway = eth_data.default_gateway;
275 }
276 }
277
Gunnar Mills274fad52018-06-13 15:45:36 -0500278 // Finally make a callback with useful data
Rapkiewicz, Pawel9391bb92018-03-20 03:12:18 +0100279 callback(true, eth_data, ipv4_data);
280 },
Ed Tanousaa2e59c2018-04-12 12:17:20 -0700281 "xyz.openbmc_project.Network", "/xyz/openbmc_project/network",
282 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
Rapkiewicz, Pawel9391bb92018-03-20 03:12:18 +0100283 };
284
285 /**
286 * Function that retrieves all Ethernet Interfaces available through Network
287 * Manager
288 * @param callback a function that shall be called to convert Dbus output into
289 * JSON.
290 */
291 template <typename CallbackFunc>
292 void getEthernetIfaceList(CallbackFunc &&callback) {
293 crow::connections::system_bus->async_method_call(
294 [ this, callback{std::move(callback)} ](
295 const boost::system::error_code error_code,
Ed Tanousaa2e59c2018-04-12 12:17:20 -0700296 GetManagedObjectsType &resp) {
Rapkiewicz, Pawel9391bb92018-03-20 03:12:18 +0100297 // Callback requires vector<string> to retrieve all available ethernet
298 // interfaces
299 std::vector<std::string> iface_list;
300 iface_list.reserve(resp.size());
301 if (error_code) {
302 // Something wrong on DBus, the error_code is not important at this
303 // moment, just return success=false, and empty output. Since size
304 // of vector may vary depending on information from Network Manager,
305 // and empty output could not be treated same way as error.
306 callback(false, iface_list);
307 return;
308 }
309
310 // Iterate over all retrieved ObjectPaths.
311 for (auto &objpath : resp) {
312 // And all interfaces available for certain ObjectPath.
313 for (auto &interface : objpath.second) {
314 // If interface is xyz.openbmc_project.Network.EthernetInterface,
315 // this is what we're looking for.
316 if (interface.first ==
317 "xyz.openbmc_project.Network.EthernetInterface") {
Ed Tanousaa2e59c2018-04-12 12:17:20 -0700318 // Cut out everyting until last "/", ...
319 const std::string iface_id =
320 static_cast<std::string>(objpath.first);
Rapkiewicz, Pawel9391bb92018-03-20 03:12:18 +0100321 std::size_t last_pos = iface_id.rfind("/");
322 if (last_pos != std::string::npos) {
323 // and put it into output vector.
324 iface_list.emplace_back(iface_id.substr(last_pos + 1));
325 }
326 }
327 }
328 }
Gunnar Mills274fad52018-06-13 15:45:36 -0500329 // Finally make a callback with useful data
Rapkiewicz, Pawel9391bb92018-03-20 03:12:18 +0100330 callback(true, iface_list);
331 },
Ed Tanousaa2e59c2018-04-12 12:17:20 -0700332 "xyz.openbmc_project.Network", "/xyz/openbmc_project/network",
333 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
Rapkiewicz, Pawel9391bb92018-03-20 03:12:18 +0100334 };
335};
336
337/**
338 * EthernetCollection derived class for delivering Ethernet Collection Schema
339 */
340class EthernetCollection : public Node {
341 public:
342 template <typename CrowApp>
343 // TODO(Pawel) Remove line from below, where we assume that there is only one
344 // manager called openbmc This shall be generic, but requires to update
345 // GetSubroutes method
346 EthernetCollection(CrowApp &app)
347 : Node(app, "/redfish/v1/Managers/openbmc/EthernetInterfaces/") {
348 Node::json["@odata.type"] =
349 "#EthernetInterfaceCollection.EthernetInterfaceCollection";
350 Node::json["@odata.context"] =
351 "/redfish/v1/"
352 "$metadata#EthernetInterfaceCollection.EthernetInterfaceCollection";
353 Node::json["@odata.id"] = "/redfish/v1/Managers/openbmc/EthernetInterfaces";
354 Node::json["Name"] = "Ethernet Network Interface Collection";
355 Node::json["Description"] =
356 "Collection of EthernetInterfaces for this Manager";
357
358 entityPrivileges = {{crow::HTTPMethod::GET, {{"Login"}}},
359 {crow::HTTPMethod::HEAD, {{"Login"}}},
360 {crow::HTTPMethod::PATCH, {{"ConfigureComponents"}}},
361 {crow::HTTPMethod::PUT, {{"ConfigureComponents"}}},
362 {crow::HTTPMethod::DELETE, {{"ConfigureComponents"}}},
363 {crow::HTTPMethod::POST, {{"ConfigureComponents"}}}};
364 }
365
366 private:
367 /**
368 * Functions triggers appropriate requests on DBus
369 */
370 void doGet(crow::response &res, const crow::request &req,
371 const std::vector<std::string> &params) override {
372 // TODO(Pawel) this shall be parametrized call to get EthernetInterfaces for
373 // any Manager, not only hardcoded 'openbmc'.
374 std::string manager_id = "openbmc";
375
376 // Get eth interface list, and call the below callback for JSON preparation
377 ethernet_provider.getEthernetIfaceList(
378 [&, manager_id{std::move(manager_id)} ](
379 const bool &success, const std::vector<std::string> &iface_list) {
380 if (success) {
381 nlohmann::json iface_array = nlohmann::json::array();
382 for (const std::string &iface_item : iface_list) {
383 iface_array.push_back(
384 {{"@odata.id", "/redfish/v1/Managers/" + manager_id +
385 "/EthernetInterfaces/" + iface_item}});
386 }
387 Node::json["Members"] = iface_array;
388 Node::json["Members@odata.count"] = iface_array.size();
389 Node::json["@odata.id"] =
390 "/redfish/v1/Managers/" + manager_id + "/EthernetInterfaces";
391 res.json_value = Node::json;
392 } else {
393 // No success, best what we can do is return INTERNALL ERROR
394 res.code = static_cast<int>(HttpRespCode::INTERNAL_ERROR);
395 }
396 res.end();
397 });
398 }
399
400 // Ethernet Provider object
401 // TODO(Pawel) consider move it to singleton
402 OnDemandEthernetProvider ethernet_provider;
403};
404
405/**
406 * EthernetInterface derived class for delivering Ethernet Schema
407 */
408class EthernetInterface : public Node {
409 public:
410 /*
411 * Default Constructor
412 */
413 template <typename CrowApp>
414 // TODO(Pawel) Remove line from below, where we assume that there is only one
415 // manager called openbmc This shall be generic, but requires to update
416 // GetSubroutes method
417 EthernetInterface(CrowApp &app)
418 : Node(app, "/redfish/v1/Managers/openbmc/EthernetInterfaces/<str>/",
419 std::string()) {
420 Node::json["@odata.type"] = "#EthernetInterface.v1_2_0.EthernetInterface";
421 Node::json["@odata.context"] =
422 "/redfish/v1/$metadata#EthernetInterface.EthernetInterface";
423 Node::json["Name"] = "Manager Ethernet Interface";
424 Node::json["Description"] = "Management Network Interface";
425
426 entityPrivileges = {{crow::HTTPMethod::GET, {{"Login"}}},
427 {crow::HTTPMethod::HEAD, {{"Login"}}},
428 {crow::HTTPMethod::PATCH, {{"ConfigureComponents"}}},
429 {crow::HTTPMethod::PUT, {{"ConfigureComponents"}}},
430 {crow::HTTPMethod::DELETE, {{"ConfigureComponents"}}},
431 {crow::HTTPMethod::POST, {{"ConfigureComponents"}}}};
432 }
433
434 private:
435 /**
436 * Functions triggers appropriate requests on DBus
437 */
438 void doGet(crow::response &res, const crow::request &req,
439 const std::vector<std::string> &params) override {
440 // TODO(Pawel) this shall be parametrized call (two params) to get
441 // EthernetInterfaces for any Manager, not only hardcoded 'openbmc'.
442 // Check if there is required param, truly entering this shall be
443 // impossible.
444 if (params.size() != 1) {
445 res.code = static_cast<int>(HttpRespCode::INTERNAL_ERROR);
446 res.end();
447 return;
448 }
449
450 const std::string &iface_id = params[0];
451
452 // Get single eth interface data, and call the below callback for JSON
453 // preparation
454 ethernet_provider.getEthernetIfaceData(
455 iface_id, [&, iface_id](const bool &success,
456 const EthernetInterfaceData &eth_data,
457 const std::vector<IPv4AddressData> &ipv4_data) {
458 if (success) {
459 // Copy JSON object to avoid race condition
460 nlohmann::json json_response(Node::json);
461
462 // Fill out obvious data...
463 json_response["Id"] = iface_id;
464 json_response["@odata.id"] =
465 "/redfish/v1/Managers/openbmc/EthernetInterfaces/" + iface_id;
466
467 // ... then the one from DBus, regarding eth iface...
468 if (eth_data.speed != nullptr)
469 json_response["SpeedMbps"] = *eth_data.speed;
470
471 if (eth_data.mac_address != nullptr)
472 json_response["MACAddress"] = *eth_data.mac_address;
473
474 if (eth_data.hostname != nullptr)
475 json_response["HostName"] = *eth_data.hostname;
476
Kowalski, Kamilc7070ac2018-03-27 15:01:02 +0200477 if (eth_data.vlan_id != nullptr) {
478 json_response["VLAN"]["VLANEnable"] = true;
479 json_response["VLAN"]["VLANId"] = *eth_data.vlan_id;
480 }
481
Rapkiewicz, Pawel9391bb92018-03-20 03:12:18 +0100482 // ... at last, check if there are IPv4 data and prepare appropriate
483 // collection
484 if (ipv4_data.size() > 0) {
485 nlohmann::json ipv4_array = nlohmann::json::array();
486 for (auto &ipv4_config : ipv4_data) {
487 nlohmann::json json_ipv4;
488 if (ipv4_config.address != nullptr) {
489 json_ipv4["Address"] = *ipv4_config.address;
490 if (ipv4_config.gateway != nullptr)
491 json_ipv4["Gateway"] = *ipv4_config.gateway;
492
493 json_ipv4["AddressOrigin"] = ipv4_config.origin;
494 json_ipv4["SubnetMask"] = ipv4_config.netmask;
495
496 ipv4_array.push_back(json_ipv4);
497 }
498 }
499 json_response["IPv4Addresses"] = ipv4_array;
500 }
501 res.json_value = std::move(json_response);
502 } else {
503 // ... otherwise return error
504 // TODO(Pawel)consider distinguish between non existing object, and
505 // other errors
506 res.code = static_cast<int>(HttpRespCode::NOT_FOUND);
507 }
508 res.end();
509 });
510 }
511
512 // Ethernet Provider object
513 // TODO(Pawel) consider move it to singleton
514 OnDemandEthernetProvider ethernet_provider;
515};
516
517} // namespace redfish