Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cmake/CliFboss2.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,8 @@ add_library(fboss2_config_lib
fboss/cli/fboss2/commands/config/CmdConfigAppliedInfo.cpp
fboss/cli/fboss2/commands/config/CmdConfigReload.h
fboss/cli/fboss2/commands/config/CmdConfigReload.cpp
fboss/cli/fboss2/commands/config/rollback/CmdConfigRollback.h
fboss/cli/fboss2/commands/config/rollback/CmdConfigRollback.cpp
fboss/cli/fboss2/commands/config/session/CmdConfigSessionCommit.h
fboss/cli/fboss2/commands/config/session/CmdConfigSessionCommit.cpp
fboss/cli/fboss2/commands/config/session/CmdConfigSessionDiff.h
Expand Down
2 changes: 2 additions & 0 deletions fboss/cli/fboss2/BUCK
Original file line number Diff line number Diff line change
Expand Up @@ -771,13 +771,15 @@ cpp_library(
"CmdListConfig.cpp",
"commands/config/CmdConfigAppliedInfo.cpp",
"commands/config/CmdConfigReload.cpp",
"commands/config/rollback/CmdConfigRollback.cpp",
"commands/config/session/CmdConfigSessionCommit.cpp",
"commands/config/session/CmdConfigSessionDiff.cpp",
"session/ConfigSession.cpp",
],
headers = [
"commands/config/CmdConfigAppliedInfo.h",
"commands/config/CmdConfigReload.h",
"commands/config/rollback/CmdConfigRollback.h",
"commands/config/session/CmdConfigSessionCommit.h",
"commands/config/session/CmdConfigSessionDiff.h",
"session/ConfigSession.h",
Expand Down
2 changes: 2 additions & 0 deletions fboss/cli/fboss2/CmdHandlerImplConfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

#include "fboss/cli/fboss2/commands/config/CmdConfigAppliedInfo.h"
#include "fboss/cli/fboss2/commands/config/CmdConfigReload.h"
#include "fboss/cli/fboss2/commands/config/rollback/CmdConfigRollback.h"
#include "fboss/cli/fboss2/commands/config/session/CmdConfigSessionCommit.h"
#include "fboss/cli/fboss2/commands/config/session/CmdConfigSessionDiff.h"

Expand All @@ -20,6 +21,7 @@ namespace facebook::fboss {
template void
CmdHandler<CmdConfigAppliedInfo, CmdConfigAppliedInfoTraits>::run();
template void CmdHandler<CmdConfigReload, CmdConfigReloadTraits>::run();
template void CmdHandler<CmdConfigRollback, CmdConfigRollbackTraits>::run();
template void
CmdHandler<CmdConfigSessionCommit, CmdConfigSessionCommitTraits>::run();
template void
Expand Down
7 changes: 7 additions & 0 deletions fboss/cli/fboss2/CmdListConfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "fboss/cli/fboss2/CmdHandler.h"
#include "fboss/cli/fboss2/commands/config/CmdConfigAppliedInfo.h"
#include "fboss/cli/fboss2/commands/config/CmdConfigReload.h"
#include "fboss/cli/fboss2/commands/config/rollback/CmdConfigRollback.h"
#include "fboss/cli/fboss2/commands/config/session/CmdConfigSessionCommit.h"
#include "fboss/cli/fboss2/commands/config/session/CmdConfigSessionDiff.h"

Expand Down Expand Up @@ -49,6 +50,12 @@ const CommandTree& kConfigCommandTree() {
"Reload agent configuration",
commandHandler<CmdConfigReload>,
argTypeHandler<CmdConfigReloadTraits>},

{"config",
"rollback",
"Rollback to a previous config revision",
commandHandler<CmdConfigRollback>,
argTypeHandler<CmdConfigRollbackTraits>},
};
sort(root.begin(), root.end());
return root;
Expand Down
57 changes: 57 additions & 0 deletions fboss/cli/fboss2/commands/config/rollback/CmdConfigRollback.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright (c) 2004-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/

#include "fboss/cli/fboss2/commands/config/rollback/CmdConfigRollback.h"
#include "fboss/cli/fboss2/session/ConfigSession.h"

namespace facebook::fboss {

CmdConfigRollbackTraits::RetType CmdConfigRollback::queryClient(
const HostInfo& hostInfo,
const utils::RevisionList& revisions) {
auto& session = ConfigSession::getInstance();

// Validate arguments
if (revisions.size() > 1) {
throw std::invalid_argument(
"Too many arguments. Expected 0 or 1 revision specifier.");
}

if (!revisions.empty() && revisions[0] == "current") {
throw std::invalid_argument(
"Cannot rollback to 'current'. Please specify a revision number like 'r42'.");
}

try {
int newRevision;
if (revisions.empty()) {
// No revision specified - rollback to previous revision
newRevision = session.rollback(hostInfo);
} else {
// Specific revision specified
newRevision = session.rollback(hostInfo, revisions[0]);
}
if (newRevision) {
return "Successfully rolled back to r" + std::to_string(newRevision) +
" and config reloaded.";
} else {
return "Failed to create a new revision after rollback.";
}
} catch (const std::exception& ex) {
throw std::runtime_error(
"Failed to rollback config: " + std::string(ex.what()));
}
}

void CmdConfigRollback::printOutput(const RetType& logMsg) {
std::cout << logMsg << std::endl;
}

} // namespace facebook::fboss
39 changes: 39 additions & 0 deletions fboss/cli/fboss2/commands/config/rollback/CmdConfigRollback.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright (c) 2004-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/

#pragma once

#include "fboss/cli/fboss2/CmdHandler.h"
#include "fboss/cli/fboss2/utils/CmdClientUtils.h"
#include "fboss/cli/fboss2/utils/CmdUtils.h"

namespace facebook::fboss {

struct CmdConfigRollbackTraits : public WriteCommandTraits {
static constexpr utils::ObjectArgTypeId ObjectArgTypeId =
utils::ObjectArgTypeId::OBJECT_ARG_TYPE_ID_REVISION_LIST;
using ObjectArgType = utils::RevisionList;
using RetType = std::string;
};

class CmdConfigRollback
: public CmdHandler<CmdConfigRollback, CmdConfigRollbackTraits> {
public:
using ObjectArgType = CmdConfigRollbackTraits::ObjectArgType;
using RetType = CmdConfigRollbackTraits::RetType;

RetType queryClient(
const HostInfo& hostInfo,
const utils::RevisionList& revisions);

void printOutput(const RetType& logMsg);
};

} // namespace facebook::fboss
115 changes: 115 additions & 0 deletions fboss/cli/fboss2/session/ConfigSession.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,25 @@ void ensureDirectoryExists(const std::string& dirPath) {
}
}

/*
* Get the current revision number by reading the symlink target.
* Returns -1 if unable to determine the current revision.
*/
int getCurrentRevisionNumber(const std::string& systemConfigPath) {
std::error_code ec;

if (!fs::is_symlink(systemConfigPath, ec)) {
return -1;
}

std::string target = fs::read_symlink(systemConfigPath, ec);
if (ec) {
return -1;
}

return ConfigSession::extractRevisionNumber(target);
}

} // anonymous namespace

