1 /* 2 // Copyright (c) 2018 Intel Corporation 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 */ 16 #pragma once 17 18 #include "node.hpp" 19 #include <boost/container/flat_map.hpp> 20 21 namespace redfish { 22 23 /** 24 * DBus types primitives for several generic DBus interfaces 25 * TODO(Pawel) consider move this to separate file into boost::dbus 26 */ 27 using ManagedObjectsType = std::vector< 28 std::pair<dbus::object_path, 29 std::vector<std::pair< 30 std::string, 31 std::vector<std::pair<std::string, dbus::dbus_variant>>>>>>; 32 33 using PropertiesType = 34 boost::container::flat_map<std::string, dbus::dbus_variant>; 35 36 /** 37 * OnDemandChassisProvider 38 * Chassis provider class that retrieves data directly from dbus, before seting 39 * it into JSON output. This does not cache any data. 40 * 41 * Class can be a good example on how to scale different data providing 42 * solutions to produce single schema output. 43 * 44 * TODO(Pawel) 45 * This perhaps shall be different file, which has to be chosen on compile time 46 * depending on OEM needs 47 */ 48 class OnDemandChassisProvider { 49 public: 50 /** 51 * Function that retrieves all properties for given Chassis Object from 52 * EntityManager 53 * @param res_name a chassis resource name to query on DBus 54 * @param callback a function that shall be called to convert Dbus output into 55 * JSON 56 */ 57 template <typename CallbackFunc> 58 void get_chassis_data(const std::string &res_name, CallbackFunc &&callback) { 59 crow::connections::system_bus->async_method_call( 60 [callback{std::move(callback)}]( 61 const boost::system::error_code error_code, 62 const PropertiesType &properties) { 63 // Callback requires flat_map<string, string> so prepare one. 64 boost::container::flat_map<std::string, std::string> output; 65 if (error_code) { 66 // Something wrong on DBus, the error_code is not important at this 67 // moment, just return success=false, and empty output. Since size 68 // of map may vary depending on Chassis type, an empty output could 69 // not be treated same way as error. 70 callback(false, output); 71 return; 72 } 73 for (const std::pair<const char *, const char *> &p : 74 std::array<std::pair<const char *, const char *>, 5>{ 75 {{"name", "Name"}, 76 {"manufacturer", "Manufacturer"}, 77 {"model", "Model"}, 78 {"part_number", "PartNumber"}, 79 {"serial_number", "SerialNumber"}}}) { 80 PropertiesType::const_iterator it = properties.find(p.first); 81 if (it != properties.end()) { 82 const std::string *s = boost::get<std::string>(&it->second); 83 if (s != nullptr) { 84 output[p.second] = *s; 85 } 86 } 87 } 88 // Callback with success, and hopefully data. 89 callback(true, output); 90 }, 91 {"xyz.openbmc_project.EntityManager", 92 "/xyz/openbmc_project/Inventory/Item/Chassis/" + res_name, 93 "org.freedesktop.DBus.Properties", "GetAll"}, 94 "xyz.openbmc_project.Configuration.Chassis"); 95 } 96 97 /** 98 * Function that retrieves all Chassis available through EntityManager. 99 * @param callback a function that shall be called to convert Dbus output into 100 * JSON. 101 */ 102 template <typename CallbackFunc> 103 void get_chassis_list(CallbackFunc &&callback) { 104 crow::connections::system_bus->async_method_call( 105 [callback{std::move(callback)}]( 106 const boost::system::error_code error_code, 107 const ManagedObjectsType &resp) { 108 // Callback requires vector<string> to retrieve all available chassis 109 // list. 110 std::vector<std::string> chassis_list; 111 if (error_code) { 112 // Something wrong on DBus, the error_code is not important at this 113 // moment, just return success=false, and empty output. Since size 114 // of vector may vary depending on information from Entity Manager, 115 // and empty output could not be treated same way as error. 116 callback(false, chassis_list); 117 return; 118 } 119 120 // Iterate over all retrieved ObjectPaths. 121 for (auto &objpath : resp) { 122 // And all interfaces available for certain ObjectPath. 123 for (auto &interface : objpath.second) { 124 // If interface is xyz.openbmc_project.Configuration.Chassis, this 125 // is Chassis. 126 if (interface.first == 127 "xyz.openbmc_project.Configuration.Chassis") { 128 // Cut out everyting until last "/", ... 129 const std::string &chassis_id = objpath.first.value; 130 std::size_t last_pos = chassis_id.rfind("/"); 131 if (last_pos != std::string::npos) { 132 // and put it into output vector. 133 chassis_list.emplace_back(chassis_id.substr(last_pos + 1)); 134 } 135 } 136 } 137 } 138 // Finally make a callback with usefull data 139 callback(true, chassis_list); 140 }, 141 {"xyz.openbmc_project.EntityManager", 142 "/xyz/openbmc_project/Inventory/Item/Chassis", 143 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"}); 144 }; 145 }; 146 147 /** 148 * ChassisCollection derived class for delivering Chassis Collection Schema 149 */ 150 class ChassisCollection : public Node { 151 public: 152 template <typename CrowApp> 153 ChassisCollection(CrowApp &app) : Node(app, "/redfish/v1/Chassis/") { 154 Node::json["@odata.type"] = "#ChassisCollection.ChassisCollection"; 155 Node::json["@odata.id"] = "/redfish/v1/Chassis"; 156 Node::json["@odata.context"] = 157 "/redfish/v1/$metadata#ChassisCollection.ChassisCollection"; 158 Node::json["Name"] = "Chassis Collection"; 159 160 entityPrivileges = {{crow::HTTPMethod::GET, {{"Login"}}}, 161 {crow::HTTPMethod::HEAD, {{"Login"}}}, 162 {crow::HTTPMethod::PATCH, {{"ConfigureComponents"}}}, 163 {crow::HTTPMethod::PUT, {{"ConfigureComponents"}}}, 164 {crow::HTTPMethod::DELETE, {{"ConfigureComponents"}}}, 165 {crow::HTTPMethod::POST, {{"ConfigureComponents"}}}}; 166 } 167 168 private: 169 /** 170 * Functions triggers appropriate requests on DBus 171 */ 172 void doGet(crow::response &res, const crow::request &req, 173 const std::vector<std::string> ¶ms) override { 174 // Get chassis list, and call the below callback for JSON preparation 175 chassis_provider.get_chassis_list( 176 [&](const bool &success, const std::vector<std::string> &output) { 177 if (success) { 178 // ... prepare json array with appropriate @odata.id links 179 nlohmann::json chassis_array = nlohmann::json::array(); 180 for (const std::string &chassis_item : output) { 181 chassis_array.push_back( 182 {{"@odata.id", "/redfish/v1/Chassis/" + chassis_item}}); 183 } 184 // Then attach members, count size and return, 185 Node::json["Members"] = chassis_array; 186 Node::json["Members@odata.count"] = chassis_array.size(); 187 res.json_value = Node::json; 188 } else { 189 // ... otherwise, return INTERNALL ERROR 190 res.code = static_cast<int>(HttpRespCode::INTERNAL_ERROR); 191 } 192 res.end(); 193 }); 194 } 195 196 // Chassis Provider object 197 // TODO(Pawel) consider move it to singleton 198 OnDemandChassisProvider chassis_provider; 199 }; 200 201 /** 202 * Chassis override class for delivering Chassis Schema 203 */ 204 class Chassis : public Node { 205 public: 206 /* 207 * Default Constructor 208 */ 209 template <typename CrowApp> 210 Chassis(CrowApp &app) 211 : Node(app, "/redfish/v1/Chassis/<str>/", std::string()) { 212 Node::json["@odata.type"] = "#Chassis.v1_4_0.Chassis"; 213 Node::json["@odata.id"] = "/redfish/v1/Chassis"; 214 Node::json["@odata.context"] = "/redfish/v1/$metadata#Chassis.Chassis"; 215 Node::json["Name"] = "Chassis Collection"; 216 Node::json["ChassisType"] = "RackMount"; 217 218 entityPrivileges = {{crow::HTTPMethod::GET, {{"Login"}}}, 219 {crow::HTTPMethod::HEAD, {{"Login"}}}, 220 {crow::HTTPMethod::PATCH, {{"ConfigureComponents"}}}, 221 {crow::HTTPMethod::PUT, {{"ConfigureComponents"}}}, 222 {crow::HTTPMethod::DELETE, {{"ConfigureComponents"}}}, 223 {crow::HTTPMethod::POST, {{"ConfigureComponents"}}}}; 224 } 225 226 private: 227 /** 228 * Functions triggers appropriate requests on DBus 229 */ 230 void doGet(crow::response &res, const crow::request &req, 231 const std::vector<std::string> ¶ms) override { 232 // Check if there is required param, truly entering this shall be 233 // impossible. 234 if (params.size() != 1) { 235 res.code = static_cast<int>(HttpRespCode::INTERNAL_ERROR); 236 res.end(); 237 return; 238 } 239 const std::string &chassis_id = params[0]; 240 // Get certain Chassis Data, and call the below callback for JSON 241 // preparation lambda requires everything as reference, and chassis_id, 242 // which is local by copy 243 chassis_provider.get_chassis_data( 244 chassis_id, 245 [&, chassis_id](const bool &success, 246 const boost::container::flat_map<std::string, 247 std::string> &output) { 248 // Create JSON copy based on Node::json, this is to avoid possible 249 // race condition 250 nlohmann::json json_response(Node::json); 251 // If success... 252 if (success) { 253 // prepare all the schema required fields. 254 json_response["@odata.id"] = "/redfish/v1/Chassis/" + chassis_id; 255 // also the one from dbus 256 for (const std::pair<std::string, std::string> &chassis_item : 257 output) { 258 json_response[chassis_item.first] = chassis_item.second; 259 } 260 261 json_response["Id"] = chassis_id; 262 // prepare respond, and send 263 res.json_value = json_response; 264 } else { 265 // ... otherwise return error 266 // TODO(Pawel)consider distinguish between non existing object, and 267 // other errors 268 res.code = static_cast<int>(HttpRespCode::NOT_FOUND); 269 } 270 res.end(); 271 }); 272 } 273 274 // Chassis Provider object 275 // TODO(Pawel) consider move it to singleton 276 OnDemandChassisProvider chassis_provider; 277 }; 278 279 } // namespace redfish 280