#include "config.h" #include "activation.hpp" #include "utils.hpp" #include #include #include #include namespace phosphor { namespace software { namespace updater { constexpr auto SYSTEMD_BUSNAME = "org.freedesktop.systemd1"; constexpr auto SYSTEMD_PATH = "/org/freedesktop/systemd1"; constexpr auto SYSTEMD_INTERFACE = "org.freedesktop.systemd1.Manager"; namespace fs = std::filesystem; namespace softwareServer = sdbusplus::xyz::openbmc_project::Software::server; using namespace phosphor::logging; using SoftwareActivation = softwareServer::Activation; auto Activation::activation(Activations value) -> Activations { if (value == Status::Activating) { value = startActivation(); } else { activationBlocksTransition.reset(); activationProgress.reset(); } return SoftwareActivation::activation(value); } auto Activation::requestedActivation(RequestedActivations value) -> RequestedActivations { if ((value == SoftwareActivation::RequestedActivations::Active) && (SoftwareActivation::requestedActivation() != SoftwareActivation::RequestedActivations::Active)) { // PSU image could be activated even when it's in active, // e.g. in case a PSU is replaced and has a older image, it will be // updated with the running PSU image that is stored in BMC. if ((activation() == Status::Ready) || (activation() == Status::Failed) || activation() == Status::Active) { activation(Status::Activating); } } return SoftwareActivation::requestedActivation(value); } void Activation::unitStateChange(sdbusplus::message_t& msg) { uint32_t newStateID{}; sdbusplus::message::object_path newStateObjPath; std::string newStateUnit{}; std::string newStateResult{}; // Read the msg and populate each variable msg.read(newStateID, newStateObjPath, newStateUnit, newStateResult); if (newStateUnit == psuUpdateUnit) { if (newStateResult == "done") { onUpdateDone(); } if (newStateResult == "failed" || newStateResult == "dependency") { onUpdateFailed(); } } } bool Activation::doUpdate(const std::string& psuInventoryPath) { currentUpdatingPsu = psuInventoryPath; psuUpdateUnit = getUpdateService(currentUpdatingPsu); try { auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH, SYSTEMD_INTERFACE, "StartUnit"); method.append(psuUpdateUnit, "replace"); bus.call_noreply(method); return true; } catch (const sdbusplus::exception_t& e) { log("Error staring service", entry("ERROR=%s", e.what())); onUpdateFailed(); return false; } } bool Activation::doUpdate() { // When the queue is empty, all updates are done if (psuQueue.empty()) { finishActivation(); return true; } // Do the update on a PSU const auto& psu = psuQueue.front(); return doUpdate(psu); } void Activation::onUpdateDone() { auto progress = activationProgress->progress() + progressStep; activationProgress->progress(progress); // Update the activation association auto assocs = associations(); assocs.emplace_back(ACTIVATION_FWD_ASSOCIATION, ACTIVATION_REV_ASSOCIATION, currentUpdatingPsu); associations(assocs); activationListener->onUpdateDone(versionId, currentUpdatingPsu); currentUpdatingPsu.clear(); psuQueue.pop(); doUpdate(); // Update the next psu } void Activation::onUpdateFailed() { // TODO: report an event log("Failed to update PSU", entry("PSU=%s", psuQueue.front().c_str())); std::queue().swap(psuQueue); // Clear the queue activation(Status::Failed); } Activation::Status Activation::startActivation() { // Check if the activation has file path if (path().empty()) { log("No image for the activation, skipped", entry("VERSION_ID=%s", versionId.c_str())); return activation(); // Return the previous activation status } auto psuPaths = utils::getPSUInventoryPath(bus); if (psuPaths.empty()) { log("No PSU inventory found"); return Status::Failed; } for (const auto& p : psuPaths) { if (isCompatible(p)) { if (utils::isAssociated(p, associations())) { log("PSU already running the image, skipping", entry("PSU=%s", p.c_str())); continue; } psuQueue.push(p); } else { log("PSU not compatible", entry("PSU=%s", p.c_str())); } } if (psuQueue.empty()) { log("No PSU compatible with the software"); return activation(); // Return the previous activation status } if (!activationProgress) { activationProgress = std::make_unique(bus, objPath); } if (!activationBlocksTransition) { activationBlocksTransition = std::make_unique(bus, objPath); } // The progress to be increased for each successful update of PSU // E.g. in case we have 4 PSUs: // 1. Initial progress is 10 // 2. Add 20 after each update is done, so we will see progress to be 30, // 50, 70, 90 // 3. When all PSUs are updated, it will be 100 and the interface is // removed. progressStep = 80 / psuQueue.size(); if (doUpdate()) { activationProgress->progress(10); return Status::Activating; } else { return Status::Failed; } } void Activation::finishActivation() { storeImage(); activationProgress->progress(100); deleteImageManagerObject(); associationInterface->createActiveAssociation(objPath); associationInterface->addFunctionalAssociation(objPath); associationInterface->addUpdateableAssociation(objPath); // Reset RequestedActivations to none so that it could be activated in // future requestedActivation(SoftwareActivation::RequestedActivations::None); activation(Status::Active); } void Activation::deleteImageManagerObject() { // Get the Delete object for inside image_manager constexpr auto versionServiceStr = "xyz.openbmc_project.Software.Version"; constexpr auto deleteInterface = "xyz.openbmc_project.Object.Delete"; std::string versionService; auto services = utils::getServices(bus, objPath.c_str(), deleteInterface); // We need to find the phosphor-version-software-manager's version service // to invoke the delete interface for (const auto& service : services) { if (service.find(versionServiceStr) != std::string::npos) { versionService = service; break; } } if (versionService.empty()) { // When updating a stored image, there is no version object created by // "xyz.openbmc_project.Software.Version" service, so skip it. return; } // Call the Delete object for inside image_manager auto method = bus.new_method_call(versionService.c_str(), objPath.c_str(), deleteInterface, "Delete"); try { bus.call(method); } catch (const sdbusplus::exception_t& e) { log("Error performing call to Delete object path", entry("ERROR=%s", e.what()), entry("PATH=%s", objPath.c_str())); } } bool Activation::isCompatible(const std::string& psuInventoryPath) { auto service = utils::getService(bus, psuInventoryPath.c_str(), ASSET_IFACE); auto psuManufacturer = utils::getProperty( bus, service.c_str(), psuInventoryPath.c_str(), ASSET_IFACE, MANUFACTURER); auto psuModel = utils::getProperty( bus, service.c_str(), psuInventoryPath.c_str(), ASSET_IFACE, MODEL); if (psuModel != model) { // The model shall match return false; } if (!psuManufacturer.empty()) { // If PSU inventory has manufacturer property, it shall match return psuManufacturer == manufacturer; } return true; } void Activation::storeImage() { // Store image in persistent dir separated by model // and only store the latest one by removing old ones auto src = path(); auto dst = fs::path(IMG_DIR_PERSIST) / model; if (src == dst) { // This happens when updating an stored image, no need to store it again return; } try { fs::remove_all(dst); fs::create_directories(dst); fs::copy(src, dst); path(dst.string()); // Update the FilePath interface } catch (const fs::filesystem_error& e) { log("Error storing PSU image", entry("ERROR=%s", e.what()), entry("SRC=%s", src.c_str()), entry("DST=%s", dst.c_str())); } } std::string Activation::getUpdateService(const std::string& psuInventoryPath) { fs::path imagePath(path()); // The systemd unit shall be escaped std::string args = psuInventoryPath; args += "\\x20"; args += imagePath; std::replace(args.begin(), args.end(), '/', '-'); std::string service = PSU_UPDATE_SERVICE; auto p = service.find('@'); assert(p != std::string::npos); service.insert(p + 1, args); return service; } void ActivationBlocksTransition::enableRebootGuard() { log("PSU image activating - BMC reboots are disabled."); auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH, SYSTEMD_INTERFACE, "StartUnit"); method.append("reboot-guard-enable.service", "replace"); bus.call_noreply_noerror(method); } void ActivationBlocksTransition::disableRebootGuard() { log("PSU activation has ended - BMC reboots are re-enabled."); auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH, SYSTEMD_INTERFACE, "StartUnit"); method.append("reboot-guard-disable.service", "replace"); bus.call_noreply_noerror(method); } } // namespace updater } // namespace software } // namespace phosphor