blob: 729aa564fa03296f38856502ec7655e2435ff5bb [file] [log] [blame]
Jennifer Lee729dae72018-04-24 15:59:34 -07001/*
2// Copyright (c) 2018 Intel 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#pragma once
17
Tejas Patild61e5192021-06-04 15:49:35 +053018#include "bmcweb_config.h"
19
John Edward Broadbent7e860f12021-04-08 15:57:16 -070020#include <app.hpp>
Ed Tanous168e20c2021-12-13 14:39:53 -080021#include <dbus_utility.hpp>
Ed Tanoused398212021-06-09 17:05:54 -070022#include <registries/privilege_registry.hpp>
Jonathan Doman1e1e5982021-06-11 09:36:17 -070023#include <sdbusplus/asio/property.hpp>
Andrew Geissler87d84722019-02-28 14:28:39 -060024#include <utils/fw_utils.hpp>
Gunnar Mills1214b7e2020-06-04 10:11:30 -050025
Ed Tanous1abe55e2018-09-05 08:30:59 -070026namespace redfish
27{
Ed Tanous27826b52018-10-29 11:40:58 -070028
Andrew Geissler0e7de462019-03-04 19:11:54 -060029// Match signals added on software path
Jennifer Leeacb7cfb2018-06-07 16:08:15 -070030static std::unique_ptr<sdbusplus::bus::match::match> fwUpdateMatcher;
James Feist4cde5d92020-06-11 10:39:55 -070031static std::unique_ptr<sdbusplus::bus::match::match> fwUpdateErrorMatcher;
Andrew Geissler0e7de462019-03-04 19:11:54 -060032// Only allow one update at a time
33static bool fwUpdateInProgress = false;
Andrew Geissler86adcd62019-04-18 10:58:05 -050034// Timer for software available
Ed Tanous271584a2019-07-09 16:24:22 -070035static std::unique_ptr<boost::asio::steady_timer> fwAvailableTimer;
Andrew Geissler86adcd62019-04-18 10:58:05 -050036
John Edward Broadbent7e860f12021-04-08 15:57:16 -070037inline static void cleanUp()
Andrew Geissler86adcd62019-04-18 10:58:05 -050038{
39 fwUpdateInProgress = false;
40 fwUpdateMatcher = nullptr;
James Feist4cde5d92020-06-11 10:39:55 -070041 fwUpdateErrorMatcher = nullptr;
Andrew Geissler86adcd62019-04-18 10:58:05 -050042}
John Edward Broadbent7e860f12021-04-08 15:57:16 -070043inline static void activateImage(const std::string& objPath,
44 const std::string& service)
Andrew Geissler86adcd62019-04-18 10:58:05 -050045{
46 BMCWEB_LOG_DEBUG << "Activate image for " << objPath << " " << service;
47 crow::connections::systemBus->async_method_call(
Ed Tanous81ce6092020-12-17 16:54:55 +000048 [](const boost::system::error_code errorCode) {
49 if (errorCode)
Andrew Geissler86adcd62019-04-18 10:58:05 -050050 {
Ed Tanous81ce6092020-12-17 16:54:55 +000051 BMCWEB_LOG_DEBUG << "error_code = " << errorCode;
52 BMCWEB_LOG_DEBUG << "error msg = " << errorCode.message();
Andrew Geissler86adcd62019-04-18 10:58:05 -050053 }
54 },
55 service, objPath, "org.freedesktop.DBus.Properties", "Set",
56 "xyz.openbmc_project.Software.Activation", "RequestedActivation",
Ed Tanous168e20c2021-12-13 14:39:53 -080057 dbus::utility::DbusVariantType(
George Liu0fda0f12021-11-16 10:06:17 +080058 "xyz.openbmc_project.Software.Activation.RequestedActivations.Active"));
Andrew Geissler86adcd62019-04-18 10:58:05 -050059}
Andrew Geissler0554c982019-04-23 14:40:12 -050060
61// Note that asyncResp can be either a valid pointer or nullptr. If nullptr
62// then no asyncResp updates will occur
zhanghch058d1b46d2021-04-01 11:18:24 +080063static void
64 softwareInterfaceAdded(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
65 sdbusplus::message::message& m,
Ed Tanousa3e65892021-09-16 14:13:20 -070066 task::Payload&& payload)
Andrew Geissler86adcd62019-04-18 10:58:05 -050067{
Ed Tanousb9d36b42022-02-26 21:42:46 -080068 dbus::utility::DBusInteracesMap interfacesProperties;
Andrew Geissler86adcd62019-04-18 10:58:05 -050069
70 sdbusplus::message::object_path objPath;
71
72 m.read(objPath, interfacesProperties);
73
74 BMCWEB_LOG_DEBUG << "obj path = " << objPath.str;
Gunnar Mills1214b7e2020-06-04 10:11:30 -050075 for (auto& interface : interfacesProperties)
Andrew Geissler86adcd62019-04-18 10:58:05 -050076 {
77 BMCWEB_LOG_DEBUG << "interface = " << interface.first;
78
79 if (interface.first == "xyz.openbmc_project.Software.Activation")
80 {
Andrew Geissler86adcd62019-04-18 10:58:05 -050081 // Retrieve service and activate
82 crow::connections::systemBus->async_method_call(
Ed Tanousa3e65892021-09-16 14:13:20 -070083 [objPath, asyncResp, payload(std::move(payload))](
84 const boost::system::error_code errorCode,
85 const std::vector<
86 std::pair<std::string, std::vector<std::string>>>&
87 objInfo) mutable {
Ed Tanous81ce6092020-12-17 16:54:55 +000088 if (errorCode)
Andrew Geissler86adcd62019-04-18 10:58:05 -050089 {
Ed Tanous81ce6092020-12-17 16:54:55 +000090 BMCWEB_LOG_DEBUG << "error_code = " << errorCode;
Andrew Geissler86adcd62019-04-18 10:58:05 -050091 BMCWEB_LOG_DEBUG << "error msg = "
Ed Tanous81ce6092020-12-17 16:54:55 +000092 << errorCode.message();
Andrew Geissler0554c982019-04-23 14:40:12 -050093 if (asyncResp)
94 {
95 messages::internalError(asyncResp->res);
96 }
Andrew Geissler86adcd62019-04-18 10:58:05 -050097 cleanUp();
98 return;
99 }
100 // Ensure we only got one service back
101 if (objInfo.size() != 1)
102 {
103 BMCWEB_LOG_ERROR << "Invalid Object Size "
104 << objInfo.size();
Andrew Geissler0554c982019-04-23 14:40:12 -0500105 if (asyncResp)
106 {
107 messages::internalError(asyncResp->res);
108 }
Andrew Geissler86adcd62019-04-18 10:58:05 -0500109 cleanUp();
110 return;
111 }
112 // cancel timer only when
113 // xyz.openbmc_project.Software.Activation interface
114 // is added
115 fwAvailableTimer = nullptr;
116
117 activateImage(objPath.str, objInfo[0].first);
Andrew Geissler0554c982019-04-23 14:40:12 -0500118 if (asyncResp)
119 {
James Feist32898ce2020-03-10 16:16:52 -0700120 std::shared_ptr<task::TaskData> task =
121 task::TaskData::createTask(
122 [](boost::system::error_code ec,
Gunnar Mills1214b7e2020-06-04 10:11:30 -0500123 sdbusplus::message::message& msg,
124 const std::shared_ptr<task::TaskData>&
125 taskData) {
James Feist32898ce2020-03-10 16:16:52 -0700126 if (ec)
127 {
128 return task::completed;
129 }
130
131 std::string iface;
Ed Tanousb9d36b42022-02-26 21:42:46 -0800132 dbus::utility::DBusPropertiesMap values;
James Feist32898ce2020-03-10 16:16:52 -0700133
James Feiste5d50062020-05-11 17:29:00 -0700134 std::string index =
135 std::to_string(taskData->index);
James Feistfd9ab9e2020-05-19 13:48:07 -0700136 msg.read(iface, values);
James Feiste5d50062020-05-11 17:29:00 -0700137
George Liu0fda0f12021-11-16 10:06:17 +0800138 if (iface ==
139 "xyz.openbmc_project.Software.Activation")
James Feist32898ce2020-03-10 16:16:52 -0700140 {
Ed Tanousb9d36b42022-02-26 21:42:46 -0800141 std::string* state = nullptr;
142 for (const auto& property : values)
James Feistfd9ab9e2020-05-19 13:48:07 -0700143 {
Ed Tanousb9d36b42022-02-26 21:42:46 -0800144 if (property.first == "Activation")
145 {
146 const std::string* state =
147 std::get_if<std::string>(
148 &property.second);
149 if (state == nullptr)
150 {
151 taskData->messages
152 .emplace_back(
153 messages::
154 internalError());
155 return task::completed;
156 }
157 }
James Feistfd9ab9e2020-05-19 13:48:07 -0700158 }
James Feistfd9ab9e2020-05-19 13:48:07 -0700159
160 if (state == nullptr)
161 {
Ed Tanousb9d36b42022-02-26 21:42:46 -0800162 return !task::completed;
James Feistfd9ab9e2020-05-19 13:48:07 -0700163 }
164
165 if (boost::ends_with(*state,
166 "Invalid") ||
167 boost::ends_with(*state, "Failed"))
168 {
169 taskData->state = "Exception";
170 taskData->status = "Warning";
171 taskData->messages.emplace_back(
172 messages::taskAborted(index));
173 return task::completed;
174 }
175
176 if (boost::ends_with(*state, "Staged"))
177 {
178 taskData->state = "Stopping";
179 taskData->messages.emplace_back(
180 messages::taskPaused(index));
181
182 // its staged, set a long timer to
183 // allow them time to complete the
184 // update (probably cycle the
185 // system) if this expires then
186 // task will be cancelled
187 taskData->extendTimer(
188 std::chrono::hours(5));
189 return !task::completed;
190 }
191
192 if (boost::ends_with(*state, "Active"))
193 {
194 taskData->messages.emplace_back(
195 messages::taskCompletedOK(
196 index));
197 taskData->state = "Completed";
198 return task::completed;
199 }
James Feist32898ce2020-03-10 16:16:52 -0700200 }
George Liu0fda0f12021-11-16 10:06:17 +0800201 else if (
202 iface ==
203 "xyz.openbmc_project.Software.ActivationProgress")
James Feist32898ce2020-03-10 16:16:52 -0700204 {
Ed Tanousb9d36b42022-02-26 21:42:46 -0800205
206 const uint8_t* progress = nullptr;
207 for (const auto& property : values)
James Feistfd9ab9e2020-05-19 13:48:07 -0700208 {
Ed Tanousb9d36b42022-02-26 21:42:46 -0800209 if (property.first == "Progress")
210 {
211 const std::string* progress =
212 std::get_if<std::string>(
213 &property.second);
214 if (progress == nullptr)
215 {
216 taskData->messages
217 .emplace_back(
218 messages::
219 internalError());
220 return task::completed;
221 }
222 }
James Feistfd9ab9e2020-05-19 13:48:07 -0700223 }
James Feist32898ce2020-03-10 16:16:52 -0700224
James Feistfd9ab9e2020-05-19 13:48:07 -0700225 if (progress == nullptr)
226 {
Ed Tanousb9d36b42022-02-26 21:42:46 -0800227 return !task::completed;
James Feistfd9ab9e2020-05-19 13:48:07 -0700228 }
George Liu6868ff52021-01-02 11:37:41 +0800229 taskData->percentComplete =
230 static_cast<int>(*progress);
James Feist32898ce2020-03-10 16:16:52 -0700231 taskData->messages.emplace_back(
James Feistfd9ab9e2020-05-19 13:48:07 -0700232 messages::taskProgressChanged(
233 index, static_cast<size_t>(
234 *progress)));
235
236 // if we're getting status updates it's
237 // still alive, update timer
238 taskData->extendTimer(
239 std::chrono::minutes(5));
James Feist32898ce2020-03-10 16:16:52 -0700240 }
241
242 // as firmware update often results in a
243 // reboot, the task may never "complete"
244 // unless it is an error
245
246 return !task::completed;
247 },
George Liu0fda0f12021-11-16 10:06:17 +0800248 "type='signal',interface='org.freedesktop.DBus.Properties',"
James Feistfd9ab9e2020-05-19 13:48:07 -0700249 "member='PropertiesChanged',path='" +
James Feist32898ce2020-03-10 16:16:52 -0700250 objPath.str + "'");
251 task->startTimer(std::chrono::minutes(5));
252 task->populateResp(asyncResp->res);
Ed Tanousa3e65892021-09-16 14:13:20 -0700253 task->payload.emplace(std::move(payload));
Andrew Geissler0554c982019-04-23 14:40:12 -0500254 }
Andrew Geissler86adcd62019-04-18 10:58:05 -0500255 fwUpdateInProgress = false;
256 },
257 "xyz.openbmc_project.ObjectMapper",
258 "/xyz/openbmc_project/object_mapper",
259 "xyz.openbmc_project.ObjectMapper", "GetObject", objPath.str,
Gunnar Mills1214b7e2020-06-04 10:11:30 -0500260 std::array<const char*, 1>{
Andrew Geissler86adcd62019-04-18 10:58:05 -0500261 "xyz.openbmc_project.Software.Activation"});
262 }
263 }
264}
265
Andrew Geissler0554c982019-04-23 14:40:12 -0500266// Note that asyncResp can be either a valid pointer or nullptr. If nullptr
267// then no asyncResp updates will occur
Ed Tanousb5a76932020-09-29 16:16:58 -0700268static void monitorForSoftwareAvailable(
zhanghch058d1b46d2021-04-01 11:18:24 +0800269 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
270 const crow::Request& req, const std::string& url,
271 int timeoutTimeSeconds = 10)
Andrew Geissler86adcd62019-04-18 10:58:05 -0500272{
273 // Only allow one FW update at a time
Ed Tanouse05aec52022-01-25 10:28:56 -0800274 if (fwUpdateInProgress)
Andrew Geissler86adcd62019-04-18 10:58:05 -0500275 {
Andrew Geissler0554c982019-04-23 14:40:12 -0500276 if (asyncResp)
277 {
Andrew Geissler0554c982019-04-23 14:40:12 -0500278 messages::serviceTemporarilyUnavailable(asyncResp->res, "30");
279 }
Andrew Geissler86adcd62019-04-18 10:58:05 -0500280 return;
281 }
282
Andrew Geissler0554c982019-04-23 14:40:12 -0500283 fwAvailableTimer =
Ed Tanous271584a2019-07-09 16:24:22 -0700284 std::make_unique<boost::asio::steady_timer>(*req.ioService);
Andrew Geissler86adcd62019-04-18 10:58:05 -0500285
Ed Tanous271584a2019-07-09 16:24:22 -0700286 fwAvailableTimer->expires_after(std::chrono::seconds(timeoutTimeSeconds));
Andrew Geissler86adcd62019-04-18 10:58:05 -0500287
288 fwAvailableTimer->async_wait(
Gunnar Mills1214b7e2020-06-04 10:11:30 -0500289 [asyncResp](const boost::system::error_code& ec) {
Andrew Geissler86adcd62019-04-18 10:58:05 -0500290 cleanUp();
291 if (ec == boost::asio::error::operation_aborted)
292 {
293 // expected, we were canceled before the timer completed.
294 return;
295 }
296 BMCWEB_LOG_ERROR
297 << "Timed out waiting for firmware object being created";
298 BMCWEB_LOG_ERROR
299 << "FW image may has already been uploaded to server";
300 if (ec)
301 {
302 BMCWEB_LOG_ERROR << "Async_wait failed" << ec;
303 return;
304 }
Andrew Geissler0554c982019-04-23 14:40:12 -0500305 if (asyncResp)
306 {
307 redfish::messages::internalError(asyncResp->res);
308 }
Andrew Geissler86adcd62019-04-18 10:58:05 -0500309 });
Ed Tanousa3e65892021-09-16 14:13:20 -0700310 task::Payload payload(req);
311 auto callback = [asyncResp,
312 payload](sdbusplus::message::message& m) mutable {
Andrew Geissler86adcd62019-04-18 10:58:05 -0500313 BMCWEB_LOG_DEBUG << "Match fired";
Ed Tanousa3e65892021-09-16 14:13:20 -0700314 softwareInterfaceAdded(asyncResp, m, std::move(payload));
Andrew Geissler86adcd62019-04-18 10:58:05 -0500315 };
316
317 fwUpdateInProgress = true;
318
319 fwUpdateMatcher = std::make_unique<sdbusplus::bus::match::match>(
320 *crow::connections::systemBus,
321 "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
322 "member='InterfacesAdded',path='/xyz/openbmc_project/software'",
323 callback);
James Feist4cde5d92020-06-11 10:39:55 -0700324
325 fwUpdateErrorMatcher = std::make_unique<sdbusplus::bus::match::match>(
326 *crow::connections::systemBus,
Brian Mae1cc4822021-12-01 17:05:54 +0800327 "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
328 "member='InterfacesAdded',"
329 "path='/xyz/openbmc_project/logging'",
James Feist4cde5d92020-06-11 10:39:55 -0700330 [asyncResp, url](sdbusplus::message::message& m) {
Brian Mae1cc4822021-12-01 17:05:54 +0800331 std::vector<
332 std::pair<std::string, dbus::utility::DBusPropertiesMap>>
333 interfacesProperties;
334 sdbusplus::message::object_path objPath;
335 m.read(objPath, interfacesProperties);
336 BMCWEB_LOG_DEBUG << "obj path = " << objPath.str;
337 for (const std::pair<std::string, dbus::utility::DBusPropertiesMap>&
338 interface : interfacesProperties)
James Feist4cde5d92020-06-11 10:39:55 -0700339 {
Brian Mae1cc4822021-12-01 17:05:54 +0800340 if (interface.first == "xyz.openbmc_project.Logging.Entry")
341 {
Ed Tanous711ac7a2021-12-20 09:34:41 -0800342 for (const std::pair<std::string,
343 dbus::utility::DbusVariantType>&
344 value : interface.second)
Brian Mae1cc4822021-12-01 17:05:54 +0800345 {
Ed Tanous711ac7a2021-12-20 09:34:41 -0800346 if (value.first != "Message")
347 {
348 continue;
349 }
350 const std::string* type =
351 std::get_if<std::string>(&value.second);
352 if (type == nullptr)
353 {
354 // if this was our message, timeout will cover it
355 return;
356 }
357 fwAvailableTimer = nullptr;
358 if (*type ==
359 "xyz.openbmc_project.Software.Image.Error.UnTarFailure")
360 {
361 redfish::messages::invalidUpload(
362 asyncResp->res, url, "Invalid archive");
363 }
364 else if (*type ==
365 "xyz.openbmc_project.Software.Image.Error."
366 "ManifestFileFailure")
367 {
368 redfish::messages::invalidUpload(
369 asyncResp->res, url, "Invalid manifest");
370 }
371 else if (
372 *type ==
373 "xyz.openbmc_project.Software.Image.Error.ImageFailure")
374 {
375 redfish::messages::invalidUpload(
376 asyncResp->res, url, "Invalid image format");
377 }
378 else if (
379 *type ==
380 "xyz.openbmc_project.Software.Version.Error.AlreadyExists")
381 {
382 redfish::messages::invalidUpload(
383 asyncResp->res, url,
384 "Image version already exists");
Gunnar Mills88b3dd12020-11-20 14:26:04 -0600385
Ed Tanous711ac7a2021-12-20 09:34:41 -0800386 redfish::messages::resourceAlreadyExists(
387 asyncResp->res,
388 "UpdateService.v1_5_0.UpdateService", "Version",
389 "uploaded version");
390 }
391 else if (
392 *type ==
393 "xyz.openbmc_project.Software.Image.Error.BusyFailure")
394 {
395 redfish::messages::resourceExhaustion(
396 asyncResp->res, url);
397 }
398 else
399 {
400 redfish::messages::internalError(asyncResp->res);
401 }
Brian Mae1cc4822021-12-01 17:05:54 +0800402 }
403 }
Gunnar Mills88b3dd12020-11-20 14:26:04 -0600404 }
James Feist4cde5d92020-06-11 10:39:55 -0700405 });
Andrew Geissler86adcd62019-04-18 10:58:05 -0500406}
Jennifer Lee729dae72018-04-24 15:59:34 -0700407
Andrew Geissler0554c982019-04-23 14:40:12 -0500408/**
409 * UpdateServiceActionsSimpleUpdate class supports handle POST method for
410 * SimpleUpdate action.
411 */
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700412inline void requestRoutesUpdateServiceActionsSimpleUpdate(App& app)
Andrew Geissler0554c982019-04-23 14:40:12 -0500413{
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700414 BMCWEB_ROUTE(
415 app, "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate/")
Ed Tanoused398212021-06-09 17:05:54 -0700416 .privileges(redfish::privileges::postUpdateService)
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700417 .methods(
418 boost::beast::http::verb::
419 post)([](const crow::Request& req,
420 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
421 std::optional<std::string> transferProtocol;
422 std::string imageURI;
Andrew Geissler0554c982019-04-23 14:40:12 -0500423
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700424 BMCWEB_LOG_DEBUG << "Enter UpdateService.SimpleUpdate doPost";
Andrew Geissler0554c982019-04-23 14:40:12 -0500425
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700426 // User can pass in both TransferProtocol and ImageURI parameters or
427 // they can pass in just the ImageURI with the transfer protocol
428 // embedded within it.
429 // 1) TransferProtocol:TFTP ImageURI:1.1.1.1/myfile.bin
430 // 2) ImageURI:tftp://1.1.1.1/myfile.bin
Andrew Geissler0554c982019-04-23 14:40:12 -0500431
Willy Tu15ed6782021-12-14 11:03:16 -0800432 if (!json_util::readJsonAction(req, asyncResp->res,
433 "TransferProtocol", transferProtocol,
434 "ImageURI", imageURI))
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700435 {
436 BMCWEB_LOG_DEBUG
437 << "Missing TransferProtocol or ImageURI parameter";
438 return;
439 }
440 if (!transferProtocol)
441 {
442 // Must be option 2
443 // Verify ImageURI has transfer protocol in it
444 size_t separator = imageURI.find(':');
445 if ((separator == std::string::npos) ||
446 ((separator + 1) > imageURI.size()))
447 {
448 messages::actionParameterValueTypeError(
449 asyncResp->res, imageURI, "ImageURI",
450 "UpdateService.SimpleUpdate");
451 BMCWEB_LOG_ERROR << "ImageURI missing transfer protocol: "
452 << imageURI;
453 return;
454 }
455 transferProtocol = imageURI.substr(0, separator);
456 // Ensure protocol is upper case for a common comparison path
457 // below
458 boost::to_upper(*transferProtocol);
459 BMCWEB_LOG_DEBUG << "Encoded transfer protocol "
460 << *transferProtocol;
Andrew Geissler0554c982019-04-23 14:40:12 -0500461
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700462 // Adjust imageURI to not have the protocol on it for parsing
463 // below
464 // ex. tftp://1.1.1.1/myfile.bin -> 1.1.1.1/myfile.bin
465 imageURI = imageURI.substr(separator + 3);
466 BMCWEB_LOG_DEBUG << "Adjusted imageUri " << imageURI;
467 }
468
469 // OpenBMC currently only supports TFTP
470 if (*transferProtocol != "TFTP")
471 {
472 messages::actionParameterNotSupported(
473 asyncResp->res, "TransferProtocol",
474 "UpdateService.SimpleUpdate");
475 BMCWEB_LOG_ERROR << "Request incorrect protocol parameter: "
476 << *transferProtocol;
477 return;
478 }
479
480 // Format should be <IP or Hostname>/<file> for imageURI
481 size_t separator = imageURI.find('/');
Andrew Geissler0554c982019-04-23 14:40:12 -0500482 if ((separator == std::string::npos) ||
483 ((separator + 1) > imageURI.size()))
484 {
485 messages::actionParameterValueTypeError(
486 asyncResp->res, imageURI, "ImageURI",
487 "UpdateService.SimpleUpdate");
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700488 BMCWEB_LOG_ERROR << "Invalid ImageURI: " << imageURI;
Jayashankar Padath274dfe62019-08-23 12:30:57 +0530489 return;
Jayashankar Padathfa1a5a32019-05-28 23:54:37 +0530490 }
491
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700492 std::string tftpServer = imageURI.substr(0, separator);
493 std::string fwFile = imageURI.substr(separator + 1);
494 BMCWEB_LOG_DEBUG << "Server: " << tftpServer + " File: " << fwFile;
Jayashankar Padath274dfe62019-08-23 12:30:57 +0530495
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700496 // Setup callback for when new software detected
497 // Give TFTP 10 minutes to complete
498 monitorForSoftwareAvailable(
499 asyncResp, req,
500 "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate",
501 600);
502
503 // TFTP can take up to 10 minutes depending on image size and
504 // connection speed. Return to caller as soon as the TFTP operation
505 // has been started. The callback above will ensure the activate
506 // is started once the download has completed
507 redfish::messages::success(asyncResp->res);
508
509 // Call TFTP service
510 crow::connections::systemBus->async_method_call(
511 [](const boost::system::error_code ec) {
512 if (ec)
Jayashankar Padathfa1a5a32019-05-28 23:54:37 +0530513 {
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700514 // messages::internalError(asyncResp->res);
515 cleanUp();
516 BMCWEB_LOG_DEBUG << "error_code = " << ec;
517 BMCWEB_LOG_DEBUG << "error msg = " << ec.message();
Jayashankar Padath274dfe62019-08-23 12:30:57 +0530518 }
519 else
520 {
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700521 BMCWEB_LOG_DEBUG << "Call to DownloaViaTFTP Success";
522 }
523 },
524 "xyz.openbmc_project.Software.Download",
525 "/xyz/openbmc_project/software",
526 "xyz.openbmc_project.Common.TFTP", "DownloadViaTFTP", fwFile,
527 tftpServer);
528
529 BMCWEB_LOG_DEBUG << "Exit UpdateService.SimpleUpdate doPost";
530 });
531}
532
533inline void requestRoutesUpdateService(App& app)
534{
535 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/")
Ed Tanoused398212021-06-09 17:05:54 -0700536 .privileges(redfish::privileges::getUpdateService)
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700537 .methods(
538 boost::beast::http::verb::
539 get)([](const crow::Request&,
540 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
541 asyncResp->res.jsonValue["@odata.type"] =
Chicago Duan0588a3b2021-06-10 18:20:36 +0800542 "#UpdateService.v1_5_0.UpdateService";
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700543 asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/UpdateService";
544 asyncResp->res.jsonValue["Id"] = "UpdateService";
545 asyncResp->res.jsonValue["Description"] =
546 "Service for Software Update";
547 asyncResp->res.jsonValue["Name"] = "Update Service";
548 asyncResp->res.jsonValue["HttpPushUri"] =
549 "/redfish/v1/UpdateService";
550 // UpdateService cannot be disabled
551 asyncResp->res.jsonValue["ServiceEnabled"] = true;
552 asyncResp->res.jsonValue["FirmwareInventory"] = {
553 {"@odata.id", "/redfish/v1/UpdateService/FirmwareInventory"}};
Tejas Patild61e5192021-06-04 15:49:35 +0530554 // Get the MaxImageSizeBytes
555 asyncResp->res.jsonValue["MaxImageSizeBytes"] =
556 bmcwebHttpReqBodyLimitMb * 1024 * 1024;
557
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700558#ifdef BMCWEB_INSECURE_ENABLE_REDFISH_FW_TFTP_UPDATE
559 // Update Actions object.
560 nlohmann::json& updateSvcSimpleUpdate =
561 asyncResp->res
562 .jsonValue["Actions"]["#UpdateService.SimpleUpdate"];
563 updateSvcSimpleUpdate["target"] =
564 "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate";
565 updateSvcSimpleUpdate["TransferProtocol@Redfish.AllowableValues"] =
566 {"TFTP"};
567#endif
568 // Get the current ApplyTime value
Jonathan Doman1e1e5982021-06-11 09:36:17 -0700569 sdbusplus::asio::getProperty<std::string>(
570 *crow::connections::systemBus, "xyz.openbmc_project.Settings",
571 "/xyz/openbmc_project/software/apply_time",
572 "xyz.openbmc_project.Software.ApplyTime", "RequestedApplyTime",
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700573 [asyncResp](const boost::system::error_code ec,
Jonathan Doman1e1e5982021-06-11 09:36:17 -0700574 const std::string& applyTime) {
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700575 if (ec)
576 {
577 BMCWEB_LOG_DEBUG << "DBUS response error " << ec;
578 messages::internalError(asyncResp->res);
Jayashankar Padathfa1a5a32019-05-28 23:54:37 +0530579 return;
580 }
Jayashankar Padath274dfe62019-08-23 12:30:57 +0530581
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700582 // Store the ApplyTime Value
Jonathan Doman1e1e5982021-06-11 09:36:17 -0700583 if (applyTime == "xyz.openbmc_project.Software.ApplyTime."
584 "RequestedApplyTimes.Immediate")
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700585 {
586 asyncResp->res
587 .jsonValue["HttpPushUriOptions"]
588 ["HttpPushUriApplyTime"]["ApplyTime"] =
589 "Immediate";
590 }
Jonathan Doman1e1e5982021-06-11 09:36:17 -0700591 else if (applyTime ==
592 "xyz.openbmc_project.Software.ApplyTime."
593 "RequestedApplyTimes.OnReset")
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700594 {
595 asyncResp->res
596 .jsonValue["HttpPushUriOptions"]
597 ["HttpPushUriApplyTime"]["ApplyTime"] =
598 "OnReset";
599 }
Jonathan Doman1e1e5982021-06-11 09:36:17 -0700600 });
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700601 });
602 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/")
Ed Tanoused398212021-06-09 17:05:54 -0700603 .privileges(redfish::privileges::patchUpdateService)
George Liu0fda0f12021-11-16 10:06:17 +0800604 .methods(
605 boost::beast::http::verb::
606 patch)([](const crow::Request& req,
607 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
608 BMCWEB_LOG_DEBUG << "doPatch...";
Jayashankar Padathfa1a5a32019-05-28 23:54:37 +0530609
George Liu0fda0f12021-11-16 10:06:17 +0800610 std::optional<nlohmann::json> pushUriOptions;
Willy Tu15ed6782021-12-14 11:03:16 -0800611 if (!json_util::readJsonPatch(req, asyncResp->res,
612 "HttpPushUriOptions", pushUriOptions))
George Liu0fda0f12021-11-16 10:06:17 +0800613 {
614 return;
615 }
616
617 if (pushUriOptions)
618 {
619 std::optional<nlohmann::json> pushUriApplyTime;
620 if (!json_util::readJson(*pushUriOptions, asyncResp->res,
621 "HttpPushUriApplyTime",
622 pushUriApplyTime))
Ed Tanous1abe55e2018-09-05 08:30:59 -0700623 {
Ed Tanousc711bf82018-07-30 16:31:33 -0700624 return;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700625 }
Jennifer Lee6c4eb9d2018-05-22 10:58:31 -0700626
George Liu0fda0f12021-11-16 10:06:17 +0800627 if (pushUriApplyTime)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700628 {
George Liu0fda0f12021-11-16 10:06:17 +0800629 std::optional<std::string> applyTime;
630 if (!json_util::readJson(*pushUriApplyTime, asyncResp->res,
631 "ApplyTime", applyTime))
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700632 {
633 return;
634 }
635
George Liu0fda0f12021-11-16 10:06:17 +0800636 if (applyTime)
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700637 {
George Liu0fda0f12021-11-16 10:06:17 +0800638 std::string applyTimeNewVal;
639 if (applyTime == "Immediate")
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700640 {
George Liu0fda0f12021-11-16 10:06:17 +0800641 applyTimeNewVal =
642 "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.Immediate";
643 }
644 else if (applyTime == "OnReset")
645 {
646 applyTimeNewVal =
647 "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.OnReset";
648 }
649 else
650 {
651 BMCWEB_LOG_INFO
652 << "ApplyTime value is not in the list of acceptable values";
653 messages::propertyValueNotInList(
654 asyncResp->res, *applyTime, "ApplyTime");
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700655 return;
656 }
657
George Liu0fda0f12021-11-16 10:06:17 +0800658 // Set the requested image apply time value
659 crow::connections::systemBus->async_method_call(
660 [asyncResp](const boost::system::error_code ec) {
661 if (ec)
662 {
663 BMCWEB_LOG_ERROR
664 << "D-Bus responses error: " << ec;
665 messages::internalError(asyncResp->res);
666 return;
667 }
668 messages::success(asyncResp->res);
669 },
670 "xyz.openbmc_project.Settings",
671 "/xyz/openbmc_project/software/apply_time",
672 "org.freedesktop.DBus.Properties", "Set",
673 "xyz.openbmc_project.Software.ApplyTime",
674 "RequestedApplyTime",
Ed Tanous168e20c2021-12-13 14:39:53 -0800675 dbus::utility::DbusVariantType{applyTimeNewVal});
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700676 }
677 }
George Liu0fda0f12021-11-16 10:06:17 +0800678 }
679 });
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700680 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/")
Ed Tanoused398212021-06-09 17:05:54 -0700681 .privileges(redfish::privileges::postUpdateService)
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700682 .methods(boost::beast::http::verb::post)(
683 [](const crow::Request& req,
684 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
685 BMCWEB_LOG_DEBUG << "doPost...";
686
687 // Setup callback for when new software detected
688 monitorForSoftwareAvailable(asyncResp, req,
689 "/redfish/v1/UpdateService");
690
691 std::string filepath("/tmp/images/" +
692 boost::uuids::to_string(
693 boost::uuids::random_generator()()));
694 BMCWEB_LOG_DEBUG << "Writing file to " << filepath;
695 std::ofstream out(filepath, std::ofstream::out |
696 std::ofstream::binary |
697 std::ofstream::trunc);
698 out << req.body;
699 out.close();
700 BMCWEB_LOG_DEBUG << "file upload complete!!";
701 });
702}
703
704inline void requestRoutesSoftwareInventoryCollection(App& app)
705{
706 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/")
Ed Tanoused398212021-06-09 17:05:54 -0700707 .privileges(redfish::privileges::getSoftwareInventoryCollection)
George Liu0fda0f12021-11-16 10:06:17 +0800708 .methods(
709 boost::beast::http::verb::
710 get)([](const crow::Request&,
711 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
712 asyncResp->res.jsonValue["@odata.type"] =
713 "#SoftwareInventoryCollection.SoftwareInventoryCollection";
714 asyncResp->res.jsonValue["@odata.id"] =
715 "/redfish/v1/UpdateService/FirmwareInventory";
716 asyncResp->res.jsonValue["Name"] = "Software Inventory Collection";
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700717
George Liu0fda0f12021-11-16 10:06:17 +0800718 crow::connections::systemBus->async_method_call(
719 [asyncResp](
720 const boost::system::error_code ec,
Ed Tanousb9d36b42022-02-26 21:42:46 -0800721 const dbus::utility::MapperGetSubTreeResponse& subtree) {
George Liu0fda0f12021-11-16 10:06:17 +0800722 if (ec)
723 {
724 messages::internalError(asyncResp->res);
725 return;
726 }
727 asyncResp->res.jsonValue["Members"] =
728 nlohmann::json::array();
729 asyncResp->res.jsonValue["Members@odata.count"] = 0;
730
Ed Tanous9eb808c2022-01-25 10:19:23 -0800731 for (const auto& obj : subtree)
George Liu0fda0f12021-11-16 10:06:17 +0800732 {
733 sdbusplus::message::object_path path(obj.first);
734 std::string swId = path.filename();
735 if (swId.empty())
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700736 {
737 messages::internalError(asyncResp->res);
George Liu0fda0f12021-11-16 10:06:17 +0800738 BMCWEB_LOG_DEBUG << "Can't parse firmware ID!!";
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700739 return;
740 }
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700741
George Liu0fda0f12021-11-16 10:06:17 +0800742 nlohmann::json& members =
743 asyncResp->res.jsonValue["Members"];
744 members.push_back(
745 {{"@odata.id",
746 "/redfish/v1/UpdateService/FirmwareInventory/" +
747 swId}});
748 asyncResp->res.jsonValue["Members@odata.count"] =
749 members.size();
750 }
751 },
752 // Note that only firmware levels associated with a device
753 // are stored under /xyz/openbmc_project/software therefore
754 // to ensure only real FirmwareInventory items are returned,
755 // this full object path must be used here as input to
756 // mapper
757 "xyz.openbmc_project.ObjectMapper",
758 "/xyz/openbmc_project/object_mapper",
759 "xyz.openbmc_project.ObjectMapper", "GetSubTree",
760 "/xyz/openbmc_project/software", static_cast<int32_t>(0),
761 std::array<const char*, 1>{
762 "xyz.openbmc_project.Software.Version"});
763 });
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700764}
765/* Fill related item links (i.e. bmc, bios) in for inventory */
766inline static void
767 getRelatedItems(const std::shared_ptr<bmcweb::AsyncResp>& aResp,
768 const std::string& purpose)
769{
770 if (purpose == fw_util::bmcPurpose)
771 {
772 nlohmann::json& relatedItem = aResp->res.jsonValue["RelatedItem"];
773 relatedItem.push_back({{"@odata.id", "/redfish/v1/Managers/bmc"}});
774 aResp->res.jsonValue["RelatedItem@odata.count"] = relatedItem.size();
775 }
776 else if (purpose == fw_util::biosPurpose)
777 {
778 nlohmann::json& relatedItem = aResp->res.jsonValue["RelatedItem"];
779 relatedItem.push_back(
780 {{"@odata.id", "/redfish/v1/Systems/system/Bios"}});
Jiaqing Zhao1a6e51a2022-01-19 19:20:24 +0800781 aResp->res.jsonValue["RelatedItem@odata.count"] = relatedItem.size();
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700782 }
783 else
784 {
785 BMCWEB_LOG_ERROR << "Unknown software purpose " << purpose;
786 }
787}
788
789inline void requestRoutesSoftwareInventory(App& app)
790{
791 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/<str>/")
Ed Tanoused398212021-06-09 17:05:54 -0700792 .privileges(redfish::privileges::getSoftwareInventory)
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700793 .methods(
794 boost::beast::http::verb::get)([](const crow::Request&,
795 const std::shared_ptr<
796 bmcweb::AsyncResp>& asyncResp,
797 const std::string& param) {
798 std::shared_ptr<std::string> swId =
799 std::make_shared<std::string>(param);
800
801 asyncResp->res.jsonValue["@odata.id"] =
802 "/redfish/v1/UpdateService/FirmwareInventory/" + *swId;
803
804 crow::connections::systemBus->async_method_call(
Ed Tanousb9d36b42022-02-26 21:42:46 -0800805 [asyncResp,
806 swId](const boost::system::error_code ec,
807 const dbus::utility::MapperGetSubTreeResponse& subtree) {
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700808 BMCWEB_LOG_DEBUG << "doGet callback...";
809 if (ec)
Jennifer Leef4b65ab2018-09-18 12:00:13 -0700810 {
Jason M. Billsf12894f2018-10-09 12:45:45 -0700811 messages::internalError(asyncResp->res);
Jennifer Leef4b65ab2018-09-18 12:00:13 -0700812 return;
813 }
Jennifer Leef4b65ab2018-09-18 12:00:13 -0700814
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700815 // Ensure we find our input swId, otherwise return an error
816 bool found = false;
817 for (const std::pair<
818 std::string,
819 std::vector<std::pair<
820 std::string, std::vector<std::string>>>>& obj :
821 subtree)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700822 {
Ed Tanouse05aec52022-01-25 10:28:56 -0800823 if (!boost::ends_with(obj.first, *swId))
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700824 {
825 continue;
826 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700827
Ed Tanous26f69762022-01-25 09:49:11 -0800828 if (obj.second.empty())
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700829 {
830 continue;
831 }
832
833 found = true;
834 fw_util::getFwStatus(asyncResp, swId,
835 obj.second[0].first);
836
837 crow::connections::systemBus->async_method_call(
Ed Tanous168e20c2021-12-13 14:39:53 -0800838 [asyncResp,
839 swId](const boost::system::error_code errorCode,
Ed Tanousb9d36b42022-02-26 21:42:46 -0800840 const dbus::utility::DBusPropertiesMap&
Ed Tanous168e20c2021-12-13 14:39:53 -0800841 propertiesList) {
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700842 if (errorCode)
843 {
844 messages::internalError(asyncResp->res);
845 return;
846 }
Ed Tanousb9d36b42022-02-26 21:42:46 -0800847 const std::string* swInvPurpose = nullptr;
848 const std::string* version = nullptr;
849 for (const auto& property : propertiesList)
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700850 {
Ed Tanousb9d36b42022-02-26 21:42:46 -0800851 if (property.first == "Purpose")
852 {
853 swInvPurpose = std::get_if<std::string>(
854 &property.second);
855 }
856 if (property.first == "Version")
857 {
858 version = std::get_if<std::string>(
859 &property.second);
860 }
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700861 }
Ed Tanousb9d36b42022-02-26 21:42:46 -0800862
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700863 if (swInvPurpose == nullptr)
864 {
George Liu0fda0f12021-11-16 10:06:17 +0800865 BMCWEB_LOG_DEBUG
Ed Tanousb9d36b42022-02-26 21:42:46 -0800866 << "Can't find property \"Purpose\"!";
867 messages::internalError(asyncResp->res);
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700868 return;
869 }
870
871 BMCWEB_LOG_DEBUG << "swInvPurpose = "
872 << *swInvPurpose;
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700873
874 if (version == nullptr)
875 {
876 BMCWEB_LOG_DEBUG
877 << "Can't find property \"Version\"!";
878
Ed Tanousb9d36b42022-02-26 21:42:46 -0800879 messages::internalError(asyncResp->res);
880
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700881 return;
882 }
883 asyncResp->res.jsonValue["Version"] = *version;
884 asyncResp->res.jsonValue["Id"] = *swId;
885
886 // swInvPurpose is of format:
887 // xyz.openbmc_project.Software.Version.VersionPurpose.ABC
888 // Translate this to "ABC image"
889 size_t endDesc = swInvPurpose->rfind('.');
890 if (endDesc == std::string::npos)
891 {
892 messages::internalError(asyncResp->res);
893 return;
894 }
895 endDesc++;
896 if (endDesc >= swInvPurpose->size())
897 {
898 messages::internalError(asyncResp->res);
899 return;
900 }
901
902 std::string formatDesc =
903 swInvPurpose->substr(endDesc);
904 asyncResp->res.jsonValue["Description"] =
905 formatDesc + " image";
906 getRelatedItems(asyncResp, *swInvPurpose);
907 },
908 obj.second[0].first, obj.first,
909 "org.freedesktop.DBus.Properties", "GetAll",
910 "xyz.openbmc_project.Software.Version");
911 }
912 if (!found)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700913 {
Ed Tanous8cc8ede2022-02-28 10:20:59 -0800914 BMCWEB_LOG_ERROR << "Input swID " << *swId
915 << " not found!";
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700916 messages::resourceMissingAtURI(
917 asyncResp->res,
Ed Tanousace85d62021-10-26 12:45:59 -0700918 crow::utility::urlFromPieces(
919 "redfish", "v1", "UpdateService",
920 "FirmwareInventory", *swId));
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700921 return;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700922 }
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700923 asyncResp->res.jsonValue["@odata.type"] =
924 "#SoftwareInventory.v1_1_0.SoftwareInventory";
925 asyncResp->res.jsonValue["Name"] = "Software Inventory";
926 asyncResp->res.jsonValue["Status"]["HealthRollup"] = "OK";
Ed Tanous1abe55e2018-09-05 08:30:59 -0700927
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700928 asyncResp->res.jsonValue["Updateable"] = false;
929 fw_util::getFwUpdateableStatus(asyncResp, swId);
930 },
931 "xyz.openbmc_project.ObjectMapper",
932 "/xyz/openbmc_project/object_mapper",
933 "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/",
934 static_cast<int32_t>(0),
935 std::array<const char*, 1>{
936 "xyz.openbmc_project.Software.Version"});
937 });
938}
Ed Tanous1abe55e2018-09-05 08:30:59 -0700939
940} // namespace redfish