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