Skip to content

Commit cecc63e

Browse files
Add a fboss2 config session diff command.
1 parent 6337d9c commit cecc63e

16 files changed

+665
-7
lines changed

cmake/CliFboss2.cmake

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,8 @@ add_library(fboss2_config_lib
577577
fboss/cli/fboss2/commands/config/CmdConfigReload.cpp
578578
fboss/cli/fboss2/commands/config/session/CmdConfigSessionCommit.h
579579
fboss/cli/fboss2/commands/config/session/CmdConfigSessionCommit.cpp
580+
fboss/cli/fboss2/commands/config/session/CmdConfigSessionDiff.h
581+
fboss/cli/fboss2/commands/config/session/CmdConfigSessionDiff.cpp
580582
fboss/cli/fboss2/session/ConfigSession.h
581583
fboss/cli/fboss2/session/ConfigSession.cpp
582584
fboss/cli/fboss2/CmdListConfig.cpp

cmake/CliFboss2Test.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ add_executable(fboss2_cmd_test
55
fboss/cli/fboss2/test/TestMain.cpp
66
fboss/cli/fboss2/test/CmdConfigAppliedInfoTest.cpp
77
fboss/cli/fboss2/test/CmdConfigReloadTest.cpp
8+
fboss/cli/fboss2/test/CmdConfigSessionDiffTest.cpp
89
fboss/cli/fboss2/test/CmdConfigSessionTest.cpp
910
fboss/cli/fboss2/test/CmdSetPortStateTest.cpp
1011
fboss/cli/fboss2/test/CmdShowAclTest.cpp

fboss/cli/fboss2/BUCK

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -767,13 +767,15 @@ cpp_library(
767767
"commands/config/CmdConfigAppliedInfo.cpp",
768768
"commands/config/CmdConfigReload.cpp",
769769
"commands/config/session/CmdConfigSessionCommit.cpp",
770+
"commands/config/session/CmdConfigSessionDiff.cpp",
770771
"session/ConfigSession.cpp",
771772
"utils/PortMap.cpp",
772773
],
773774
headers = [
774775
"commands/config/CmdConfigAppliedInfo.h",
775776
"commands/config/CmdConfigReload.h",
776777
"commands/config/session/CmdConfigSessionCommit.h",
778+
"commands/config/session/CmdConfigSessionDiff.h",
777779
"session/ConfigSession.h",
778780
"utils/PortMap.h",
779781
],

fboss/cli/fboss2/CmdHandlerImplConfig.cpp

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

1718
namespace facebook::fboss {
1819

@@ -21,5 +22,7 @@ CmdHandler<CmdConfigAppliedInfo, CmdConfigAppliedInfoTraits>::run();
2122
template void CmdHandler<CmdConfigReload, CmdConfigReloadTraits>::run();
2223
template void
2324
CmdHandler<CmdConfigSessionCommit, CmdConfigSessionCommitTraits>::run();
25+
template void
26+
CmdHandler<CmdConfigSessionDiff, CmdConfigSessionDiffTraits>::run();
2427

2528
} // namespace facebook::fboss

fboss/cli/fboss2/CmdListConfig.cpp

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "fboss/cli/fboss2/commands/config/CmdConfigAppliedInfo.h"
1515
#include "fboss/cli/fboss2/commands/config/CmdConfigReload.h"
1616
#include "fboss/cli/fboss2/commands/config/session/CmdConfigSessionCommit.h"
17+
#include "fboss/cli/fboss2/commands/config/session/CmdConfigSessionDiff.h"
1718

1819
namespace facebook::fboss {
1920

@@ -30,11 +31,17 @@ const CommandTree& kConfigCommandTree() {
3031
"session",
3132
"Manage config session",
3233
{{
33-
"commit",
34-
"Commit the current config session",
35-
commandHandler<CmdConfigSessionCommit>,
36-
argTypeHandler<CmdConfigSessionCommitTraits>,
37-
}},
34+
"commit",
35+
"Commit the current config session",
36+
commandHandler<CmdConfigSessionCommit>,
37+
argTypeHandler<CmdConfigSessionCommitTraits>,
38+
},
39+
{
40+
"diff",
41+
"Show diff between configs (session vs live, session vs revision, or revision vs revision)",
42+
commandHandler<CmdConfigSessionDiff>,
43+
argTypeHandler<CmdConfigSessionDiffTraits>,
44+
}},
3845
},
3946

4047
{"config",

fboss/cli/fboss2/CmdSubcommands.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,10 @@ CLI::App* CmdSubcommands::addCommand(
219219
case utils::ObjectArgTypeId::OBJECT_ARG_TYPE_FAN_PWM:
220220
subCmd->add_option("pwm", args, "Fan PWM (0..100) or 'disable'");
221221
break;
222+
case utils::ObjectArgTypeId::OBJECT_ARG_TYPE_ID_REVISION_LIST:
223+
subCmd->add_option(
224+
"revisions", args, "Revision(s) in the form 'rN' or 'current'");
225+
break;
222226
case utils::ObjectArgTypeId::OBJECT_ARG_TYPE_ID_UNINITIALIZE:
223227
case utils::ObjectArgTypeId::OBJECT_ARG_TYPE_ID_NONE:
224228
break;
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
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/session/CmdConfigSessionDiff.h"
12+
#include "fboss/cli/fboss2/session/ConfigSession.h"
13+
14+
#include <folly/Subprocess.h>
15+
#include <filesystem>
16+
17+
namespace fs = std::filesystem;
18+
19+
namespace facebook::fboss {
20+
21+
namespace {
22+
23+
// Helper function to resolve a revision specifier to a file path
24+
// Note: Revision format validation is done in RevisionList constructor
25+
std::string resolveRevisionPath(
26+
const std::string& revision,
27+
const std::string& cliConfigDir,
28+
const std::string& systemConfigPath) {
29+
if (revision == "current") {
30+
return systemConfigPath;
31+
}
32+
33+
// Build the path (revision is already validated to be in "rN" format)
34+
std::string revisionPath = cliConfigDir + "/agent-" + revision + ".conf";
35+
36+
// Check if the file exists
37+
if (!fs::exists(revisionPath)) {
38+
throw std::invalid_argument(
39+
"Revision " + revision + " does not exist at " + revisionPath);
40+
}
41+
42+
return revisionPath;
43+
}
44+
45+
// Helper function to execute diff and return the result
46+
std::string executeDiff(
47+
const std::string& path1,
48+
const std::string& path2,
49+
const std::string& label1,
50+
const std::string& label2) {
51+
try {
52+
folly::Subprocess proc(
53+
std::vector<std::string>{
54+
"/usr/bin/diff",
55+
"-u",
56+
"--label",
57+
label1,
58+
"--label",
59+
label2,
60+
path1,
61+
path2},
62+
folly::Subprocess::Options().pipeStdout().pipeStderr());
63+
64+
auto result = proc.communicate();
65+
int returnCode = proc.wait().exitStatus();
66+
67+
// diff returns 0 if files are identical, 1 if different, 2 on error
68+
if (returnCode == 0) {
69+
return "No differences between " + label1 + " and " + label2 + ".";
70+
} else if (returnCode == 1) {
71+
// Files differ - return the diff output
72+
return result.first;
73+
} else {
74+
// Error occurred
75+
throw std::runtime_error("diff command failed: " + result.second);
76+
}
77+
} catch (const std::exception& ex) {
78+
throw std::runtime_error(
79+
"Failed to execute diff command: " + std::string(ex.what()));
80+
}
81+
}
82+
83+
} // namespace
84+
85+
CmdConfigSessionDiffTraits::RetType CmdConfigSessionDiff::queryClient(
86+
const HostInfo& /* hostInfo */,
87+
const utils::RevisionList& revisions) {
88+
auto& session = ConfigSession::getInstance();
89+
90+
std::string systemConfigPath = session.getSystemConfigPath();
91+
std::string sessionConfigPath = session.getSessionConfigPath();
92+
std::string cliConfigDir = session.getCliConfigDir();
93+
94+
// Mode 1: No arguments - diff session vs current live config
95+
if (revisions.empty()) {
96+
if (!session.sessionExists()) {
97+
return "No config session exists. Make a config change first.";
98+
}
99+
100+
return executeDiff(
101+
systemConfigPath,
102+
sessionConfigPath,
103+
"current live config",
104+
"session config");
105+
}
106+
107+
// Mode 2: One argument - diff session vs specified revision
108+
if (revisions.size() == 1) {
109+
if (!session.sessionExists()) {
110+
return "No config session exists. Make a config change first.";
111+
}
112+
113+
std::string revisionPath =
114+
resolveRevisionPath(revisions[0], cliConfigDir, systemConfigPath);
115+
std::string label =
116+
revisions[0] == "current" ? "current live config" : revisions[0];
117+
118+
return executeDiff(
119+
revisionPath, sessionConfigPath, label, "session config");
120+
}
121+
122+
// Mode 3: Two arguments - diff between two revisions
123+
if (revisions.size() == 2) {
124+
std::string path1 =
125+
resolveRevisionPath(revisions[0], cliConfigDir, systemConfigPath);
126+
std::string path2 =
127+
resolveRevisionPath(revisions[1], cliConfigDir, systemConfigPath);
128+
129+
std::string label1 =
130+
revisions[0] == "current" ? "current live config" : revisions[0];
131+
std::string label2 =
132+
revisions[1] == "current" ? "current live config" : revisions[1];
133+
134+
return executeDiff(path1, path2, label1, label2);
135+
}
136+
137+
// More than 2 arguments is an error
138+
throw std::invalid_argument(
139+
"Too many arguments. Expected 0, 1, or 2 revision specifiers.");
140+
}
141+
142+
void CmdConfigSessionDiff::printOutput(const RetType& diffOutput) {
143+
std::cout << diffOutput;
144+
if (!diffOutput.empty() && diffOutput.back() != '\n') {
145+
std::cout << std::endl;
146+
}
147+
}
148+
149+
} // 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 CmdConfigSessionDiffTraits : 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 CmdConfigSessionDiff
27+
: public CmdHandler<CmdConfigSessionDiff, CmdConfigSessionDiffTraits> {
28+
public:
29+
using ObjectArgType = CmdConfigSessionDiffTraits::ObjectArgType;
30+
using RetType = CmdConfigSessionDiffTraits::RetType;
31+
32+
RetType queryClient(
33+
const HostInfo& hostInfo,
34+
const utils::RevisionList& revisions);
35+
36+
void printOutput(const RetType& diffOutput);
37+
};
38+
39+
} // namespace facebook::fboss

fboss/cli/fboss2/session/ConfigSession.cpp

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,10 +188,24 @@ ConfigSession::ConfigSession(
188188
initializeSession();
189189
}
190190

191-
ConfigSession& ConfigSession::getInstance() {
192-
static ConfigSession instance;
191+
namespace {
192+
std::unique_ptr<ConfigSession>& getInstancePtr() {
193+
static std::unique_ptr<ConfigSession> instance;
193194
return instance;
194195
}
196+
} // namespace
197+
198+
ConfigSession& ConfigSession::getInstance() {
199+
auto& instance = getInstancePtr();
200+
if (!instance) {
201+
instance = std::make_unique<ConfigSession>();
202+
}
203+
return *instance;
204+
}
205+
206+
void ConfigSession::setInstance(std::unique_ptr<ConfigSession> newInstance) {
207+
getInstancePtr() = std::move(newInstance);
208+
}
195209

196210
std::string ConfigSession::getSessionConfigPath() const {
197211
return sessionConfigPath_;
@@ -201,6 +215,10 @@ std::string ConfigSession::getSystemConfigPath() const {
201215
return systemConfigPath_;
202216
}
203217

218+
std::string ConfigSession::getCliConfigDir() const {
219+
return cliConfigDir_;
220+
}
221+
204222
bool ConfigSession::sessionExists() const {
205223
return fs::exists(sessionConfigPath_);
206224
}

fboss/cli/fboss2/session/ConfigSession.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ class ConfigSession {
9393
// Get the path to the system config file (/etc/coop/agent.conf)
9494
std::string getSystemConfigPath() const;
9595

96+
// Get the path to the CLI config directory (/etc/coop/cli)
97+
std::string getCliConfigDir() const;
98+
9699
// Atomically commit the session to /etc/coop/cli/agent-rN.conf,
97100
// update the symlink /etc/coop/agent.conf to point to it, and reload config.
98101
// Returns the revision number that was committed if the commit was
@@ -124,6 +127,9 @@ class ConfigSession {
124127
const std::string& systemConfigPath,
125128
const std::string& cliConfigDir);
126129

130+
// Set the singleton instance (for testing only)
131+
static void setInstance(std::unique_ptr<ConfigSession> instance);
132+
127133
private:
128134
std::string sessionConfigPath_;
129135
std::string systemConfigPath_;

0 commit comments

Comments
 (0)