blob: 68e6ebba88fec8b8acee005188675cc8c890fa90 [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 */
27using PropertiesMapType =
28 boost::container::flat_map<std::string, dbus::dbus_variant>;
29
30using GetManagedObjectsType = boost::container::flat_map<
31 dbus::object_path,
32 boost::container::flat_map<std::string, PropertiesMapType>>;
33
34using 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 */
40struct 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 */
53struct 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 */
70class 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 &ethiface_id,
116 const GetManagedObjectsType &dbus_data,
117 EthernetInterfaceData &eth_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 &ethiface_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 &ethiface_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 */
325class 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> &params) 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 */
393class 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> &params) 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 &eth_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