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