1 /**
2  * Copyright © 2020 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 "zone.hpp"
17 
18 #include "../zone.hpp"
19 #include "functor.hpp"
20 #include "handlers.hpp"
21 #include "types.hpp"
22 
23 #include <nlohmann/json.hpp>
24 #include <phosphor-logging/log.hpp>
25 #include <sdbusplus/bus.hpp>
26 
27 #include <iterator>
28 #include <map>
29 #include <numeric>
30 #include <utility>
31 #include <vector>
32 
33 namespace phosphor::fan::control::json
34 {
35 
36 using json = nlohmann::json;
37 using namespace phosphor::logging;
38 
39 const std::map<std::string, std::map<std::string, propHandler>>
40     Zone::_intfPropHandlers = {{thermModeIntf,
41                                 {{supportedProp, zone::property::supported},
42                                  {currentProp, zone::property::current}}}};
43 
44 Zone::Zone(sdbusplus::bus::bus& bus, const json& jsonObj) :
45     ConfigBase(jsonObj), _incDelay(0)
46 {
47     if (jsonObj.contains("profiles"))
48     {
49         for (const auto& profile : jsonObj["profiles"])
50         {
51             _profiles.emplace_back(profile.get<std::string>());
52         }
53     }
54     // Speed increase delay is optional, defaults to 0
55     if (jsonObj.contains("increase_delay"))
56     {
57         _incDelay = jsonObj["increase_delay"].get<uint64_t>();
58     }
59     setFullSpeed(jsonObj);
60     setDefaultFloor(jsonObj);
61     setDecInterval(jsonObj);
62     // Setting properties on interfaces to be served are optional
63     if (jsonObj.contains("interfaces"))
64     {
65         setInterfaces(jsonObj);
66     }
67 }
68 
69 void Zone::setFullSpeed(const json& jsonObj)
70 {
71     if (!jsonObj.contains("full_speed"))
72     {
73         log<level::ERR>("Missing required zone's full speed",
74                         entry("JSON=%s", jsonObj.dump().c_str()));
75         throw std::runtime_error("Missing required zone's full speed");
76     }
77     _fullSpeed = jsonObj["full_speed"].get<uint64_t>();
78 }
79 
80 void Zone::setDefaultFloor(const json& jsonObj)
81 {
82     if (!jsonObj.contains("default_floor"))
83     {
84         log<level::ERR>("Missing required zone's default floor speed",
85                         entry("JSON=%s", jsonObj.dump().c_str()));
86         throw std::runtime_error("Missing required zone's default floor speed");
87     }
88     _defaultFloor = jsonObj["default_floor"].get<uint64_t>();
89 }
90 
91 void Zone::setDecInterval(const json& jsonObj)
92 {
93     if (!jsonObj.contains("decrease_interval"))
94     {
95         log<level::ERR>("Missing required zone's decrease interval",
96                         entry("JSON=%s", jsonObj.dump().c_str()));
97         throw std::runtime_error("Missing required zone's decrease interval");
98     }
99     _decInterval = jsonObj["decrease_interval"].get<uint64_t>();
100 }
101 
102 void Zone::setInterfaces(const json& jsonObj)
103 {
104     for (const auto& interface : jsonObj["interfaces"])
105     {
106         if (!interface.contains("name") || !interface.contains("properties"))
107         {
108             log<level::ERR>("Missing required zone interface attributes",
109                             entry("JSON=%s", interface.dump().c_str()));
110             throw std::runtime_error(
111                 "Missing required zone interface attributes");
112         }
113         auto propFuncs =
114             _intfPropHandlers.find(interface["name"].get<std::string>());
115         if (propFuncs == _intfPropHandlers.end())
116         {
117             // Construct list of available configurable interfaces
118             auto intfs = std::accumulate(
119                 std::next(_intfPropHandlers.begin()), _intfPropHandlers.end(),
120                 _intfPropHandlers.begin()->first, [](auto list, auto intf) {
121                     return std::move(list) + ", " + intf.first;
122                 });
123             log<level::ERR>("Configured interface not available",
124                             entry("JSON=%s", interface.dump().c_str()),
125                             entry("AVAILABLE_INTFS=%s", intfs.c_str()));
126             throw std::runtime_error("Configured interface not available");
127         }
128 
129         for (const auto& property : interface["properties"])
130         {
131             if (!property.contains("name"))
132             {
133                 log<level::ERR>(
134                     "Missing required interface property attributes",
135                     entry("JSON=%s", property.dump().c_str()));
136                 throw std::runtime_error(
137                     "Missing required interface property attributes");
138             }
139             // Attribute "persist" is optional, defaults to `false`
140             auto persist = false;
141             if (property.contains("persist"))
142             {
143                 persist = property["persist"].get<bool>();
144             }
145             // Property name from JSON must exactly match supported
146             // index names to functions in property namespace
147             auto propFunc =
148                 propFuncs->second.find(property["name"].get<std::string>());
149             if (propFunc == propFuncs->second.end())
150             {
151                 // Construct list of available configurable properties
152                 auto props = std::accumulate(
153                     std::next(propFuncs->second.begin()),
154                     propFuncs->second.end(), propFuncs->second.begin()->first,
155                     [](auto list, auto prop) {
156                         return std::move(list) + ", " + prop.first;
157                     });
158                 log<level::ERR>("Configured property not available",
159                                 entry("JSON=%s", property.dump().c_str()),
160                                 entry("AVAILABLE_PROPS=%s", props.c_str()));
161                 throw std::runtime_error(
162                     "Configured property function not available");
163             }
164             auto zHandler = propFunc->second(property, persist);
165             // Only add non-null zone handler functions
166             if (zHandler)
167             {
168                 _zoneHandlers.emplace_back(zHandler);
169             }
170         }
171     }
172 }
173 
174 /**
175  * Properties of interfaces supported by the zone configuration that return
176  * a ZoneHandler function that sets the zone's property value(s).
177  */
178 namespace zone::property
179 {
180 // Get a zone handler function for the configured values of the "Supported"
181 // property
182 ZoneHandler supported(const json& jsonObj, bool persist)
183 {
184     std::vector<std::string> values;
185     if (!jsonObj.contains("values"))
186     {
187         log<level::ERR>(
188             "No 'values' found for \"Supported\" property, using an empty list",
189             entry("JSON=%s", jsonObj.dump().c_str()));
190     }
191     else
192     {
193         for (const auto& value : jsonObj["values"])
194         {
195             if (!value.contains("value"))
196             {
197                 log<level::ERR>("No 'value' found for \"Supported\" property "
198                                 "entry, skipping",
199                                 entry("JSON=%s", value.dump().c_str()));
200             }
201             else
202             {
203                 values.emplace_back(value["value"].get<std::string>());
204             }
205         }
206     }
207 
208     return make_zoneHandler(handler::setZoneProperty<std::vector<std::string>>(
209         Zone::thermModeIntf, Zone::supportedProp, &control::Zone::supported,
210         std::move(values), persist));
211 }
212 
213 // Get a zone handler function for a configured value of the "Current"
214 // property
215 ZoneHandler current(const json& jsonObj, bool persist)
216 {
217     // Use default value for "Current" property if no "value" entry given
218     if (!jsonObj.contains("value"))
219     {
220         log<level::ERR>("No 'value' found for \"Current\" property, "
221                         "using default",
222                         entry("JSON=%s", jsonObj.dump().c_str()));
223         return {};
224     }
225 
226     return make_zoneHandler(handler::setZoneProperty<std::string>(
227         Zone::thermModeIntf, Zone::currentProp, &control::Zone::current,
228         jsonObj["value"].get<std::string>(), persist));
229 }
230 } // namespace zone::property
231 
232 } // namespace phosphor::fan::control::json
233