ConfigSession::ConfigSession() {
Expand Down Expand Up @@ -456,4 +475,100 @@ int ConfigSession::commit(const HostInfo& hostInfo) {
return revision;
}

int ConfigSession::rollback(const HostInfo& hostInfo) {
// Get the current revision number
int currentRevision = getCurrentRevisionNumber(systemConfigPath_);
if (currentRevision <= 0) {
throw std::runtime_error(
"Cannot rollback: cannot determine the current revision from " +
systemConfigPath_);
} else if (currentRevision == 1) {
throw std::runtime_error(
"Cannot rollback: already at the first revision (r1)");
}

// Rollback to the previous revision
std::string targetRevision = "r" + std::to_string(currentRevision - 1);
return rollback(hostInfo, targetRevision);
}

int ConfigSession::rollback(
const HostInfo& hostInfo,
const std::string& revision) {
ensureDirectoryExists(cliConfigDir_);

// Build the path to the target revision
std::string targetConfigPath = cliConfigDir_ + "/agent-" + revision + ".conf";

// Check if the target revision exists
if (!fs::exists(targetConfigPath)) {
throw std::runtime_error(
"Revision " + revision + " does not exist at " + targetConfigPath);
}

std::error_code ec;

// Verify that the system config is a symlink
if (!fs::is_symlink(systemConfigPath_)) {
throw std::runtime_error(
systemConfigPath_ + " is not a symlink. Expected it to be a symlink.");
}

// Read the old symlink target in case we need to undo the rollback
std::string oldSymlinkTarget = fs::read_symlink(systemConfigPath_, ec);
if (ec) {
throw std::runtime_error(
"Failed to read symlink " + systemConfigPath_ + ": " + ec.message());
}

// First, create a new revision with the same content as the target revision
auto [newRevisionPath, newRevision] =
createNextRevisionFile(fmt::format("{}/agent", cliConfigDir_));

// Copy the target config to the new revision file
fs::copy_file(
targetConfigPath,
newRevisionPath,
fs::copy_options::overwrite_existing,
ec);
if (ec) {
// Clean up the revision file we created
fs::remove(newRevisionPath);
throw std::runtime_error(
fmt::format(
"Failed to create new revision for rollback: {}", ec.message()));
}

// Atomically update the symlink to point to the new revision
atomicSymlinkUpdate(systemConfigPath_, newRevisionPath);

// Reload the config - if this fails, atomically undo the rollback
try {
auto client =
utils::createClient<apache::thrift::Client<facebook::fboss::FbossCtrl>>(
hostInfo);
client->sync_reloadConfig();
} catch (const std::exception& ex) {
// Rollback: atomically restore the old symlink
try {
atomicSymlinkUpdate(systemConfigPath_, oldSymlinkTarget);
} catch (const std::exception& rollbackEx) {
// If rollback also fails, include both errors in the message
throw std::runtime_error(
fmt::format(
"Failed to reload config: {}. Additionally, failed to rollback the symlink: {}",
ex.what(),
rollbackEx.what()));
}
throw std::runtime_error(
fmt::format(
"Failed to reload config, symlink was rolled back automatically: {}",
ex.what()));
}

// Successfully rolled back
LOG(INFO) << "Rollback committed as revision r" << newRevision;
return newRevision;
}

} // namespace facebook::fboss
5 changes: 5 additions & 0 deletions fboss/cli/fboss2/session/ConfigSession.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,11 @@ class ConfigSession {
// successful.
int commit(const HostInfo& hostInfo);

// Rollback to a specific revision or to the previous revision
// Returns the revision that was rolled back to
int rollback(const HostInfo& hostInfo);
int rollback(const HostInfo& hostInfo, const std::string& revision);

// Check if a session exists
bool sessionExists() const;

Expand Down
Loading
Loading