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