1 #include "config.h" 2 3 #include "activation.hpp" 4 5 #include "utils.hpp" 6 7 #include <cassert> 8 #include <filesystem> 9 #include <phosphor-logging/elog-errors.hpp> 10 #include <phosphor-logging/log.hpp> 11 12 namespace phosphor 13 { 14 namespace software 15 { 16 namespace updater 17 { 18 19 constexpr auto SYSTEMD_BUSNAME = "org.freedesktop.systemd1"; 20 constexpr auto SYSTEMD_PATH = "/org/freedesktop/systemd1"; 21 constexpr auto SYSTEMD_INTERFACE = "org.freedesktop.systemd1.Manager"; 22 23 namespace fs = std::filesystem; 24 namespace softwareServer = sdbusplus::xyz::openbmc_project::Software::server; 25 26 using namespace phosphor::logging; 27 using sdbusplus::exception::SdBusError; 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::message& 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 SdBusError& 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 currentUpdatingPsu.clear(); 131 associations(assocs); 132 133 psuQueue.pop(); 134 doUpdate(); // Update the next psu 135 } 136 137 void Activation::onUpdateFailed() 138 { 139 // TODO: report an event 140 log<level::ERR>("Failed to udpate PSU", 141 entry("PSU=%s", psuQueue.front().c_str())); 142 std::queue<std::string>().swap(psuQueue); // Clear the queue 143 activation(Status::Failed); 144 } 145 146 Activation::Status Activation::startActivation() 147 { 148 // Check if the activation has file path 149 if (path().empty()) 150 { 151 log<level::WARNING>("No image for the activation, skipped", 152 entry("VERSION_ID=%s", versionId.c_str())); 153 return activation(); // Return the previous activation status 154 } 155 if (!activationProgress) 156 { 157 activationProgress = std::make_unique<ActivationProgress>(bus, objPath); 158 } 159 if (!activationBlocksTransition) 160 { 161 activationBlocksTransition = 162 std::make_unique<ActivationBlocksTransition>(bus, objPath); 163 } 164 165 auto psuPaths = utils::getPSUInventoryPath(bus); 166 if (psuPaths.empty()) 167 { 168 log<level::WARNING>("No PSU inventory found"); 169 return Status::Failed; 170 } 171 172 for (const auto& p : psuPaths) 173 { 174 if (isCompatible(p)) 175 { 176 if (utils::isAssociated(p, associations())) 177 { 178 log<level::NOTICE>("PSU already running the image, skipping", 179 entry("PSU=%s", p.c_str())); 180 continue; 181 } 182 psuQueue.push(p); 183 } 184 else 185 { 186 log<level::NOTICE>("PSU not compatible", 187 entry("PSU=%s", p.c_str())); 188 } 189 } 190 191 if (psuQueue.empty()) 192 { 193 log<level::WARNING>("No PSU compatible with the software"); 194 return activation(); // Return the previous activation status 195 } 196 197 // The progress to be increased for each successful update of PSU 198 // E.g. in case we have 4 PSUs: 199 // 1. Initial progrss is 10 200 // 2. Add 20 after each update is done, so we will see progress to be 30, 201 // 50, 70, 90 202 // 3. When all PSUs are updated, it will be 100 and the interface is 203 // removed. 204 progressStep = 80 / psuQueue.size(); 205 if (doUpdate()) 206 { 207 activationProgress->progress(10); 208 return Status::Activating; 209 } 210 else 211 { 212 return Status::Failed; 213 } 214 } 215 216 void Activation::finishActivation() 217 { 218 storeImage(); 219 activationProgress->progress(100); 220 221 // TODO: delete the old software object 222 deleteImageManagerObject(); 223 224 associationInterface->createActiveAssociation(objPath); 225 associationInterface->addFunctionalAssociation(objPath); 226 227 activation(Status::Active); 228 } 229 230 void Activation::deleteImageManagerObject() 231 { 232 // Get the Delete object for <versionID> inside image_manager 233 constexpr auto versionServiceStr = "xyz.openbmc_project.Software.Version"; 234 constexpr auto deleteInterface = "xyz.openbmc_project.Object.Delete"; 235 std::string versionService; 236 auto services = utils::getServices(bus, objPath.c_str(), deleteInterface); 237 238 // We need to find the phosphor-version-software-manager's version service 239 // to invoke the delete interface 240 for (const auto& service : services) 241 { 242 if (service.find(versionServiceStr) != std::string::npos) 243 { 244 versionService = service; 245 break; 246 } 247 } 248 if (versionService.empty()) 249 { 250 // When updating a stored image, there is no version object created by 251 // "xyz.openbmc_project.Software.Version" service, so skip it. 252 return; 253 } 254 255 // Call the Delete object for <versionID> inside image_manager 256 auto method = bus.new_method_call(versionService.c_str(), objPath.c_str(), 257 deleteInterface, "Delete"); 258 try 259 { 260 bus.call(method); 261 } 262 catch (const SdBusError& e) 263 { 264 log<level::ERR>("Error performing call to Delete object path", 265 entry("ERROR=%s", e.what()), 266 entry("PATH=%s", objPath.c_str())); 267 } 268 } 269 270 bool Activation::isCompatible(const std::string& psuInventoryPath) 271 { 272 auto service = 273 utils::getService(bus, psuInventoryPath.c_str(), ASSET_IFACE); 274 auto psuManufacturer = utils::getProperty<std::string>( 275 bus, service.c_str(), psuInventoryPath.c_str(), ASSET_IFACE, 276 MANUFACTURER); 277 auto psuModel = utils::getProperty<std::string>( 278 bus, service.c_str(), psuInventoryPath.c_str(), ASSET_IFACE, MODEL); 279 if (psuModel != model) 280 { 281 // The model shall match 282 return false; 283 } 284 if (!psuManufacturer.empty()) 285 { 286 // If PSU inventory has manufacturer property, it shall match 287 return psuManufacturer == manufacturer; 288 } 289 return true; 290 } 291 292 void Activation::storeImage() 293 { 294 // Store image in persistent dir separated by model 295 // and only store the latest one by removing old ones 296 auto src = path(); 297 auto dst = fs::path(IMG_DIR_PERSIST) / model; 298 if (src == dst) 299 { 300 // This happens when updating an stored image, no need to store it again 301 return; 302 } 303 try 304 { 305 fs::remove_all(dst); 306 fs::create_directories(dst); 307 fs::copy(src, dst); 308 path(dst.string()); // Update the FilePath interface 309 } 310 catch (const fs::filesystem_error& e) 311 { 312 log<level::ERR>("Error storing PSU image", entry("ERROR=%s", e.what()), 313 entry("SRC=%s", src.c_str()), 314 entry("DST=%s", dst.c_str())); 315 } 316 } 317 318 std::string Activation::getUpdateService(const std::string& psuInventoryPath) 319 { 320 fs::path imagePath(path()); 321 322 // The systemd unit shall be escaped 323 std::string args = psuInventoryPath; 324 args += "\\x20"; 325 args += imagePath; 326 std::replace(args.begin(), args.end(), '/', '-'); 327 328 std::string service = PSU_UPDATE_SERVICE; 329 auto p = service.find('@'); 330 assert(p != std::string::npos); 331 service.insert(p + 1, args); 332 return service; 333 } 334 335 } // namespace updater 336 } // namespace software 337 } // namespace phosphor 338