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