Skip to content

Commit 0215db8

Browse files
Add a fboss2 config rollback command.
1 parent cecc63e commit 0215db8

File tree

9 files changed

+335
-0
lines changed

9 files changed

+335
-0
lines changed

cmake/CliFboss2.cmake

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,8 @@ add_library(fboss2_config_lib
575575
fboss/cli/fboss2/commands/config/CmdConfigAppliedInfo.cpp
576576
fboss/cli/fboss2/commands/config/CmdConfigReload.h
577577
fboss/cli/fboss2/commands/config/CmdConfigReload.cpp
578+
fboss/cli/fboss2/commands/config/rollback/CmdConfigRollback.h
579+
fboss/cli/fboss2/commands/config/rollback/CmdConfigRollback.cpp
578580
fboss/cli/fboss2/commands/config/session/CmdConfigSessionCommit.h
579581
fboss/cli/fboss2/commands/config/session/CmdConfigSessionCommit.cpp
580582
fboss/cli/fboss2/commands/config/session/CmdConfigSessionDiff.h

fboss/cli/fboss2/BUCK

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -766,6 +766,7 @@ cpp_library(
766766
"CmdListConfig.cpp",
767767
"commands/config/CmdConfigAppliedInfo.cpp",
768768
"commands/config/CmdConfigReload.cpp",
769+
"commands/config/rollback/CmdConfigRollback.cpp",
769770
"commands/config/session/CmdConfigSessionCommit.cpp",
770771
"commands/config/session/CmdConfigSessionDiff.cpp",
771772
"session/ConfigSession.cpp",
@@ -774,6 +775,7 @@ cpp_library(
774775
headers = [
775776
"commands/config/CmdConfigAppliedInfo.h",
776777
"commands/config/CmdConfigReload.h",
778+
"commands/config/rollback/CmdConfigRollback.h",
777779
"commands/config/session/CmdConfigSessionCommit.h",
778780
"commands/config/session/CmdConfigSessionDiff.h",
779781
"session/ConfigSession.h",

fboss/cli/fboss2/CmdHandlerImplConfig.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
#include "fboss/cli/fboss2/commands/config/CmdConfigAppliedInfo.h"
1414
#include "fboss/cli/fboss2/commands/config/CmdConfigReload.h"
15+
#include "fboss/cli/fboss2/commands/config/rollback/CmdConfigRollback.h"
1516
#include "fboss/cli/fboss2/commands/config/session/CmdConfigSessionCommit.h"
1617
#include "fboss/cli/fboss2/commands/config/session/CmdConfigSessionDiff.h"
1718

@@ -20,6 +21,7 @@ namespace facebook::fboss {
2021
template void
2122
CmdHandler<CmdConfigAppliedInfo, CmdConfigAppliedInfoTraits>::run();
2223
template void CmdHandler<CmdConfigReload, CmdConfigReloadTraits>::run();
24+
template void CmdHandler<CmdConfigRollback, CmdConfigRollbackTraits>::run();
2325
template void
2426
CmdHandler<CmdConfigSessionCommit, CmdConfigSessionCommitTraits>::run();
2527
template void

fboss/cli/fboss2/CmdListConfig.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "fboss/cli/fboss2/CmdHandler.h"
1414
#include "fboss/cli/fboss2/commands/config/CmdConfigAppliedInfo.h"
1515
#include "fboss/cli/fboss2/commands/config/CmdConfigReload.h"
16+
#include "fboss/cli/fboss2/commands/config/rollback/CmdConfigRollback.h"
1617
#include "fboss/cli/fboss2/commands/config/session/CmdConfigSessionCommit.h"
1718
#include "fboss/cli/fboss2/commands/config/session/CmdConfigSessionDiff.h"
1819

@@ -49,6 +50,12 @@ const CommandTree& kConfigCommandTree() {
4950
"Reload agent configuration",
5051
commandHandler<CmdConfigReload>,
5152
argTypeHandler<CmdConfigReloadTraits>},
53+
54+
{"config",
55+
"rollback",
56+
"Rollback to a previous config revision",
57+
commandHandler<CmdConfigRollback>,
58+
argTypeHandler<CmdConfigRollbackTraits>},
5259
};
5360
sort(root.begin(), root.end());
5461
return root;
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright (c) 2004-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*
9+
*/
10+
11+
#include "fboss/cli/fboss2/commands/config/rollback/CmdConfigRollback.h"
12+
#include "fboss/cli/fboss2/session/ConfigSession.h"
13+
14+
namespace facebook::fboss {
15+
16+
CmdConfigRollbackTraits::RetType CmdConfigRollback::queryClient(
17+
const HostInfo& hostInfo,
18+
const utils::RevisionList& revisions) {
19+
auto& session = ConfigSession::getInstance();
20+
21+
// Validate arguments
22+
if (revisions.size() > 1) {
23+
throw std::invalid_argument(
24+
"Too many arguments. Expected 0 or 1 revision specifier.");
25+
}
26+
27+
if (!revisions.empty() && revisions[0] == "current") {
28+
throw std::invalid_argument(
29+
"Cannot rollback to 'current'. Please specify a revision number like 'r42'.");
30+
}
31+
32+
try {
33+
int newRevision;
34+
if (revisions.empty()) {
35+
// No revision specified - rollback to previous revision
36+
newRevision = session.rollback(hostInfo);
37+
} else {
38+
// Specific revision specified
39+
newRevision = session.rollback(hostInfo, revisions[0]);
40+
}
41+
if (newRevision) {
42+
return "Successfully rolled back to r" + std::to_string(newRevision) +
43+
" and config reloaded.";
44+
} else {
45+
return "Failed to create a new revision after rollback.";
46+
}
47+
} catch (const std::exception& ex) {
48+
throw std::runtime_error(
49+
"Failed to rollback config: " + std::string(ex.what()));
50+
}
51+
}
52+
53+
void CmdConfigRollback::printOutput(const RetType& logMsg) {
54+
std::cout << logMsg << std::endl;
55+
}
56+
57+
} // namespace facebook::fboss
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright (c) 2004-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*
9+
*/
10+
11+
#pragma once
12+
13+
#include "fboss/cli/fboss2/CmdHandler.h"
14+
#include "fboss/cli/fboss2/utils/CmdClientUtils.h"
15+
#include "fboss/cli/fboss2/utils/CmdUtils.h"
16+
17+
namespace facebook::fboss {
18+
19+
struct CmdConfigRollbackTraits : public WriteCommandTraits {
20+
static constexpr utils::ObjectArgTypeId ObjectArgTypeId =
21+
utils::ObjectArgTypeId::OBJECT_ARG_TYPE_ID_REVISION_LIST;
22+
using ObjectArgType = utils::RevisionList;
23+
using RetType = std::string;
24+
};
25+
26+
class CmdConfigRollback
27+
: public CmdHandler<CmdConfigRollback, CmdConfigRollbackTraits> {
28+
public:
29+
using ObjectArgType = CmdConfigRollbackTraits::ObjectArgType;
30+
using RetType = CmdConfigRollbackTraits::RetType;
31+
32+
RetType queryClient(
33+
const HostInfo& hostInfo,
34+
const utils::RevisionList& revisions);
35+
36+
void printOutput(const RetType& logMsg);
37+
};
38+
39+
} // namespace facebook::fboss

fboss/cli/fboss2/session/ConfigSession.cpp

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

163182
ConfigSession::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

fboss/cli/fboss2/session/ConfigSession.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,11 @@ class ConfigSession {
102102
// successful.
103103
int commit(const HostInfo& hostInfo);
104104

105+
// Rollback to a specific revision or to the previous revision
106+
// Returns the revision that was rolled back to
107+
int rollback(const HostInfo& hostInfo);
108+
int rollback(const HostInfo& hostInfo, const std::string& revision);
109+
105110
// Check if a session exists
106111
bool sessionExists() const;
107112

0 commit comments

Comments
 (0)