xref: /openbmc/phosphor-inventory-manager/association_manager.cpp (revision d8fba8bec76389c4a21d000fb17b07a9cea3bf0c)
1852db67bSMatt Spinler #include "association_manager.hpp"
2852db67bSMatt Spinler 
3cf5d024bSGeorge Liu #include <phosphor-logging/lg2.hpp>
499e66a03SMatt Spinler 
5a83db30eSBrad Bishop #include <filesystem>
6a83db30eSBrad Bishop #include <fstream>
7a83db30eSBrad Bishop 
8852db67bSMatt Spinler namespace phosphor
9852db67bSMatt Spinler {
10852db67bSMatt Spinler namespace inventory
11852db67bSMatt Spinler {
12852db67bSMatt Spinler namespace manager
13852db67bSMatt Spinler {
14852db67bSMatt Spinler namespace associations
15852db67bSMatt Spinler {
1659521e87SMatt Spinler namespace fs = std::filesystem;
17852db67bSMatt Spinler 
Manager(sdbusplus::bus_t & bus,const std::string & jsonPath)18563306f6SPatrick Williams Manager::Manager(sdbusplus::bus_t& bus, const std::string& jsonPath) :
19852db67bSMatt Spinler     _bus(bus), _jsonFile(jsonPath)
20852db67bSMatt Spinler {
2159521e87SMatt Spinler     // If there aren't any conditional associations files, look for
2259521e87SMatt Spinler     // that default nonconditional one.
2359521e87SMatt Spinler     if (!loadConditions())
2459521e87SMatt Spinler     {
2559521e87SMatt Spinler         if (fs::exists(_jsonFile))
2659521e87SMatt Spinler         {
2759521e87SMatt Spinler             std::ifstream file{_jsonFile};
2859521e87SMatt Spinler             auto json = nlohmann::json::parse(file, nullptr, true);
2959521e87SMatt Spinler             load(json);
3059521e87SMatt Spinler         }
3159521e87SMatt Spinler     }
3299e66a03SMatt Spinler }
3399e66a03SMatt Spinler 
3499e66a03SMatt Spinler /**
3599e66a03SMatt Spinler  * @brief Throws an exception if 'num' is zero. Used for JSON
3699e66a03SMatt Spinler  *        sanity checking.
3799e66a03SMatt Spinler  *
3899e66a03SMatt Spinler  * @param[in] num - the number to check
3999e66a03SMatt Spinler  */
throwIfZero(int num)4099e66a03SMatt Spinler void throwIfZero(int num)
4199e66a03SMatt Spinler {
4299e66a03SMatt Spinler     if (!num)
4399e66a03SMatt Spinler     {
4499e66a03SMatt Spinler         throw std::invalid_argument("Invalid empty field in JSON");
4599e66a03SMatt Spinler     }
4699e66a03SMatt Spinler }
4799e66a03SMatt Spinler 
loadConditions()4859521e87SMatt Spinler bool Manager::loadConditions()
4999e66a03SMatt Spinler {
5059521e87SMatt Spinler     auto dir = _jsonFile.parent_path();
5199e66a03SMatt Spinler 
5259521e87SMatt Spinler     for (const auto& dirent : fs::recursive_directory_iterator(dir))
5359521e87SMatt Spinler     {
5459521e87SMatt Spinler         const auto& path = dirent.path();
5559521e87SMatt Spinler         if (path.extension() == ".json")
5659521e87SMatt Spinler         {
5759521e87SMatt Spinler             std::ifstream file{path};
5899e66a03SMatt Spinler             auto json = nlohmann::json::parse(file, nullptr, true);
5999e66a03SMatt Spinler 
6059521e87SMatt Spinler             if (json.is_object() && json.contains("condition"))
6159521e87SMatt Spinler             {
6259521e87SMatt Spinler                 const auto& conditionJSON = json.at("condition");
6359521e87SMatt Spinler                 if (!conditionJSON.contains("path") ||
6459521e87SMatt Spinler                     !conditionJSON.contains("interface") ||
6559521e87SMatt Spinler                     !conditionJSON.contains("property") ||
6659521e87SMatt Spinler                     !conditionJSON.contains("values"))
6759521e87SMatt Spinler                 {
68cf5d024bSGeorge Liu                     lg2::error(
69cf5d024bSGeorge Liu                         "Invalid JSON in associations condition entry in {PATH}. Skipping file.",
70cf5d024bSGeorge Liu                         "PATH", path);
7159521e87SMatt Spinler                     continue;
7259521e87SMatt Spinler                 }
7359521e87SMatt Spinler 
7459521e87SMatt Spinler                 Condition c;
7559521e87SMatt Spinler                 c.file = path;
7659521e87SMatt Spinler                 c.path = conditionJSON["path"].get<std::string>();
7759521e87SMatt Spinler                 if (c.path.front() != '/')
7859521e87SMatt Spinler                 {
7959521e87SMatt Spinler                     c.path = '/' + c.path;
8059521e87SMatt Spinler                 }
8159521e87SMatt Spinler                 fprintf(stderr, "found conditions file %s\n", c.file.c_str());
8259521e87SMatt Spinler                 c.interface = conditionJSON["interface"].get<std::string>();
8359521e87SMatt Spinler                 c.property = conditionJSON["property"].get<std::string>();
8459521e87SMatt Spinler 
8559521e87SMatt Spinler                 // The values are in an array, and need to be
8659521e87SMatt Spinler                 // converted to an InterfaceVariantType.
8759521e87SMatt Spinler                 for (const auto& value : conditionJSON["values"])
8859521e87SMatt Spinler                 {
8959521e87SMatt Spinler                     if (value.is_array())
9059521e87SMatt Spinler                     {
9159521e87SMatt Spinler                         std::vector<uint8_t> variantValue;
9259521e87SMatt Spinler                         for (const auto& v : value)
9359521e87SMatt Spinler                         {
9459521e87SMatt Spinler                             variantValue.push_back(v.get<uint8_t>());
9559521e87SMatt Spinler                         }
9659521e87SMatt Spinler                         c.values.push_back(variantValue);
9759521e87SMatt Spinler                         continue;
9859521e87SMatt Spinler                     }
9959521e87SMatt Spinler 
10059521e87SMatt Spinler                     // Try the remaining types
10159521e87SMatt Spinler                     auto s = value.get_ptr<const std::string*>();
10259521e87SMatt Spinler                     auto i = value.get_ptr<const int64_t*>();
10359521e87SMatt Spinler                     auto b = value.get_ptr<const bool*>();
10459521e87SMatt Spinler                     if (s)
10559521e87SMatt Spinler                     {
10659521e87SMatt Spinler                         c.values.push_back(*s);
10759521e87SMatt Spinler                     }
10859521e87SMatt Spinler                     else if (i)
10959521e87SMatt Spinler                     {
11059521e87SMatt Spinler                         c.values.push_back(*i);
11159521e87SMatt Spinler                     }
11259521e87SMatt Spinler                     else if (b)
11359521e87SMatt Spinler                     {
11459521e87SMatt Spinler                         c.values.push_back(*b);
11559521e87SMatt Spinler                     }
11659521e87SMatt Spinler                     else
11759521e87SMatt Spinler                     {
118cf5d024bSGeorge Liu                         lg2::error(
119cf5d024bSGeorge Liu                             "Invalid condition property value in {FILE}:",
120cf5d024bSGeorge Liu                             "FILE", c.file);
121cf5d024bSGeorge Liu                         throw std::runtime_error(
122cf5d024bSGeorge Liu                             "Invalid condition property value");
12359521e87SMatt Spinler                     }
12459521e87SMatt Spinler                 }
12559521e87SMatt Spinler 
12659521e87SMatt Spinler                 _conditions.push_back(std::move(c));
12759521e87SMatt Spinler             }
12859521e87SMatt Spinler         }
12959521e87SMatt Spinler     }
13059521e87SMatt Spinler 
13159521e87SMatt Spinler     return !_conditions.empty();
13259521e87SMatt Spinler }
13359521e87SMatt Spinler 
conditionMatch(const sdbusplus::message::object_path & objectPath,const Object & object)13459521e87SMatt Spinler bool Manager::conditionMatch(const sdbusplus::message::object_path& objectPath,
13559521e87SMatt Spinler                              const Object& object)
13659521e87SMatt Spinler {
13759521e87SMatt Spinler     fs::path foundPath;
13859521e87SMatt Spinler     for (const auto& condition : _conditions)
13959521e87SMatt Spinler     {
14059521e87SMatt Spinler         if (condition.path != objectPath)
14159521e87SMatt Spinler         {
14259521e87SMatt Spinler             continue;
14359521e87SMatt Spinler         }
14459521e87SMatt Spinler 
145*d8fba8beSPatrick Williams         auto interface = std::find_if(
146*d8fba8beSPatrick Williams             object.begin(), object.end(), [&condition](const auto& i) {
14759521e87SMatt Spinler                 return i.first == condition.interface;
14859521e87SMatt Spinler             });
14959521e87SMatt Spinler         if (interface == object.end())
15059521e87SMatt Spinler         {
15159521e87SMatt Spinler             continue;
15259521e87SMatt Spinler         }
15359521e87SMatt Spinler 
154*d8fba8beSPatrick Williams         auto property =
155*d8fba8beSPatrick Williams             std::find_if(interface->second.begin(), interface->second.end(),
15659521e87SMatt Spinler                          [&condition](const auto& p) {
15759521e87SMatt Spinler                              return condition.property == p.first;
15859521e87SMatt Spinler                          });
15959521e87SMatt Spinler         if (property == interface->second.end())
16059521e87SMatt Spinler         {
16159521e87SMatt Spinler             continue;
16259521e87SMatt Spinler         }
16359521e87SMatt Spinler 
16459521e87SMatt Spinler         auto match = std::find(condition.values.begin(), condition.values.end(),
16559521e87SMatt Spinler                                property->second);
16659521e87SMatt Spinler         if (match != condition.values.end())
16759521e87SMatt Spinler         {
16859521e87SMatt Spinler             foundPath = condition.file;
16959521e87SMatt Spinler             break;
17059521e87SMatt Spinler         }
17159521e87SMatt Spinler     }
17259521e87SMatt Spinler 
17359521e87SMatt Spinler     if (!foundPath.empty())
17459521e87SMatt Spinler     {
17559521e87SMatt Spinler         std::ifstream file{foundPath};
17659521e87SMatt Spinler         auto json = nlohmann::json::parse(file, nullptr, true);
17759521e87SMatt Spinler         load(json["associations"]);
17859521e87SMatt Spinler         _conditions.clear();
17959521e87SMatt Spinler         return true;
18059521e87SMatt Spinler     }
18159521e87SMatt Spinler 
18259521e87SMatt Spinler     return false;
18359521e87SMatt Spinler }
18459521e87SMatt Spinler 
conditionMatch()18559521e87SMatt Spinler bool Manager::conditionMatch()
18659521e87SMatt Spinler {
18759521e87SMatt Spinler     fs::path foundPath;
18859521e87SMatt Spinler 
18959521e87SMatt Spinler     for (const auto& condition : _conditions)
19059521e87SMatt Spinler     {
19159521e87SMatt Spinler         // Compare the actualValue field against the values in the
19259521e87SMatt Spinler         // values vector to see if there is a condition match.
19359521e87SMatt Spinler         auto found = std::find(condition.values.begin(), condition.values.end(),
19459521e87SMatt Spinler                                condition.actualValue);
19559521e87SMatt Spinler         if (found != condition.values.end())
19659521e87SMatt Spinler         {
19759521e87SMatt Spinler             foundPath = condition.file;
19859521e87SMatt Spinler             break;
19959521e87SMatt Spinler         }
20059521e87SMatt Spinler     }
20159521e87SMatt Spinler 
20259521e87SMatt Spinler     if (!foundPath.empty())
20359521e87SMatt Spinler     {
20459521e87SMatt Spinler         std::ifstream file{foundPath};
20559521e87SMatt Spinler         auto json = nlohmann::json::parse(file, nullptr, true);
20659521e87SMatt Spinler         load(json["associations"]);
20759521e87SMatt Spinler         _conditions.clear();
20859521e87SMatt Spinler         return true;
20959521e87SMatt Spinler     }
21059521e87SMatt Spinler 
21159521e87SMatt Spinler     return false;
21259521e87SMatt Spinler }
21359521e87SMatt Spinler 
load(const nlohmann::json & json)21459521e87SMatt Spinler void Manager::load(const nlohmann::json& json)
21559521e87SMatt Spinler {
21699e66a03SMatt Spinler     const std::string root{INVENTORY_ROOT};
21799e66a03SMatt Spinler 
21899e66a03SMatt Spinler     for (const auto& jsonAssoc : json)
21999e66a03SMatt Spinler     {
22099e66a03SMatt Spinler         // Only add the slash if necessary
22199e66a03SMatt Spinler         std::string path = jsonAssoc.at("path");
22299e66a03SMatt Spinler         throwIfZero(path.size());
22399e66a03SMatt Spinler         if (path.front() != '/')
22499e66a03SMatt Spinler         {
22599e66a03SMatt Spinler             path = root + "/" + path;
22699e66a03SMatt Spinler         }
22799e66a03SMatt Spinler         else
22899e66a03SMatt Spinler         {
22999e66a03SMatt Spinler             path = root + path;
23099e66a03SMatt Spinler         }
23199e66a03SMatt Spinler 
23299e66a03SMatt Spinler         auto& assocEndpoints = _associations[path];
23399e66a03SMatt Spinler 
23499e66a03SMatt Spinler         for (const auto& endpoint : jsonAssoc.at("endpoints"))
23599e66a03SMatt Spinler         {
23699e66a03SMatt Spinler             std::string ftype = endpoint.at("types").at("fType");
23799e66a03SMatt Spinler             std::string rtype = endpoint.at("types").at("rType");
23899e66a03SMatt Spinler             throwIfZero(ftype.size());
23999e66a03SMatt Spinler             throwIfZero(rtype.size());
24099e66a03SMatt Spinler             Types types{std::move(ftype), std::move(rtype)};
24199e66a03SMatt Spinler 
24299e66a03SMatt Spinler             Paths paths = endpoint.at("paths");
24399e66a03SMatt Spinler             throwIfZero(paths.size());
24499e66a03SMatt Spinler             assocEndpoints.emplace_back(std::move(types), std::move(paths));
24599e66a03SMatt Spinler         }
24699e66a03SMatt Spinler     }
247852db67bSMatt Spinler }
248852db67bSMatt Spinler 
createAssociations(const std::string & objectPath,bool deferSignal)249104ccba9SBrad Bishop void Manager::createAssociations(const std::string& objectPath,
250104ccba9SBrad Bishop                                  bool deferSignal)
251852db67bSMatt Spinler {
252c47ca585SMatt Spinler     auto endpoints = _associations.find(objectPath);
253c47ca585SMatt Spinler     if (endpoints == _associations.end())
254c47ca585SMatt Spinler     {
255c47ca585SMatt Spinler         return;
256c47ca585SMatt Spinler     }
257c47ca585SMatt Spinler 
258c47ca585SMatt Spinler     if (std::find(_handled.begin(), _handled.end(), objectPath) !=
259c47ca585SMatt Spinler         _handled.end())
260c47ca585SMatt Spinler     {
261c47ca585SMatt Spinler         return;
262c47ca585SMatt Spinler     }
263c47ca585SMatt Spinler 
264c47ca585SMatt Spinler     _handled.push_back(objectPath);
265c47ca585SMatt Spinler 
266c47ca585SMatt Spinler     for (const auto& endpoint : endpoints->second)
267c47ca585SMatt Spinler     {
268c47ca585SMatt Spinler         const auto& types = std::get<typesPos>(endpoint);
269c47ca585SMatt Spinler         const auto& paths = std::get<pathsPos>(endpoint);
270c47ca585SMatt Spinler 
271c47ca585SMatt Spinler         for (const auto& endpointPath : paths)
272c47ca585SMatt Spinler         {
273c47ca585SMatt Spinler             const auto& forwardType = std::get<forwardTypePos>(types);
274c47ca585SMatt Spinler             const auto& reverseType = std::get<reverseTypePos>(types);
275c47ca585SMatt Spinler 
276c47ca585SMatt Spinler             createAssociation(objectPath, forwardType, endpointPath,
277104ccba9SBrad Bishop                               reverseType, deferSignal);
278c47ca585SMatt Spinler         }
279c47ca585SMatt Spinler     }
280c47ca585SMatt Spinler }
281c47ca585SMatt Spinler 
createAssociation(const std::string & forwardPath,const std::string & forwardType,const std::string & reversePath,const std::string & reverseType,bool deferSignal)282*d8fba8beSPatrick Williams void Manager::createAssociation(
283*d8fba8beSPatrick Williams     const std::string& forwardPath, const std::string& forwardType,
284*d8fba8beSPatrick Williams     const std::string& reversePath, const std::string& reverseType,
285104ccba9SBrad Bishop     bool deferSignal)
286c47ca585SMatt Spinler {
287c47ca585SMatt Spinler     auto object = _associationIfaces.find(forwardPath);
288c47ca585SMatt Spinler     if (object == _associationIfaces.end())
289c47ca585SMatt Spinler     {
2901bb06235SPatrick Williams         auto a = std::make_unique<AssociationObject>(
2911bb06235SPatrick Williams             _bus, forwardPath.c_str(), AssociationObject::action::defer_emit);
292c47ca585SMatt Spinler 
293c47ca585SMatt Spinler         using AssociationProperty =
294c47ca585SMatt Spinler             std::vector<std::tuple<std::string, std::string, std::string>>;
295c47ca585SMatt Spinler         AssociationProperty prop;
296c47ca585SMatt Spinler 
297c47ca585SMatt Spinler         prop.emplace_back(forwardType, reverseType, reversePath);
298c47ca585SMatt Spinler         a->associations(std::move(prop));
299104ccba9SBrad Bishop         if (!deferSignal)
300104ccba9SBrad Bishop         {
301c47ca585SMatt Spinler             a->emit_object_added();
302104ccba9SBrad Bishop         }
303c47ca585SMatt Spinler         _associationIfaces.emplace(forwardPath, std::move(a));
304c47ca585SMatt Spinler     }
305c47ca585SMatt Spinler     else
306c47ca585SMatt Spinler     {
307c47ca585SMatt Spinler         // Interface exists, just update the property
308c47ca585SMatt Spinler         auto prop = object->second->associations();
309c47ca585SMatt Spinler         prop.emplace_back(forwardType, reverseType, reversePath);
310104ccba9SBrad Bishop         object->second->associations(std::move(prop), deferSignal);
311c47ca585SMatt Spinler     }
312852db67bSMatt Spinler }
313852db67bSMatt Spinler } // namespace associations
314852db67bSMatt Spinler } // namespace manager
315852db67bSMatt Spinler } // namespace inventory
316852db67bSMatt Spinler } // namespace phosphor
317