@@ -158,6 +158,25 @@ void ensureDirectoryExists(const std::string& dirPath) {
158158 }
159159}
160160
161+ /*
162+ * Get the current revision number by reading the symlink target.
163+ * Returns -1 if unable to determine the current revision.
164+ */
165+ int getCurrentRevisionNumber (const std::string& systemConfigPath) {
166+ std::error_code ec;
167+
168+ if (!fs::is_symlink (systemConfigPath, ec)) {
169+ return -1 ;
170+ }
171+
172+ std::string target = fs::read_symlink (systemConfigPath, ec);
173+ if (ec) {
174+ return -1 ;
175+ }
176+
177+ return ConfigSession::extractRevisionNumber (target);
178+ }
179+
161180} // anonymous namespace
162181
163182ConfigSession::ConfigSession () {
@@ -441,4 +460,99 @@ int ConfigSession::commit(const HostInfo& hostInfo) {
441460 return revision;
442461}
443462
463+ int ConfigSession::rollback (const HostInfo& hostInfo) {
464+ // Get the current revision number
465+ int currentRevision = getCurrentRevisionNumber (systemConfigPath_);
466+ if (currentRevision <= 0 ) {
467+ throw std::runtime_error (
468+ " Cannot rollback: cannot determine the current revision from " +
469+ systemConfigPath_);
470+ } else if (currentRevision == 1 ) {
471+ throw std::runtime_error (
472+ " Cannot rollback: already at the first revision (r1)" );
473+ }
474+
475+ // Rollback to the previous revision
476+ std::string targetRevision = " r" + std::to_string (currentRevision - 1 );
477+ return rollback (hostInfo, targetRevision);
478+ }
479+
480+ int ConfigSession::rollback (
481+ const HostInfo& hostInfo,
482+ const std::string& revision) {
483+ ensureDirectoryExists (cliConfigDir_);
484+
485+ // Build the path to the target revision
486+ std::string targetConfigPath = cliConfigDir_ + " /agent-" + revision + " .conf" ;
487+
488+ // Check if the target revision exists
489+ if (!fs::exists (targetConfigPath)) {
490+ throw std::runtime_error (
491+ " Revision " + revision + " does not exist at " + targetConfigPath);
492+ }
493+
494+ std::error_code ec;
495+
496+ // Verify that the system config is a symlink
497+ if (!fs::is_symlink (systemConfigPath_)) {
498+ throw std::runtime_error (
499+ systemConfigPath_ + " is not a symlink. Expected it to be a symlink." );
500+ }
501+
502+ // Read the old symlink target in case we need to undo the rollback
503+ std::string oldSymlinkTarget = fs::read_symlink (systemConfigPath_, ec);
504+ if (ec) {
505+ throw std::runtime_error (
506+ " Failed to read symlink " + systemConfigPath_ + " : " + ec.message ());
507+ }
508+
509+ // First, create a new revision with the same content as the target revision
510+ std::string newRevisionPath =
511+ createNextRevisionFile (cliConfigDir_ + " /agent" );
512+
513+ // Copy the target config to the new revision file
514+ fs::copy_file (
515+ targetConfigPath,
516+ newRevisionPath,
517+ fs::copy_options::overwrite_existing,
518+ ec);
519+ if (ec) {
520+ // Clean up the revision file we created
521+ fs::remove (newRevisionPath);
522+ throw std::runtime_error (
523+ " Failed to create new revision for rollback: " + ec.message ());
524+ }
525+
526+ // Atomically update the symlink to point to the new revision
527+ atomicSymlinkUpdate (systemConfigPath_, newRevisionPath);
528+
529+ // Reload the config - if this fails, atomically undo the rollback
530+ try {
531+ auto client =
532+ utils::createClient<facebook::fboss::FbossCtrlAsyncClient>(hostInfo);
533+ client->sync_reloadConfig ();
534+ } catch (const std::exception& ex) {
535+ // Rollback: atomically restore the old symlink
536+ try {
537+ atomicSymlinkUpdate (systemConfigPath_, oldSymlinkTarget);
538+ } catch (const std::exception& rollbackEx) {
539+ // If rollback also fails, include both errors in the message
540+ throw std::runtime_error (
541+ std::string (" Failed to reload config: " ) + ex.what () +
542+ " . Additionally, failed to rollback the symlink: " +
543+ rollbackEx.what ());
544+ }
545+ throw std::runtime_error (
546+ std::string (
547+ " Failed to reload config, symlink was rolled back automatically: " ) +
548+ ex.what ());
549+ }
550+
551+ // Successfully rolled back
552+ int newRevision = extractRevisionNumber (
553+ newRevisionPath); // Kinda ugly to get the number here this way.
554+ LOG (INFO) << " Rollback committed as revision r" << newRevision;
555+ return newRevision;
556+ }
557+
444558} // namespace facebook::fboss
0 commit comments