xref: /openbmc/phosphor-fan-presence/control/json/actions/timer_based_actions.cpp (revision 5e15c3ba7e863d57a38fa44b343d625fb26da9ca)
1 /**
2  * Copyright © 2021 IBM 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 #include "timer_based_actions.hpp"
17 
18 #include "../manager.hpp"
19 #include "action.hpp"
20 #include "event.hpp"
21 #include "group.hpp"
22 #include "sdbusplus.hpp"
23 #include "sdeventplus.hpp"
24 #include "zone.hpp"
25 
26 #include <nlohmann/json.hpp>
27 
28 #include <algorithm>
29 #include <chrono>
30 #include <format>
31 
32 namespace phosphor::fan::control::json
33 {
34 
35 using json = nlohmann::json;
36 
37 TimerBasedActions::TimerBasedActions(const json& jsonObj,
38                                      const std::vector<Group>& groups) :
39     ActionBase(jsonObj, groups),
40     _timer(util::SDEventPlus::getEvent(),
41            std::bind(&TimerBasedActions::timerExpired, this))
42 {
43     // If any of groups' value == nullopt(i.e. not configured), action is
44     // driven by the service owned state of the group members
45     _byOwner = std::any_of(
46         _groups.begin(), _groups.end(),
47         [](const auto& group) { return group.getValue() == std::nullopt; });
48 
49     setTimerConf(jsonObj);
50     setActions(jsonObj);
51 }
52 
53 void TimerBasedActions::run(Zone& zone)
54 {
55     if (_byOwner)
56     {
57         // If any service providing a group member is not owned, start
58         // timer and if all members' services are owned, stop timer.
59         if (std::any_of(_groups.begin(), _groups.end(), [](const auto& group) {
60             const auto& members = group.getMembers();
61             return std::any_of(members.begin(), members.end(),
62                                [&group](const auto& member) {
63                 return !Manager::hasOwner(member, group.getInterface());
64             });
65         }))
66         {
67             startTimer();
68         }
69         else
70         {
71             stopTimer();
72         }
73     }
74     else
75     {
76         auto* mgr = zone.getManager();
77         // If all group members have a given value and it matches what's
78         // in the cache, start timer and if any do not match, stop
79         // timer.
80         if (std::all_of(_groups.begin(), _groups.end(),
81                         [&mgr](const auto& group) {
82             const auto& members = group.getMembers();
83             return std::all_of(members.begin(), members.end(),
84                                [&mgr, &group](const auto& member) {
85                 return group.getValue() ==
86                        mgr->getProperty(member, group.getInterface(),
87                                         group.getProperty());
88             });
89         }))
90         {
91             // Timer will be started(and never stopped) when _groups is empty
92             startTimer();
93         }
94         else
95         {
96             stopTimer();
97         }
98     }
99 }
100 
101 void TimerBasedActions::startTimer()
102 {
103     if (!_timer.isEnabled())
104     {
105         if (_type == TimerType::repeating)
106         {
107             _timer.restart(_interval);
108         }
109         else if (_type == TimerType::oneshot)
110         {
111             _timer.restartOnce(_interval);
112         }
113     }
114 }
115 
116 void TimerBasedActions::stopTimer()
117 {
118     if (_timer.isEnabled())
119     {
120         _timer.setEnabled(false);
121     }
122     else
123     {
124         // Perform the actions in case state changed after the configured time
125         std::for_each(_actions.begin(), _actions.end(),
126                       [](auto& action) { action->run(); });
127     }
128 }
129 
130 void TimerBasedActions::timerExpired()
131 {
132     // Perform the actions
133     std::for_each(_actions.begin(), _actions.end(),
134                   [](auto& action) { action->run(); });
135 }
136 
137 void TimerBasedActions::setZones(
138     std::vector<std::reference_wrapper<Zone>>& zones)
139 {
140     for (auto& zone : zones)
141     {
142         this->addZone(zone);
143         // Add zone to _actions
144         std::for_each(_actions.begin(), _actions.end(),
145                       [&zone](std::unique_ptr<ActionBase>& action) {
146             action->addZone(zone);
147         });
148     }
149 }
150 
151 void TimerBasedActions::setTimerConf(const json& jsonObj)
152 {
153     if (!jsonObj.contains("timer"))
154     {
155         throw ActionParseError{getName(), "Missing required timer entry"};
156     }
157     auto jsonTimer = jsonObj["timer"];
158     if (!jsonTimer.contains("interval") || !jsonTimer.contains("type"))
159     {
160         throw ActionParseError{
161             getName(), "Missing required timer parameters {interval, type}"};
162     }
163 
164     // Interval provided in microseconds
165     _interval = static_cast<std::chrono::microseconds>(
166         jsonTimer["interval"].get<uint64_t>());
167 
168     // Retrieve type of timer
169     auto type = jsonTimer["type"].get<std::string>();
170     if (type == "oneshot")
171     {
172         _type = TimerType::oneshot;
173     }
174     else if (type == "repeating")
175     {
176         _type = TimerType::repeating;
177     }
178     else
179     {
180         throw ActionParseError{
181             getName(), std::format("Timer type '{}' is not supported", type)};
182     }
183 }
184 
185 void TimerBasedActions::setActions(const json& jsonObj)
186 {
187     if (!jsonObj.contains("actions"))
188     {
189         throw ActionParseError{getName(), "Missing required actions entry"};
190     }
191     for (const auto& jsonAct : jsonObj["actions"])
192     {
193         if (!jsonAct.contains("name"))
194         {
195             throw ActionParseError{getName(), "Missing required action name"};
196         }
197 
198         // Get any configured profile restrictions on the action
199         std::vector<std::string> profiles;
200         if (jsonAct.contains("profiles"))
201         {
202             for (const auto& profile : jsonAct["profiles"])
203             {
204                 profiles.emplace_back(profile.get<std::string>());
205             }
206         }
207 
208         // Set the groups configured for each action run when the timer expires
209         std::vector<Group> groups;
210         Event::setGroups(jsonAct, profiles, groups);
211 
212         // List of zones is set on these actions by overriden setZones()
213         auto actObj = ActionFactory::getAction(
214             jsonAct["name"].get<std::string>(), jsonAct, std::move(groups), {});
215         if (actObj)
216         {
217             _actions.emplace_back(std::move(actObj));
218         }
219     }
220 }
221 
222 } // namespace phosphor::fan::control::json
223