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