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 <boost/container/flat_map.hpp> 19 #include <boost/container/flat_set.hpp> 20 #include <boost/optional.hpp> 21 #include <dbus_singleton.hpp> 22 #include <error_messages.hpp> 23 #include <node.hpp> 24 #include <utils/json_utils.hpp> 25 26 namespace redfish 27 { 28 29 /** 30 * DBus types primitives for several generic DBus interfaces 31 * TODO(Pawel) consider move this to separate file into boost::dbus 32 */ 33 using PropertiesMapType = boost::container::flat_map< 34 std::string, 35 sdbusplus::message::variant<std::string, bool, uint8_t, int16_t, uint16_t, 36 int32_t, uint32_t, int64_t, uint64_t, double>>; 37 38 using GetManagedObjects = std::vector<std::pair< 39 sdbusplus::message::object_path, 40 std::vector<std::pair< 41 std::string, 42 boost::container::flat_map< 43 std::string, sdbusplus::message::variant< 44 std::string, bool, uint8_t, int16_t, uint16_t, 45 int32_t, uint32_t, int64_t, uint64_t, double>>>>>>; 46 47 enum class LinkType 48 { 49 Local, 50 Global 51 }; 52 53 /** 54 * Structure for keeping IPv4 data required by Redfish 55 */ 56 struct IPv4AddressData 57 { 58 std::string id; 59 std::string address; 60 std::string domain; 61 std::string gateway; 62 std::string netmask; 63 std::string origin; 64 LinkType linktype; 65 66 bool operator<(const IPv4AddressData &obj) const 67 { 68 return id < obj.id; 69 } 70 }; 71 72 /** 73 * Structure for keeping basic single Ethernet Interface information 74 * available from DBus 75 */ 76 struct EthernetInterfaceData 77 { 78 uint32_t speed; 79 bool auto_neg; 80 std::string hostname; 81 std::string default_gateway; 82 std::string mac_address; 83 boost::optional<uint32_t> vlan_id; 84 }; 85 86 // Helper function that changes bits netmask notation (i.e. /24) 87 // into full dot notation 88 inline std::string getNetmask(unsigned int bits) 89 { 90 uint32_t value = 0xffffffff << (32 - bits); 91 std::string netmask = std::to_string((value >> 24) & 0xff) + "." + 92 std::to_string((value >> 16) & 0xff) + "." + 93 std::to_string((value >> 8) & 0xff) + "." + 94 std::to_string(value & 0xff); 95 return netmask; 96 } 97 98 inline std::string 99 translateAddressOriginDbusToRedfish(const std::string &inputOrigin, 100 bool isIPv4) 101 { 102 if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.Static") 103 { 104 return "Static"; 105 } 106 if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.LinkLocal") 107 { 108 if (isIPv4) 109 { 110 return "IPv4LinkLocal"; 111 } 112 else 113 { 114 return "LinkLocal"; 115 } 116 } 117 if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.DHCP") 118 { 119 if (isIPv4) 120 { 121 return "DHCP"; 122 } 123 else 124 { 125 return "DHCPv6"; 126 } 127 } 128 if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.SLAAC") 129 { 130 return "SLAAC"; 131 } 132 return ""; 133 } 134 135 inline std::string 136 translateAddressOriginRedfishToDbus(const std::string &inputOrigin) 137 { 138 if (inputOrigin == "Static") 139 { 140 return "xyz.openbmc_project.Network.IP.AddressOrigin.Static"; 141 } 142 if (inputOrigin == "DHCP" || inputOrigin == "DHCPv6") 143 { 144 return "xyz.openbmc_project.Network.IP.AddressOrigin.DHCP"; 145 } 146 if (inputOrigin == "IPv4LinkLocal" || inputOrigin == "LinkLocal") 147 { 148 return "xyz.openbmc_project.Network.IP.AddressOrigin.LinkLocal"; 149 } 150 if (inputOrigin == "SLAAC") 151 { 152 return "xyz.openbmc_project.Network.IP.AddressOrigin.SLAAC"; 153 } 154 return ""; 155 } 156 157 inline void extractEthernetInterfaceData(const std::string ðiface_id, 158 const GetManagedObjects &dbus_data, 159 EthernetInterfaceData ðData) 160 { 161 for (const auto &objpath : dbus_data) 162 { 163 if (objpath.first == "/xyz/openbmc_project/network/" + ethiface_id) 164 { 165 for (const auto &ifacePair : objpath.second) 166 { 167 if (ifacePair.first == "xyz.openbmc_project.Network.MACAddress") 168 { 169 for (const auto &propertyPair : ifacePair.second) 170 { 171 if (propertyPair.first == "MACAddress") 172 { 173 const std::string *mac = 174 mapbox::getPtr<const std::string>( 175 propertyPair.second); 176 if (mac != nullptr) 177 { 178 ethData.mac_address = *mac; 179 } 180 } 181 } 182 } 183 else if (ifacePair.first == "xyz.openbmc_project.Network.VLAN") 184 { 185 for (const auto &propertyPair : ifacePair.second) 186 { 187 if (propertyPair.first == "Id") 188 { 189 const uint32_t *id = mapbox::getPtr<const uint32_t>( 190 propertyPair.second); 191 if (id != nullptr) 192 { 193 ethData.vlan_id = *id; 194 } 195 } 196 } 197 } 198 else if (ifacePair.first == 199 "xyz.openbmc_project.Network.EthernetInterface") 200 { 201 for (const auto &propertyPair : ifacePair.second) 202 { 203 if (propertyPair.first == "AutoNeg") 204 { 205 const bool *auto_neg = 206 mapbox::getPtr<const bool>(propertyPair.second); 207 if (auto_neg != nullptr) 208 { 209 ethData.auto_neg = *auto_neg; 210 } 211 } 212 else if (propertyPair.first == "Speed") 213 { 214 const uint32_t *speed = 215 mapbox::getPtr<const uint32_t>( 216 propertyPair.second); 217 if (speed != nullptr) 218 { 219 ethData.speed = *speed; 220 } 221 } 222 } 223 } 224 else if (ifacePair.first == 225 "xyz.openbmc_project.Network.SystemConfiguration") 226 { 227 for (const auto &propertyPair : ifacePair.second) 228 { 229 if (propertyPair.first == "HostName") 230 { 231 const std::string *hostname = 232 mapbox::getPtr<const std::string>( 233 propertyPair.second); 234 if (hostname != nullptr) 235 { 236 ethData.hostname = *hostname; 237 } 238 } 239 else if (propertyPair.first == "DefaultGateway") 240 { 241 const std::string *defaultGateway = 242 mapbox::getPtr<const std::string>( 243 propertyPair.second); 244 if (defaultGateway != nullptr) 245 { 246 ethData.default_gateway = *defaultGateway; 247 } 248 } 249 } 250 } 251 } 252 } 253 } 254 } 255 256 // Helper function that extracts data for single ethernet ipv4 address 257 inline void 258 extractIPData(const std::string ðiface_id, 259 const GetManagedObjects &dbus_data, 260 boost::container::flat_set<IPv4AddressData> &ipv4_config) 261 { 262 const std::string ipv4PathStart = 263 "/xyz/openbmc_project/network/" + ethiface_id + "/ipv4/"; 264 265 // Since there might be several IPv4 configurations aligned with 266 // single ethernet interface, loop over all of them 267 for (const auto &objpath : dbus_data) 268 { 269 // Check if proper pattern for object path appears 270 if (boost::starts_with(objpath.first.str, ipv4PathStart)) 271 { 272 for (auto &interface : objpath.second) 273 { 274 if (interface.first == "xyz.openbmc_project.Network.IP") 275 { 276 // Instance IPv4AddressData structure, and set as 277 // appropriate 278 std::pair< 279 boost::container::flat_set<IPv4AddressData>::iterator, 280 bool> 281 it = ipv4_config.insert( 282 {objpath.first.str.substr(ipv4PathStart.size())}); 283 IPv4AddressData &ipv4_address = *it.first; 284 for (auto &property : interface.second) 285 { 286 if (property.first == "Address") 287 { 288 const std::string *address = 289 mapbox::getPtr<const std::string>( 290 property.second); 291 if (address != nullptr) 292 { 293 ipv4_address.address = *address; 294 } 295 } 296 else if (property.first == "Gateway") 297 { 298 const std::string *gateway = 299 mapbox::getPtr<const std::string>( 300 property.second); 301 if (gateway != nullptr) 302 { 303 ipv4_address.gateway = *gateway; 304 } 305 } 306 else if (property.first == "Origin") 307 { 308 const std::string *origin = 309 mapbox::getPtr<const std::string>( 310 property.second); 311 if (origin != nullptr) 312 { 313 ipv4_address.origin = 314 translateAddressOriginDbusToRedfish(*origin, 315 true); 316 } 317 } 318 else if (property.first == "PrefixLength") 319 { 320 const uint8_t *mask = 321 mapbox::getPtr<uint8_t>(property.second); 322 if (mask != nullptr) 323 { 324 // convert it to the string 325 ipv4_address.netmask = getNetmask(*mask); 326 } 327 } 328 else 329 { 330 BMCWEB_LOG_ERROR 331 << "Got extra property: " << property.first 332 << " on the " << objpath.first.str << " object"; 333 } 334 } 335 // Check if given address is local, or global 336 ipv4_address.linktype = 337 boost::starts_with(ipv4_address.address, "169.254.") 338 ? LinkType::Global 339 : LinkType::Local; 340 } 341 } 342 } 343 } 344 } 345 346 /** 347 * @brief Sets given Id on the given VLAN interface through D-Bus 348 * 349 * @param[in] ifaceId Id of VLAN interface that should be modified 350 * @param[in] inputVlanId New ID of the VLAN 351 * @param[in] callback Function that will be called after the operation 352 * 353 * @return None. 354 */ 355 template <typename CallbackFunc> 356 void changeVlanId(const std::string &ifaceId, const uint32_t &inputVlanId, 357 CallbackFunc &&callback) 358 { 359 crow::connections::systemBus->async_method_call( 360 callback, "xyz.openbmc_project.Network", 361 std::string("/xyz/openbmc_project/network/") + ifaceId, 362 "org.freedesktop.DBus.Properties", "Set", 363 "xyz.openbmc_project.Network.VLAN", "Id", 364 sdbusplus::message::variant<uint32_t>(inputVlanId)); 365 } 366 367 /** 368 * @brief Helper function that verifies IP address to check if it is in 369 * proper format. If bits pointer is provided, also calculates active 370 * bit count for Subnet Mask. 371 * 372 * @param[in] ip IP that will be verified 373 * @param[out] bits Calculated mask in bits notation 374 * 375 * @return true in case of success, false otherwise 376 */ 377 inline bool ipv4VerifyIpAndGetBitcount(const std::string &ip, 378 uint8_t *bits = nullptr) 379 { 380 std::vector<std::string> bytesInMask; 381 382 boost::split(bytesInMask, ip, boost::is_any_of(".")); 383 384 static const constexpr int ipV4AddressSectionsCount = 4; 385 if (bytesInMask.size() != ipV4AddressSectionsCount) 386 { 387 return false; 388 } 389 390 if (bits != nullptr) 391 { 392 *bits = 0; 393 } 394 395 char *endPtr; 396 long previousValue = 255; 397 bool firstZeroInByteHit; 398 for (const std::string &byte : bytesInMask) 399 { 400 if (byte.empty()) 401 { 402 return false; 403 } 404 405 // Use strtol instead of stroi to avoid exceptions 406 long value = std::strtol(byte.c_str(), &endPtr, 10); 407 408 // endPtr should point to the end of the string, otherwise given string 409 // is not 100% number 410 if (*endPtr != '\0') 411 { 412 return false; 413 } 414 415 // Value should be contained in byte 416 if (value < 0 || value > 255) 417 { 418 return false; 419 } 420 421 if (bits != nullptr) 422 { 423 // Mask has to be continuous between bytes 424 if (previousValue != 255 && value != 0) 425 { 426 return false; 427 } 428 429 // Mask has to be continuous inside bytes 430 firstZeroInByteHit = false; 431 432 // Count bits 433 for (int bitIdx = 7; bitIdx >= 0; bitIdx--) 434 { 435 if (value & (1 << bitIdx)) 436 { 437 if (firstZeroInByteHit) 438 { 439 // Continuity not preserved 440 return false; 441 } 442 else 443 { 444 (*bits)++; 445 } 446 } 447 else 448 { 449 firstZeroInByteHit = true; 450 } 451 } 452 } 453 454 previousValue = value; 455 } 456 457 return true; 458 } 459 460 /** 461 * @brief Changes IPv4 address type property (Address, Gateway) 462 * 463 * @param[in] ifaceId Id of interface whose IP should be modified 464 * @param[in] ipIdx Index of IP in input array that should be modified 465 * @param[in] ipHash DBus Hash id of modified IP 466 * @param[in] name Name of field in JSON representation 467 * @param[in] newValue New value that should be written 468 * @param[io] asyncResp Response object that will be returned to client 469 * 470 * @return true if give IP is valid and has been sent do D-Bus, false 471 * otherwise 472 */ 473 inline void changeIPv4AddressProperty( 474 const std::string &ifaceId, int ipIdx, const std::string &ipHash, 475 const std::string &name, const std::string &newValue, 476 const std::shared_ptr<AsyncResp> asyncResp) 477 { 478 auto callback = [asyncResp, ipIdx, name{std::string(name)}, 479 newValue{std::move(newValue)}]( 480 const boost::system::error_code ec) { 481 if (ec) 482 { 483 messages::internalError(asyncResp->res); 484 } 485 else 486 { 487 asyncResp->res.jsonValue["IPv4Addresses"][ipIdx][name] = newValue; 488 } 489 }; 490 491 crow::connections::systemBus->async_method_call( 492 std::move(callback), "xyz.openbmc_project.Network", 493 "/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" + ipHash, 494 "org.freedesktop.DBus.Properties", "Set", 495 "xyz.openbmc_project.Network.IP", name, 496 sdbusplus::message::variant<std::string>(newValue)); 497 } 498 499 /** 500 * @brief Changes IPv4 address origin property 501 * 502 * @param[in] ifaceId Id of interface whose IP should be modified 503 * @param[in] ipIdx Index of IP in input array that should be 504 * modified 505 * @param[in] ipHash DBus Hash id of modified IP 506 * @param[in] newValue New value in Redfish format 507 * @param[in] newValueDbus New value in D-Bus format 508 * @param[io] asyncResp Response object that will be returned to client 509 * 510 * @return true if give IP is valid and has been sent do D-Bus, false 511 * otherwise 512 */ 513 inline void changeIPv4Origin(const std::string &ifaceId, int ipIdx, 514 const std::string &ipHash, 515 const std::string &newValue, 516 const std::string &newValueDbus, 517 const std::shared_ptr<AsyncResp> asyncResp) 518 { 519 auto callback = [asyncResp, ipIdx, newValue{std::move(newValue)}]( 520 const boost::system::error_code ec) { 521 if (ec) 522 { 523 messages::internalError(asyncResp->res); 524 } 525 else 526 { 527 asyncResp->res.jsonValue["IPv4Addresses"][ipIdx]["AddressOrigin"] = 528 newValue; 529 } 530 }; 531 532 crow::connections::systemBus->async_method_call( 533 std::move(callback), "xyz.openbmc_project.Network", 534 "/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" + ipHash, 535 "org.freedesktop.DBus.Properties", "Set", 536 "xyz.openbmc_project.Network.IP", "Origin", 537 sdbusplus::message::variant<std::string>(newValueDbus)); 538 } 539 540 /** 541 * @brief Modifies SubnetMask for given IP 542 * 543 * @param[in] ifaceId Id of interface whose IP should be modified 544 * @param[in] ipIdx Index of IP in input array that should be 545 * modified 546 * @param[in] ipHash DBus Hash id of modified IP 547 * @param[in] newValueStr Mask in dot notation as string 548 * @param[in] newValue Mask as PrefixLength in bitcount 549 * @param[io] asyncResp Response object that will be returned to client 550 * 551 * @return None 552 */ 553 inline void changeIPv4SubnetMaskProperty(const std::string &ifaceId, int ipIdx, 554 const std::string &ipHash, 555 const std::string &newValueStr, 556 uint8_t &newValue, 557 std::shared_ptr<AsyncResp> asyncResp) 558 { 559 auto callback = [asyncResp, ipIdx, newValueStr{std::move(newValueStr)}]( 560 const boost::system::error_code ec) { 561 if (ec) 562 { 563 messages::internalError(asyncResp->res); 564 } 565 else 566 { 567 asyncResp->res.jsonValue["IPv4Addresses"][ipIdx]["SubnetMask"] = 568 newValueStr; 569 } 570 }; 571 572 crow::connections::systemBus->async_method_call( 573 std::move(callback), "xyz.openbmc_project.Network", 574 "/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" + ipHash, 575 "org.freedesktop.DBus.Properties", "Set", 576 "xyz.openbmc_project.Network.IP", "PrefixLength", 577 sdbusplus::message::variant<uint8_t>(newValue)); 578 } 579 580 /** 581 * @brief Sets given HostName of the machine through D-Bus 582 * 583 * @param[in] newHostname New name that HostName will be changed to 584 * @param[in] callback Function that will be called after the operation 585 * 586 * @return None. 587 */ 588 template <typename CallbackFunc> 589 void setHostName(const std::string &newHostname, CallbackFunc &&callback) 590 { 591 crow::connections::systemBus->async_method_call( 592 callback, "xyz.openbmc_project.Network", 593 "/xyz/openbmc_project/network/config", 594 "org.freedesktop.DBus.Properties", "Set", 595 "xyz.openbmc_project.Network.SystemConfiguration", "HostName", 596 sdbusplus::message::variant<std::string>(newHostname)); 597 } 598 599 /** 600 * @brief Deletes given IPv4 601 * 602 * @param[in] ifaceId Id of interface whose IP should be deleted 603 * @param[in] ipIdx Index of IP in input array that should be deleted 604 * @param[in] ipHash DBus Hash id of IP that should be deleted 605 * @param[io] asyncResp Response object that will be returned to client 606 * 607 * @return None 608 */ 609 inline void deleteIPv4(const std::string &ifaceId, const std::string &ipHash, 610 unsigned int ipIdx, 611 const std::shared_ptr<AsyncResp> asyncResp) 612 { 613 crow::connections::systemBus->async_method_call( 614 [ipIdx, asyncResp](const boost::system::error_code ec) { 615 if (ec) 616 { 617 messages::internalError(asyncResp->res); 618 } 619 else 620 { 621 asyncResp->res.jsonValue["IPv4Addresses"][ipIdx] = nullptr; 622 } 623 }, 624 "xyz.openbmc_project.Network", 625 "/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" + ipHash, 626 "xyz.openbmc_project.Object.Delete", "Delete"); 627 } 628 629 /** 630 * @brief Creates IPv4 with given data 631 * 632 * @param[in] ifaceId Id of interface whose IP should be deleted 633 * @param[in] ipIdx Index of IP in input array that should be deleted 634 * @param[in] ipHash DBus Hash id of IP that should be deleted 635 * @param[io] asyncResp Response object that will be returned to client 636 * 637 * @return None 638 */ 639 inline void createIPv4(const std::string &ifaceId, unsigned int ipIdx, 640 uint8_t subnetMask, const std::string &gateway, 641 const std::string &address, 642 std::shared_ptr<AsyncResp> asyncResp) 643 { 644 auto createIpHandler = [ipIdx, 645 asyncResp](const boost::system::error_code ec) { 646 if (ec) 647 { 648 messages::internalError(asyncResp->res); 649 } 650 }; 651 652 crow::connections::systemBus->async_method_call( 653 std::move(createIpHandler), "xyz.openbmc_project.Network", 654 "/xyz/openbmc_project/network/" + ifaceId, 655 "xyz.openbmc_project.Network.IP.Create", "IP", 656 "xyz.openbmc_project.Network.IP.Protocol.IPv4", address, subnetMask, 657 gateway); 658 } 659 660 /** 661 * Function that retrieves all properties for given Ethernet Interface 662 * Object 663 * from EntityManager Network Manager 664 * @param ethiface_id a eth interface id to query on DBus 665 * @param callback a function that shall be called to convert Dbus output 666 * into JSON 667 */ 668 template <typename CallbackFunc> 669 void getEthernetIfaceData(const std::string ðiface_id, 670 CallbackFunc &&callback) 671 { 672 crow::connections::systemBus->async_method_call( 673 [ethiface_id{std::string{ethiface_id}}, callback{std::move(callback)}]( 674 const boost::system::error_code error_code, 675 const GetManagedObjects &resp) { 676 EthernetInterfaceData ethData{}; 677 boost::container::flat_set<IPv4AddressData> ipv4Data; 678 679 if (error_code) 680 { 681 callback(false, ethData, ipv4Data); 682 return; 683 } 684 685 extractEthernetInterfaceData(ethiface_id, resp, ethData); 686 extractIPData(ethiface_id, resp, ipv4Data); 687 688 // Fix global GW 689 for (IPv4AddressData &ipv4 : ipv4Data) 690 { 691 if ((ipv4.linktype == LinkType::Global) && 692 (ipv4.gateway == "0.0.0.0")) 693 { 694 ipv4.gateway = ethData.default_gateway; 695 } 696 } 697 698 // Finally make a callback with usefull data 699 callback(true, ethData, ipv4Data); 700 }, 701 "xyz.openbmc_project.Network", "/xyz/openbmc_project/network", 702 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 703 }; 704 705 /** 706 * Function that retrieves all Ethernet Interfaces available through Network 707 * Manager 708 * @param callback a function that shall be called to convert Dbus output 709 * into JSON. 710 */ 711 template <typename CallbackFunc> 712 void getEthernetIfaceList(CallbackFunc &&callback) 713 { 714 crow::connections::systemBus->async_method_call( 715 [callback{std::move(callback)}]( 716 const boost::system::error_code error_code, 717 GetManagedObjects &resp) { 718 // Callback requires vector<string> to retrieve all available 719 // ethernet interfaces 720 std::vector<std::string> iface_list; 721 iface_list.reserve(resp.size()); 722 if (error_code) 723 { 724 callback(false, iface_list); 725 return; 726 } 727 728 // Iterate over all retrieved ObjectPaths. 729 for (const auto &objpath : resp) 730 { 731 // And all interfaces available for certain ObjectPath. 732 for (const auto &interface : objpath.second) 733 { 734 // If interface is 735 // xyz.openbmc_project.Network.EthernetInterface, this is 736 // what we're looking for. 737 if (interface.first == 738 "xyz.openbmc_project.Network.EthernetInterface") 739 { 740 // Cut out everyting until last "/", ... 741 const std::string &iface_id = objpath.first.str; 742 std::size_t last_pos = iface_id.rfind("/"); 743 if (last_pos != std::string::npos) 744 { 745 // and put it into output vector. 746 iface_list.emplace_back( 747 iface_id.substr(last_pos + 1)); 748 } 749 } 750 } 751 } 752 // Finally make a callback with useful data 753 callback(true, iface_list); 754 }, 755 "xyz.openbmc_project.Network", "/xyz/openbmc_project/network", 756 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 757 }; 758 759 /** 760 * EthernetCollection derived class for delivering Ethernet Collection Schema 761 */ 762 class EthernetCollection : public Node 763 { 764 public: 765 template <typename CrowApp> 766 EthernetCollection(CrowApp &app) : 767 Node(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/") 768 { 769 Node::json["@odata.type"] = 770 "#EthernetInterfaceCollection.EthernetInterfaceCollection"; 771 Node::json["@odata.context"] = 772 "/redfish/v1/" 773 "$metadata#EthernetInterfaceCollection.EthernetInterfaceCollection"; 774 Node::json["@odata.id"] = "/redfish/v1/Managers/bmc/EthernetInterfaces"; 775 Node::json["Name"] = "Ethernet Network Interface Collection"; 776 Node::json["Description"] = 777 "Collection of EthernetInterfaces for this Manager"; 778 779 entityPrivileges = { 780 {boost::beast::http::verb::get, {{"Login"}}}, 781 {boost::beast::http::verb::head, {{"Login"}}}, 782 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 783 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 784 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 785 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 786 } 787 788 private: 789 /** 790 * Functions triggers appropriate requests on DBus 791 */ 792 void doGet(crow::Response &res, const crow::Request &req, 793 const std::vector<std::string> ¶ms) override 794 { 795 res.jsonValue = Node::json; 796 // Get eth interface list, and call the below callback for JSON 797 // preparation 798 getEthernetIfaceList( 799 [&res](const bool &success, 800 const std::vector<std::string> &iface_list) { 801 if (!success) 802 { 803 messages::internalError(res); 804 res.end(); 805 return; 806 } 807 808 nlohmann::json &iface_array = res.jsonValue["Members"]; 809 iface_array = nlohmann::json::array(); 810 for (const std::string &iface_item : iface_list) 811 { 812 iface_array.push_back( 813 {{"@odata.id", 814 "/redfish/v1/Managers/bmc/EthernetInterfaces/" + 815 iface_item}}); 816 } 817 818 res.jsonValue["Members@odata.count"] = iface_array.size(); 819 res.jsonValue["@odata.id"] = 820 "/redfish/v1/Managers/bmc/EthernetInterfaces"; 821 res.end(); 822 }); 823 } 824 }; 825 826 /** 827 * EthernetInterface derived class for delivering Ethernet Schema 828 */ 829 class EthernetInterface : public Node 830 { 831 public: 832 /* 833 * Default Constructor 834 */ 835 template <typename CrowApp> 836 EthernetInterface(CrowApp &app) : 837 Node(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/", 838 std::string()) 839 { 840 Node::json["@odata.type"] = 841 "#EthernetInterface.v1_2_0.EthernetInterface"; 842 Node::json["@odata.context"] = 843 "/redfish/v1/$metadata#EthernetInterface.EthernetInterface"; 844 Node::json["Name"] = "Manager Ethernet Interface"; 845 Node::json["Description"] = "Management Network Interface"; 846 847 entityPrivileges = { 848 {boost::beast::http::verb::get, {{"Login"}}}, 849 {boost::beast::http::verb::head, {{"Login"}}}, 850 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 851 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 852 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 853 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 854 } 855 856 // TODO(kkowalsk) Find a suitable class/namespace for this 857 static void handleVlanPatch(const std::string &ifaceId, 858 const nlohmann::json &input, 859 const EthernetInterfaceData ðData, 860 const std::shared_ptr<AsyncResp> asyncResp) 861 { 862 if (!input.is_object()) 863 { 864 messages::propertyValueTypeError(asyncResp->res, input.dump(), 865 "VLAN"); 866 return; 867 } 868 869 nlohmann::json::const_iterator vlanEnable = input.find("VLANEnable"); 870 if (vlanEnable == input.end()) 871 { 872 messages::propertyMissing(asyncResp->res, "VLANEnable"); 873 return; 874 } 875 const bool *vlanEnableBool = vlanEnable->get_ptr<const bool *>(); 876 if (vlanEnableBool == nullptr) 877 { 878 messages::propertyValueTypeError(asyncResp->res, vlanEnable->dump(), 879 "VLANEnable"); 880 return; 881 } 882 883 nlohmann::json::const_iterator vlanId = input.find("VLANId"); 884 if (vlanId == input.end()) 885 { 886 messages::propertyMissing(asyncResp->res, "VLANId"); 887 return; 888 } 889 const uint64_t *vlanIdUint = vlanId->get_ptr<const uint64_t *>(); 890 if (vlanIdUint == nullptr) 891 { 892 messages::propertyValueTypeError(asyncResp->res, vlanId->dump(), 893 "VLANId"); 894 return; 895 } 896 897 if (!ethData.vlan_id) 898 { 899 // This interface is not a VLAN. Cannot do anything with it 900 // TODO(kkowalsk) Change this message 901 messages::propertyNotWritable(asyncResp->res, "VLANEnable"); 902 903 return; 904 } 905 906 // VLAN is configured on the interface 907 if (*vlanEnableBool == true) 908 { 909 // Change VLAN Id 910 asyncResp->res.jsonValue["VLANId"] = *vlanIdUint; 911 auto callback = [asyncResp](const boost::system::error_code ec) { 912 if (ec) 913 { 914 messages::internalError(asyncResp->res); 915 } 916 else 917 { 918 asyncResp->res.jsonValue["VLANEnable"] = true; 919 } 920 }; 921 crow::connections::systemBus->async_method_call( 922 std::move(callback), "xyz.openbmc_project.Network", 923 "/xyz/openbmc_project/network/" + ifaceId, 924 "org.freedesktop.DBus.Properties", "Set", 925 "xyz.openbmc_project.Network.VLAN", "Id", 926 sdbusplus::message::variant<uint32_t>(*vlanIdUint)); 927 } 928 else 929 { 930 auto callback = [asyncResp](const boost::system::error_code ec) { 931 if (ec) 932 { 933 messages::internalError(asyncResp->res); 934 return; 935 } 936 asyncResp->res.jsonValue["VLANEnable"] = false; 937 }; 938 939 crow::connections::systemBus->async_method_call( 940 std::move(callback), "xyz.openbmc_project.Network", 941 "/xyz/openbmc_project/network/" + ifaceId, 942 "xyz.openbmc_project.Object.Delete", "Delete"); 943 } 944 } 945 946 private: 947 void handleHostnamePatch(const nlohmann::json &input, 948 const std::shared_ptr<AsyncResp> asyncResp) 949 { 950 const std::string *newHostname = input.get_ptr<const std::string *>(); 951 if (newHostname == nullptr) 952 { 953 messages::propertyValueTypeError(asyncResp->res, input.dump(), 954 "HostName"); 955 return; 956 } 957 958 // Change hostname 959 setHostName(*newHostname, 960 [asyncResp, newHostname{std::string(*newHostname)}]( 961 const boost::system::error_code ec) { 962 if (ec) 963 { 964 messages::internalError(asyncResp->res); 965 } 966 else 967 { 968 asyncResp->res.jsonValue["HostName"] = newHostname; 969 } 970 }); 971 } 972 973 void handleIPv4Patch( 974 const std::string &ifaceId, const nlohmann::json &input, 975 const boost::container::flat_set<IPv4AddressData> &ipv4Data, 976 const std::shared_ptr<AsyncResp> asyncResp) 977 { 978 if (!input.is_array()) 979 { 980 messages::propertyValueTypeError(asyncResp->res, input.dump(), 981 "IPv4Addresses"); 982 return; 983 } 984 985 // According to Redfish PATCH definition, size must be at least equal 986 if (input.size() < ipv4Data.size()) 987 { 988 messages::propertyValueFormatError(asyncResp->res, input.dump(), 989 "IPv4Addresses"); 990 return; 991 } 992 993 int entryIdx = 0; 994 boost::container::flat_set<IPv4AddressData>::const_iterator thisData = 995 ipv4Data.begin(); 996 for (const nlohmann::json &thisJson : input) 997 { 998 std::string pathString = 999 "IPv4Addresses/" + std::to_string(entryIdx); 1000 // Check that entry is not of some unexpected type 1001 if (!thisJson.is_object() && !thisJson.is_null()) 1002 { 1003 messages::propertyValueTypeError(asyncResp->res, 1004 thisJson.dump(), 1005 pathString + "/IPv4Address"); 1006 1007 continue; 1008 } 1009 1010 nlohmann::json::const_iterator addressFieldIt = 1011 thisJson.find("Address"); 1012 const std::string *addressField = nullptr; 1013 if (addressFieldIt != thisJson.end()) 1014 { 1015 addressField = addressFieldIt->get_ptr<const std::string *>(); 1016 if (addressField == nullptr) 1017 { 1018 messages::propertyValueFormatError(asyncResp->res, 1019 addressFieldIt->dump(), 1020 pathString + "/Address"); 1021 continue; 1022 } 1023 else 1024 { 1025 if (!ipv4VerifyIpAndGetBitcount(*addressField)) 1026 { 1027 messages::propertyValueFormatError( 1028 asyncResp->res, *addressField, 1029 pathString + "/Address"); 1030 continue; 1031 } 1032 } 1033 } 1034 1035 boost::optional<uint8_t> prefixLength; 1036 const std::string *subnetField = nullptr; 1037 nlohmann::json::const_iterator subnetFieldIt = 1038 thisJson.find("SubnetMask"); 1039 if (subnetFieldIt != thisJson.end()) 1040 { 1041 subnetField = subnetFieldIt->get_ptr<const std::string *>(); 1042 if (subnetField == nullptr) 1043 { 1044 messages::propertyValueFormatError( 1045 asyncResp->res, *subnetField, 1046 pathString + "/SubnetMask"); 1047 continue; 1048 } 1049 else 1050 { 1051 prefixLength = 0; 1052 if (!ipv4VerifyIpAndGetBitcount(*subnetField, 1053 &*prefixLength)) 1054 { 1055 messages::propertyValueFormatError( 1056 asyncResp->res, *subnetField, 1057 pathString + "/SubnetMask"); 1058 continue; 1059 } 1060 } 1061 } 1062 1063 std::string addressOriginInDBusFormat; 1064 const std::string *addressOriginField = nullptr; 1065 nlohmann::json::const_iterator addressOriginFieldIt = 1066 thisJson.find("AddressOrigin"); 1067 if (addressOriginFieldIt != thisJson.end()) 1068 { 1069 const std::string *addressOriginField = 1070 addressOriginFieldIt->get_ptr<const std::string *>(); 1071 if (addressOriginField == nullptr) 1072 { 1073 messages::propertyValueFormatError( 1074 asyncResp->res, *addressOriginField, 1075 pathString + "/AddressOrigin"); 1076 continue; 1077 } 1078 else 1079 { 1080 // Get Address origin in proper format 1081 addressOriginInDBusFormat = 1082 translateAddressOriginRedfishToDbus( 1083 *addressOriginField); 1084 if (addressOriginInDBusFormat.empty()) 1085 { 1086 messages::propertyValueNotInList( 1087 asyncResp->res, *addressOriginField, 1088 pathString + "/AddressOrigin"); 1089 continue; 1090 } 1091 } 1092 } 1093 1094 nlohmann::json::const_iterator gatewayFieldIt = 1095 thisJson.find("Gateway"); 1096 const std::string *gatewayField = nullptr; 1097 if (gatewayFieldIt != thisJson.end()) 1098 { 1099 const std::string *gatewayField = 1100 gatewayFieldIt->get_ptr<const std::string *>(); 1101 if (gatewayField == nullptr || 1102 !ipv4VerifyIpAndGetBitcount(*gatewayField)) 1103 { 1104 messages::propertyValueFormatError( 1105 asyncResp->res, *gatewayField, pathString + "/Gateway"); 1106 continue; 1107 } 1108 } 1109 1110 // if a vlan already exists, modify the existing 1111 if (thisData != ipv4Data.end()) 1112 { 1113 // Existing object that should be modified/deleted/remain 1114 // unchanged 1115 if (thisJson.is_null()) 1116 { 1117 auto callback = [entryIdx{std::to_string(entryIdx)}, 1118 asyncResp]( 1119 const boost::system::error_code ec) { 1120 if (ec) 1121 { 1122 messages::internalError(asyncResp->res); 1123 return; 1124 } 1125 asyncResp->res.jsonValue["IPv4Addresses"][entryIdx] = 1126 nullptr; 1127 }; 1128 crow::connections::systemBus->async_method_call( 1129 std::move(callback), "xyz.openbmc_project.Network", 1130 "/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" + 1131 thisData->id, 1132 "xyz.openbmc_project.Object.Delete", "Delete"); 1133 } 1134 else if (thisJson.is_object()) 1135 { 1136 // Apply changes 1137 if (addressField != nullptr) 1138 { 1139 auto callback = 1140 [asyncResp, entryIdx, 1141 addressField{std::string(*addressField)}]( 1142 const boost::system::error_code ec) { 1143 if (ec) 1144 { 1145 messages::internalError(asyncResp->res); 1146 return; 1147 } 1148 asyncResp->res 1149 .jsonValue["IPv4Addresses"][std::to_string( 1150 entryIdx)]["Address"] = addressField; 1151 }; 1152 1153 crow::connections::systemBus->async_method_call( 1154 std::move(callback), "xyz.openbmc_project.Network", 1155 "/xyz/openbmc_project/network/" + ifaceId + 1156 "/ipv4/" + thisData->id, 1157 "org.freedesktop.DBus.Properties", "Set", 1158 "xyz.openbmc_project.Network.IP", "Address", 1159 sdbusplus::message::variant<std::string>( 1160 *addressField)); 1161 } 1162 1163 if (prefixLength && subnetField != nullptr) 1164 { 1165 changeIPv4SubnetMaskProperty(ifaceId, entryIdx, 1166 thisData->id, *subnetField, 1167 *prefixLength, asyncResp); 1168 } 1169 1170 if (!addressOriginInDBusFormat.empty() && 1171 addressOriginField != nullptr) 1172 { 1173 changeIPv4Origin(ifaceId, entryIdx, thisData->id, 1174 *addressOriginField, 1175 addressOriginInDBusFormat, asyncResp); 1176 } 1177 1178 if (gatewayField != nullptr) 1179 { 1180 auto callback = 1181 [asyncResp, entryIdx, 1182 gatewayField{std::string(*gatewayField)}]( 1183 const boost::system::error_code ec) { 1184 if (ec) 1185 { 1186 messages::internalError(asyncResp->res); 1187 return; 1188 } 1189 asyncResp->res 1190 .jsonValue["IPv4Addresses"][std::to_string( 1191 entryIdx)]["Gateway"] = 1192 std::move(gatewayField); 1193 }; 1194 1195 crow::connections::systemBus->async_method_call( 1196 std::move(callback), "xyz.openbmc_project.Network", 1197 "/xyz/openbmc_project/network/" + ifaceId + 1198 "/ipv4/" + thisData->id, 1199 "org.freedesktop.DBus.Properties", "Set", 1200 "xyz.openbmc_project.Network.IP", "Gateway", 1201 sdbusplus::message::variant<std::string>( 1202 *gatewayField)); 1203 } 1204 } 1205 thisData++; 1206 } 1207 else 1208 { 1209 // Create IPv4 with provided data 1210 if (gatewayField == nullptr) 1211 { 1212 messages::propertyMissing(asyncResp->res, 1213 pathString + "/Gateway"); 1214 continue; 1215 } 1216 1217 if (addressField == nullptr) 1218 { 1219 messages::propertyMissing(asyncResp->res, 1220 pathString + "/Address"); 1221 continue; 1222 } 1223 1224 if (!prefixLength) 1225 { 1226 messages::propertyMissing(asyncResp->res, 1227 pathString + "/SubnetMask"); 1228 continue; 1229 } 1230 1231 createIPv4(ifaceId, entryIdx, *prefixLength, *gatewayField, 1232 *addressField, asyncResp); 1233 asyncResp->res.jsonValue["IPv4Addresses"][entryIdx] = thisJson; 1234 } 1235 entryIdx++; 1236 } 1237 } 1238 1239 nlohmann::json parseInterfaceData( 1240 const std::string &iface_id, const EthernetInterfaceData ðData, 1241 const boost::container::flat_set<IPv4AddressData> &ipv4Data) 1242 { 1243 // Copy JSON object to avoid race condition 1244 nlohmann::json json_response(Node::json); 1245 1246 json_response["Id"] = iface_id; 1247 json_response["@odata.id"] = 1248 "/redfish/v1/Managers/bmc/EthernetInterfaces/" + iface_id; 1249 1250 json_response["SpeedMbps"] = ethData.speed; 1251 json_response["MACAddress"] = ethData.mac_address; 1252 if (!ethData.hostname.empty()) 1253 { 1254 json_response["HostName"] = ethData.hostname; 1255 } 1256 1257 nlohmann::json &vlanObj = json_response["VLAN"]; 1258 if (ethData.vlan_id) 1259 { 1260 vlanObj["VLANEnable"] = true; 1261 vlanObj["VLANId"] = *ethData.vlan_id; 1262 } 1263 else 1264 { 1265 vlanObj["VLANEnable"] = false; 1266 vlanObj["VLANId"] = 0; 1267 } 1268 1269 if (ipv4Data.size() > 0) 1270 { 1271 nlohmann::json &ipv4_array = json_response["IPv4Addresses"]; 1272 ipv4_array = nlohmann::json::array(); 1273 for (auto &ipv4_config : ipv4Data) 1274 { 1275 if (!ipv4_config.address.empty()) 1276 { 1277 ipv4_array.push_back({{"AddressOrigin", ipv4_config.origin}, 1278 {"SubnetMask", ipv4_config.netmask}, 1279 {"Address", ipv4_config.address}}); 1280 1281 if (!ipv4_config.gateway.empty()) 1282 { 1283 ipv4_array.back()["Gateway"] = ipv4_config.gateway; 1284 } 1285 } 1286 } 1287 } 1288 1289 return json_response; 1290 } 1291 1292 /** 1293 * Functions triggers appropriate requests on DBus 1294 */ 1295 void doGet(crow::Response &res, const crow::Request &req, 1296 const std::vector<std::string> ¶ms) override 1297 { 1298 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1299 if (params.size() != 1) 1300 { 1301 messages::internalError(asyncResp->res); 1302 return; 1303 } 1304 1305 getEthernetIfaceData( 1306 params[0], 1307 [this, asyncResp, iface_id{std::string(params[0])}]( 1308 const bool &success, const EthernetInterfaceData ðData, 1309 const boost::container::flat_set<IPv4AddressData> &ipv4Data) { 1310 if (!success) 1311 { 1312 // TODO(Pawel)consider distinguish between non existing 1313 // object, and other errors 1314 messages::resourceNotFound(asyncResp->res, 1315 "EthernetInterface", iface_id); 1316 return; 1317 } 1318 asyncResp->res.jsonValue = 1319 parseInterfaceData(iface_id, ethData, ipv4Data); 1320 }); 1321 } 1322 1323 void doPatch(crow::Response &res, const crow::Request &req, 1324 const std::vector<std::string> ¶ms) override 1325 { 1326 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1327 if (params.size() != 1) 1328 { 1329 messages::internalError(asyncResp->res); 1330 return; 1331 } 1332 1333 const std::string &iface_id = params[0]; 1334 1335 nlohmann::json patchReq; 1336 if (!json_util::processJsonFromRequest(res, req, patchReq)) 1337 { 1338 return; 1339 } 1340 1341 // Get single eth interface data, and call the below callback for JSON 1342 // preparation 1343 getEthernetIfaceData( 1344 iface_id, 1345 [this, asyncResp, iface_id, patchReq = std::move(patchReq)]( 1346 const bool &success, const EthernetInterfaceData ðData, 1347 const boost::container::flat_set<IPv4AddressData> &ipv4Data) { 1348 if (!success) 1349 { 1350 // ... otherwise return error 1351 // TODO(Pawel)consider distinguish between non existing 1352 // object, and other errors 1353 messages::resourceNotFound( 1354 asyncResp->res, "VLAN Network Interface", iface_id); 1355 return; 1356 } 1357 1358 asyncResp->res.jsonValue = 1359 parseInterfaceData(iface_id, ethData, ipv4Data); 1360 1361 for (auto propertyIt : patchReq.items()) 1362 { 1363 if (propertyIt.key() == "VLAN") 1364 { 1365 handleVlanPatch(iface_id, propertyIt.value(), ethData, 1366 asyncResp); 1367 } 1368 else if (propertyIt.key() == "HostName") 1369 { 1370 handleHostnamePatch(propertyIt.value(), asyncResp); 1371 } 1372 else if (propertyIt.key() == "IPv4Addresses") 1373 { 1374 handleIPv4Patch(iface_id, propertyIt.value(), ipv4Data, 1375 asyncResp); 1376 } 1377 else if (propertyIt.key() == "IPv6Addresses") 1378 { 1379 // TODO(kkowalsk) IPv6 Not supported on D-Bus yet 1380 messages::propertyNotWritable(asyncResp->res, 1381 propertyIt.key()); 1382 } 1383 else 1384 { 1385 auto fieldInJsonIt = 1386 asyncResp->res.jsonValue.find(propertyIt.key()); 1387 1388 if (fieldInJsonIt == asyncResp->res.jsonValue.end()) 1389 { 1390 // Field not in scope of defined fields 1391 messages::propertyUnknown(asyncResp->res, 1392 propertyIt.key()); 1393 } 1394 else 1395 { 1396 // User attempted to modify non-writable field 1397 messages::propertyNotWritable(asyncResp->res, 1398 propertyIt.key()); 1399 } 1400 } 1401 } 1402 }); 1403 } 1404 }; 1405 1406 /** 1407 * VlanNetworkInterface derived class for delivering VLANNetworkInterface 1408 * Schema 1409 */ 1410 class VlanNetworkInterface : public Node 1411 { 1412 public: 1413 /* 1414 * Default Constructor 1415 */ 1416 template <typename CrowApp> 1417 // TODO(Pawel) Remove line from below, where we assume that there is only 1418 // one manager called openbmc. This shall be generic, but requires to 1419 // update GetSubroutes method 1420 VlanNetworkInterface(CrowApp &app) : 1421 Node(app, 1422 "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>>/VLANs/" 1423 "<str>", 1424 std::string(), std::string()) 1425 { 1426 Node::json["@odata.type"] = 1427 "#VLanNetworkInterface.v1_1_0.VLanNetworkInterface"; 1428 Node::json["@odata.context"] = 1429 "/redfish/v1/$metadata#VLanNetworkInterface.VLanNetworkInterface"; 1430 Node::json["Name"] = "VLAN Network Interface"; 1431 1432 entityPrivileges = { 1433 {boost::beast::http::verb::get, {{"Login"}}}, 1434 {boost::beast::http::verb::head, {{"Login"}}}, 1435 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 1436 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 1437 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 1438 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 1439 } 1440 1441 private: 1442 nlohmann::json parseInterfaceData( 1443 const std::string &parent_iface_id, const std::string &iface_id, 1444 const EthernetInterfaceData ðData, 1445 const boost::container::flat_set<IPv4AddressData> &ipv4Data) 1446 { 1447 // Copy JSON object to avoid race condition 1448 nlohmann::json json_response(Node::json); 1449 1450 // Fill out obvious data... 1451 json_response["Id"] = iface_id; 1452 json_response["@odata.id"] = 1453 "/redfish/v1/Managers/bmc/EthernetInterfaces/" + parent_iface_id + 1454 "/VLANs/" + iface_id; 1455 1456 json_response["VLANEnable"] = true; 1457 if (ethData.vlan_id) 1458 { 1459 json_response["VLANId"] = *ethData.vlan_id; 1460 } 1461 return json_response; 1462 } 1463 1464 bool verifyNames(crow::Response &res, const std::string &parent, 1465 const std::string &iface) 1466 { 1467 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1468 if (!boost::starts_with(iface, parent + "_")) 1469 { 1470 messages::resourceNotFound(asyncResp->res, "VLAN Network Interface", 1471 iface); 1472 return false; 1473 } 1474 else 1475 { 1476 return true; 1477 } 1478 } 1479 1480 /** 1481 * Functions triggers appropriate requests on DBus 1482 */ 1483 void doGet(crow::Response &res, const crow::Request &req, 1484 const std::vector<std::string> ¶ms) override 1485 { 1486 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1487 // TODO(Pawel) this shall be parameterized call (two params) to get 1488 // EthernetInterfaces for any Manager, not only hardcoded 'openbmc'. 1489 // Check if there is required param, truly entering this shall be 1490 // impossible. 1491 if (params.size() != 2) 1492 { 1493 messages::internalError(res); 1494 res.end(); 1495 return; 1496 } 1497 1498 const std::string &parent_iface_id = params[0]; 1499 const std::string &iface_id = params[1]; 1500 1501 if (!verifyNames(res, parent_iface_id, iface_id)) 1502 { 1503 return; 1504 } 1505 1506 // Get single eth interface data, and call the below callback for JSON 1507 // preparation 1508 getEthernetIfaceData( 1509 iface_id, 1510 [this, asyncResp, parent_iface_id, iface_id]( 1511 const bool &success, const EthernetInterfaceData ðData, 1512 const boost::container::flat_set<IPv4AddressData> &ipv4Data) { 1513 if (success && ethData.vlan_id) 1514 { 1515 asyncResp->res.jsonValue = parseInterfaceData( 1516 parent_iface_id, iface_id, ethData, ipv4Data); 1517 } 1518 else 1519 { 1520 // ... otherwise return error 1521 // TODO(Pawel)consider distinguish between non existing 1522 // object, and other errors 1523 messages::resourceNotFound( 1524 asyncResp->res, "VLAN Network Interface", iface_id); 1525 } 1526 }); 1527 } 1528 1529 void doPatch(crow::Response &res, const crow::Request &req, 1530 const std::vector<std::string> ¶ms) override 1531 { 1532 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1533 if (params.size() != 2) 1534 { 1535 messages::internalError(asyncResp->res); 1536 return; 1537 } 1538 1539 const std::string &parentIfaceId = params[0]; 1540 const std::string &ifaceId = params[1]; 1541 1542 if (!verifyNames(res, parentIfaceId, ifaceId)) 1543 { 1544 return; 1545 } 1546 1547 nlohmann::json patchReq; 1548 if (!json_util::processJsonFromRequest(res, req, patchReq)) 1549 { 1550 return; 1551 } 1552 1553 // Get single eth interface data, and call the below callback for JSON 1554 // preparation 1555 getEthernetIfaceData( 1556 ifaceId, 1557 [this, asyncResp, parentIfaceId, ifaceId, 1558 patchReq{std::move(patchReq)}]( 1559 const bool &success, const EthernetInterfaceData ðData, 1560 const boost::container::flat_set<IPv4AddressData> &ipv4Data) { 1561 if (!success) 1562 { 1563 // TODO(Pawel)consider distinguish between non existing 1564 // object, and other errors 1565 messages::resourceNotFound( 1566 asyncResp->res, "VLAN Network Interface", ifaceId); 1567 1568 return; 1569 } 1570 1571 asyncResp->res.jsonValue = parseInterfaceData( 1572 parentIfaceId, ifaceId, ethData, ipv4Data); 1573 1574 for (auto propertyIt : patchReq.items()) 1575 { 1576 if (propertyIt.key() != "VLANEnable" && 1577 propertyIt.key() != "VLANId") 1578 { 1579 auto fieldInJsonIt = 1580 asyncResp->res.jsonValue.find(propertyIt.key()); 1581 if (fieldInJsonIt == asyncResp->res.jsonValue.end()) 1582 { 1583 // Field not in scope of defined fields 1584 messages::propertyUnknown(asyncResp->res, 1585 propertyIt.key()); 1586 } 1587 else 1588 { 1589 // User attempted to modify non-writable field 1590 messages::propertyNotWritable(asyncResp->res, 1591 propertyIt.key()); 1592 } 1593 } 1594 } 1595 1596 EthernetInterface::handleVlanPatch(ifaceId, patchReq, ethData, 1597 asyncResp); 1598 }); 1599 } 1600 1601 void doDelete(crow::Response &res, const crow::Request &req, 1602 const std::vector<std::string> ¶ms) override 1603 { 1604 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1605 if (params.size() != 2) 1606 { 1607 messages::internalError(asyncResp->res); 1608 return; 1609 } 1610 1611 const std::string &parentIfaceId = params[0]; 1612 const std::string &ifaceId = params[1]; 1613 1614 if (!verifyNames(asyncResp->res, parentIfaceId, ifaceId)) 1615 { 1616 return; 1617 } 1618 1619 // Get single eth interface data, and call the below callback for JSON 1620 // preparation 1621 getEthernetIfaceData( 1622 ifaceId, 1623 [this, asyncResp, parentIfaceId{std::string(parentIfaceId)}, 1624 ifaceId{std::string(ifaceId)}]( 1625 const bool &success, const EthernetInterfaceData ðData, 1626 const boost::container::flat_set<IPv4AddressData> &ipv4Data) { 1627 if (success && ethData.vlan_id) 1628 { 1629 asyncResp->res.jsonValue = parseInterfaceData( 1630 parentIfaceId, ifaceId, ethData, ipv4Data); 1631 1632 auto callback = 1633 [asyncResp](const boost::system::error_code ec) { 1634 if (ec) 1635 { 1636 messages::internalError(asyncResp->res); 1637 } 1638 }; 1639 crow::connections::systemBus->async_method_call( 1640 std::move(callback), "xyz.openbmc_project.Network", 1641 std::string("/xyz/openbmc_project/network/") + ifaceId, 1642 "xyz.openbmc_project.Object.Delete", "Delete"); 1643 } 1644 else 1645 { 1646 // ... otherwise return error 1647 // TODO(Pawel)consider distinguish between non existing 1648 // object, and other errors 1649 messages::resourceNotFound( 1650 asyncResp->res, "VLAN Network Interface", ifaceId); 1651 } 1652 }); 1653 } 1654 }; 1655 1656 /** 1657 * VlanNetworkInterfaceCollection derived class for delivering 1658 * VLANNetworkInterface Collection Schema 1659 */ 1660 class VlanNetworkInterfaceCollection : public Node 1661 { 1662 public: 1663 template <typename CrowApp> 1664 // TODO(Pawel) Remove line from below, where we assume that there is only 1665 // one manager called openbmc. This shall be generic, but requires to 1666 // update GetSubroutes method 1667 VlanNetworkInterfaceCollection(CrowApp &app) : 1668 Node(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/", 1669 std::string()) 1670 { 1671 Node::json["@odata.type"] = 1672 "#VLanNetworkInterfaceCollection.VLanNetworkInterfaceCollection"; 1673 Node::json["@odata.context"] = 1674 "/redfish/v1/$metadata" 1675 "#VLanNetworkInterfaceCollection.VLanNetworkInterfaceCollection"; 1676 Node::json["Name"] = "VLAN Network Interface Collection"; 1677 1678 entityPrivileges = { 1679 {boost::beast::http::verb::get, {{"Login"}}}, 1680 {boost::beast::http::verb::head, {{"Login"}}}, 1681 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 1682 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 1683 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 1684 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 1685 } 1686 1687 private: 1688 /** 1689 * Functions triggers appropriate requests on DBus 1690 */ 1691 void doGet(crow::Response &res, const crow::Request &req, 1692 const std::vector<std::string> ¶ms) override 1693 { 1694 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1695 if (params.size() != 1) 1696 { 1697 // This means there is a problem with the router 1698 messages::internalError(asyncResp->res); 1699 return; 1700 } 1701 1702 const std::string &rootInterfaceName = params[0]; 1703 1704 // Get eth interface list, and call the below callback for JSON 1705 // preparation 1706 getEthernetIfaceList( 1707 [this, asyncResp, 1708 rootInterfaceName{std::string(rootInterfaceName)}]( 1709 const bool &success, 1710 const std::vector<std::string> &iface_list) { 1711 if (!success) 1712 { 1713 messages::internalError(asyncResp->res); 1714 return; 1715 } 1716 asyncResp->res.jsonValue = Node::json; 1717 1718 nlohmann::json iface_array = nlohmann::json::array(); 1719 1720 for (const std::string &iface_item : iface_list) 1721 { 1722 if (boost::starts_with(iface_item, rootInterfaceName + "_")) 1723 { 1724 iface_array.push_back( 1725 {{"@odata.id", 1726 "/redfish/v1/Managers/bmc/EthernetInterfaces/" + 1727 rootInterfaceName + "/VLANs/" + iface_item}}); 1728 } 1729 } 1730 1731 if (iface_array.empty()) 1732 { 1733 messages::resourceNotFound( 1734 asyncResp->res, "EthernetInterface", rootInterfaceName); 1735 return; 1736 } 1737 asyncResp->res.jsonValue["Members@odata.count"] = 1738 iface_array.size(); 1739 asyncResp->res.jsonValue["Members"] = std::move(iface_array); 1740 asyncResp->res.jsonValue["@odata.id"] = 1741 "/redfish/v1/Managers/bmc/EthernetInterfaces/" + 1742 rootInterfaceName + "/VLANs"; 1743 }); 1744 } 1745 1746 void doPost(crow::Response &res, const crow::Request &req, 1747 const std::vector<std::string> ¶ms) override 1748 { 1749 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1750 if (params.size() != 1) 1751 { 1752 messages::internalError(asyncResp->res); 1753 return; 1754 } 1755 1756 nlohmann::json postReq; 1757 if (!json_util::processJsonFromRequest(res, req, postReq)) 1758 { 1759 return; 1760 } 1761 1762 auto vlanIdJson = json.find("VLANId"); 1763 if (vlanIdJson == json.end()) 1764 { 1765 messages::propertyMissing(asyncResp->res, "VLANId"); 1766 return; 1767 } 1768 1769 const uint64_t *vlanId = vlanIdJson->get_ptr<const uint64_t *>(); 1770 if (vlanId == nullptr) 1771 { 1772 messages::propertyValueTypeError(asyncResp->res, vlanIdJson->dump(), 1773 "VLANId"); 1774 return; 1775 } 1776 const std::string &rootInterfaceName = params[0]; 1777 1778 auto callback = [asyncResp](const boost::system::error_code ec) { 1779 if (ec) 1780 { 1781 // TODO(ed) make more consistent error messages based on 1782 // phosphor-network responses 1783 messages::internalError(asyncResp->res); 1784 return; 1785 } 1786 messages::created(asyncResp->res); 1787 }; 1788 crow::connections::systemBus->async_method_call( 1789 std::move(callback), "xyz.openbmc_project.Network", 1790 "/xyz/openbmc_project/network", 1791 "xyz.openbmc_project.Network.VLAN.Create", "VLAN", 1792 rootInterfaceName, static_cast<uint32_t>(*vlanId)); 1793 } 1794 }; 1795 } // namespace redfish 1796