1 #include "config.h" 2 3 #include "activation.hpp" 4 5 #include "utils.hpp" 6 7 #include <phosphor-logging/elog-errors.hpp> 8 #include <phosphor-logging/log.hpp> 9 10 #include <cassert> 11 #include <filesystem> 12 13 namespace phosphor 14 { 15 namespace software 16 { 17 namespace updater 18 { 19 20 constexpr auto SYSTEMD_BUSNAME = "org.freedesktop.systemd1"; 21 constexpr auto SYSTEMD_PATH = "/org/freedesktop/systemd1"; 22 constexpr auto SYSTEMD_INTERFACE = "org.freedesktop.systemd1.Manager"; 23 24 namespace fs = std::filesystem; 25 namespace softwareServer = sdbusplus::xyz::openbmc_project::Software::server; 26 27 using namespace phosphor::logging; 28 using SoftwareActivation = softwareServer::Activation; 29 30 auto Activation::activation(Activations value) -> Activations 31 { 32 if (value == Status::Activating) 33 { 34 value = startActivation(); 35 } 36 else 37 { 38 activationBlocksTransition.reset(); 39 activationProgress.reset(); 40 } 41 42 return SoftwareActivation::activation(value); 43 } 44 45 auto Activation::requestedActivation(RequestedActivations value) 46 -> RequestedActivations 47 { 48 if ((value == SoftwareActivation::RequestedActivations::Active) && 49 (SoftwareActivation::requestedActivation() != 50 SoftwareActivation::RequestedActivations::Active)) 51 { 52 // PSU image could be activated even when it's in active, 53 // e.g. in case a PSU is replaced and has a older image, it will be 54 // updated with the running PSU image that is stored in BMC. 55 if ((activation() == Status::Ready) || 56 (activation() == Status::Failed) || activation() == Status::Active) 57 { 58 activation(Status::Activating); 59 } 60 } 61 return SoftwareActivation::requestedActivation(value); 62 } 63 64 void Activation::unitStateChange(sdbusplus::message_t& msg) 65 { 66 uint32_t newStateID{}; 67 sdbusplus::message::object_path newStateObjPath; 68 std::string newStateUnit{}; 69 std::string newStateResult{}; 70 71 // Read the msg and populate each variable 72 msg.read(newStateID, newStateObjPath, newStateUnit, newStateResult); 73 74 if (newStateUnit == psuUpdateUnit) 75 { 76 if (newStateResult == "done") 77 { 78 onUpdateDone(); 79 } 80 if (newStateResult == "failed" || newStateResult == "dependency") 81 { 82 onUpdateFailed(); 83 } 84 } 85 } 86 87 bool Activation::doUpdate(const std::string& psuInventoryPath) 88 { 89 currentUpdatingPsu = psuInventoryPath; 90 psuUpdateUnit = getUpdateService(currentUpdatingPsu); 91 try 92 { 93 auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH, 94 SYSTEMD_INTERFACE, "StartUnit"); 95 method.append(psuUpdateUnit, "replace"); 96 bus.call_noreply(method); 97 return true; 98 } 99 catch (const sdbusplus::exception_t& e) 100 { 101 log<level::ERR>("Error staring service", entry("ERROR=%s", e.what())); 102 onUpdateFailed(); 103 return false; 104 } 105 } 106 107 bool Activation::doUpdate() 108 { 109 // When the queue is empty, all updates are done 110 if (psuQueue.empty()) 111 { 112 finishActivation(); 113 return true; 114 } 115 116 // Do the update on a PSU 117 const auto& psu = psuQueue.front(); 118 return doUpdate(psu); 119 } 120 121 void Activation::onUpdateDone() 122 { 123 auto progress = activationProgress->progress() + progressStep; 124 activationProgress->progress(progress); 125 126 // Update the activation association 127 auto assocs = associations(); 128 assocs.emplace_back(ACTIVATION_FWD_ASSOCIATION, ACTIVATION_REV_ASSOCIATION, 129 currentUpdatingPsu); 130 associations(assocs); 131 132 activationListener->onUpdateDone(versionId, currentUpdatingPsu); 133 currentUpdatingPsu.clear(); 134 135 psuQueue.pop(); 136 doUpdate(); // Update the next psu 137 } 138 139 void Activation::onUpdateFailed() 140 { 141 // TODO: report an event 142 log<level::ERR>("Failed to udpate PSU", 143 entry("PSU=%s", psuQueue.front().c_str())); 144 std::queue<std::string>().swap(psuQueue); // Clear the queue 145 activation(Status::Failed); 146 } 147 148 Activation::Status Activation::startActivation() 149 { 150 // Check if the activation has file path 151 if (path().empty()) 152 { 153 log<level::WARNING>("No image for the activation, skipped", 154 entry("VERSION_ID=%s", versionId.c_str())); 155 return activation(); // Return the previous activation status 156 } 157 158 auto psuPaths = utils::getPSUInventoryPath(bus); 159 if (psuPaths.empty()) 160 { 161 log<level::WARNING>("No PSU inventory found"); 162 return Status::Failed; 163 } 164 165 for (const auto& p : psuPaths) 166 { 167 if (isCompatible(p)) 168 { 169 if (utils::isAssociated(p, associations())) 170 { 171 log<level::NOTICE>("PSU already running the image, skipping", 172 entry("PSU=%s", p.c_str())); 173 continue; 174 } 175 psuQueue.push(p); 176 } 177 else 178 { 179 log<level::NOTICE>("PSU not compatible", 180 entry("PSU=%s", p.c_str())); 181 } 182 } 183 184 if (psuQueue.empty()) 185 { 186 log<level::WARNING>("No PSU compatible with the software"); 187 return activation(); // Return the previous activation status 188 } 189 190 if (!activationProgress) 191 { 192 activationProgress = std::make_unique<ActivationProgress>(bus, objPath); 193 } 194 if (!activationBlocksTransition) 195 { 196 activationBlocksTransition = 197 std::make_unique<ActivationBlocksTransition>(bus, objPath); 198 } 199 200 // The progress to be increased for each successful update of PSU 201 // E.g. in case we have 4 PSUs: 202 // 1. Initial progrss is 10 203 // 2. Add 20 after each update is done, so we will see progress to be 30, 204 // 50, 70, 90 205 // 3. When all PSUs are updated, it will be 100 and the interface is 206 // removed. 207 progressStep = 80 / psuQueue.size(); 208 if (doUpdate()) 209 { 210 activationProgress->progress(10); 211 return Status::Activating; 212 } 213 else 214 { 215 return Status::Failed; 216 } 217 } 218 219 void Activation::finishActivation() 220 { 221 storeImage(); 222 activationProgress->progress(100); 223 224 deleteImageManagerObject(); 225 226 associationInterface->createActiveAssociation(objPath); 227 associationInterface->addFunctionalAssociation(objPath); 228 associationInterface->addUpdateableAssociation(objPath); 229 230 // Reset RequestedActivations to none so that it could be activated in 231 // future 232 requestedActivation(SoftwareActivation::RequestedActivations::None); 233 activation(Status::Active); 234 } 235 236 void Activation::deleteImageManagerObject() 237 { 238 // Get the Delete object for <versionID> inside image_manager 239 constexpr auto versionServiceStr = "xyz.openbmc_project.Software.Version"; 240 constexpr auto deleteInterface = "xyz.openbmc_project.Object.Delete"; 241 std::string versionService; 242 auto services = utils::getServices(bus, objPath.c_str(), deleteInterface); 243 244 // We need to find the phosphor-version-software-manager's version service 245 // to invoke the delete interface 246 for (const auto& service : services) 247 { 248 if (service.find(versionServiceStr) != std::string::npos) 249 { 250 versionService = service; 251 break; 252 } 253 } 254 if (versionService.empty()) 255 { 256 // When updating a stored image, there is no version object created by 257 // "xyz.openbmc_project.Software.Version" service, so skip it. 258 return; 259 } 260 261 // Call the Delete object for <versionID> inside image_manager 262 auto method = bus.new_method_call(versionService.c_str(), objPath.c_str(), 263 deleteInterface, "Delete"); 264 try 265 { 266 bus.call(method); 267 } 268 catch (const sdbusplus::exception_t& e) 269 { 270 log<level::ERR>("Error performing call to Delete object path", 271 entry("ERROR=%s", e.what()), 272 entry("PATH=%s", objPath.c_str())); 273 } 274 } 275 276 bool Activation::isCompatible(const std::string& psuInventoryPath) 277 { 278 auto service = utils::getService(bus, psuInventoryPath.c_str(), 279 ASSET_IFACE); 280 auto psuManufacturer = utils::getProperty<std::string>( 281 bus, service.c_str(), psuInventoryPath.c_str(), ASSET_IFACE, 282 MANUFACTURER); 283 auto psuModel = utils::getProperty<std::string>( 284 bus, service.c_str(), psuInventoryPath.c_str(), ASSET_IFACE, MODEL); 285 if (psuModel != model) 286 { 287 // The model shall match 288 return false; 289 } 290 if (!psuManufacturer.empty()) 291 { 292 // If PSU inventory has manufacturer property, it shall match 293 return psuManufacturer == manufacturer; 294 } 295 return true; 296 } 297 298 void Activation::storeImage() 299 { 300 // Store image in persistent dir separated by model 301 // and only store the latest one by removing old ones 302 auto src = path(); 303 auto dst = fs::path(IMG_DIR_PERSIST) / model; 304 if (src == dst) 305 { 306 // This happens when updating an stored image, no need to store it again 307 return; 308 } 309 try 310 { 311 fs::remove_all(dst); 312 fs::create_directories(dst); 313 fs::copy(src, dst); 314 path(dst.string()); // Update the FilePath interface 315 } 316 catch (const fs::filesystem_error& e) 317 { 318 log<level::ERR>("Error storing PSU image", entry("ERROR=%s", e.what()), 319 entry("SRC=%s", src.c_str()), 320 entry("DST=%s", dst.c_str())); 321 } 322 } 323 324 std::string Activation::getUpdateService(const std::string& psuInventoryPath) 325 { 326 fs::path imagePath(path()); 327 328 // The systemd unit shall be escaped 329 std::string args = psuInventoryPath; 330 args += "\\x20"; 331 args += imagePath; 332 std::replace(args.begin(), args.end(), '/', '-'); 333 334 std::string service = PSU_UPDATE_SERVICE; 335 auto p = service.find('@'); 336 assert(p != std::string::npos); 337 service.insert(p + 1, args); 338 return service; 339 } 340 341 void ActivationBlocksTransition::enableRebootGuard() 342 { 343 log<level::INFO>("PSU image activating - BMC reboots are disabled."); 344 345 auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH, 346 SYSTEMD_INTERFACE, "StartUnit"); 347 method.append("reboot-guard-enable.service", "replace"); 348 bus.call_noreply_noerror(method); 349 } 350 351 void ActivationBlocksTransition::disableRebootGuard() 352 { 353 log<level::INFO>("PSU activation has ended - BMC reboots are re-enabled."); 354 355 auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH, 356 SYSTEMD_INTERFACE, "StartUnit"); 357 method.append("reboot-guard-disable.service", "replace"); 358 bus.call_noreply_noerror(method); 359 } 360 361 } // namespace updater 362 } // namespace software 363 } // namespace phosphor 364