Skip to content

Commit f6831c4

Browse files
Add a "fboss2 config session diff" command.
1 parent 4e3a7a0 commit f6831c4

File tree

16 files changed

+665
-7
lines changed

16 files changed

+665
-7
lines changed

cmake/CliFboss2.cmake

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,8 @@ add_library(fboss2_lib
378378
fboss/cli/fboss2/commands/config/CmdConfigReload.cpp
379379
fboss/cli/fboss2/commands/config/session/CmdConfigSessionCommit.h
380380
fboss/cli/fboss2/commands/config/session/CmdConfigSessionCommit.cpp
381+
fboss/cli/fboss2/commands/config/session/CmdConfigSessionDiff.h
382+
fboss/cli/fboss2/commands/config/session/CmdConfigSessionDiff.cpp
381383
fboss/cli/fboss2/session/ConfigSession.h
382384
fboss/cli/fboss2/session/ConfigSession.cpp
383385
fboss/cli/fboss2/CmdGlobalOptions.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
@@ -344,6 +344,8 @@ cpp_library(
344344
"commands/config/CmdConfigReload.cpp",
345345
"commands/config/session/CmdConfigSessionCommit.h",
346346
"commands/config/session/CmdConfigSessionCommit.cpp",
347+
"commands/config/session/CmdConfigSessionDiff.h",
348+
"commands/config/session/CmdConfigSessionDiff.cpp",
347349
"session/ConfigSession.h",
348350
"session/ConfigSession.cpp",
349351
"commands/create/facebook/CmdCreateConfig.h",

fboss/cli/fboss2/CmdHandlerImpl.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "fboss/cli/fboss2/commands/config/CmdConfigAppliedInfo.h"
2222
#include "fboss/cli/fboss2/commands/config/CmdConfigReload.h"
2323
#include "fboss/cli/fboss2/commands/config/session/CmdConfigSessionCommit.h"
24+
#include "fboss/cli/fboss2/commands/config/session/CmdConfigSessionDiff.h"
2425
#include "fboss/cli/fboss2/commands/get/pcap/CmdGetPcap.h"
2526
#include "fboss/cli/fboss2/commands/help/CmdHelp.h"
2627
#include "fboss/cli/fboss2/commands/set/interface/CmdSetInterface.h"
@@ -251,6 +252,8 @@ template void CmdHandler<CmdConfigReload, CmdConfigReloadTraits>::run();
251252
template void
252253
CmdHandler<CmdConfigSessionCommit, CmdConfigSessionCommitTraits>::run();
253254
template void
255+
CmdHandler<CmdConfigSessionDiff, CmdConfigSessionDiffTraits>::run();
256+
template void
254257
CmdHandler<CmdShowInterfaceStatus, CmdShowInterfaceStatusTraits>::run();
255258
template void CmdHandler<CmdBounceInterface, CmdBounceInterfaceTraits>::run();
256259
template void CmdHandler<CmdShowMplsRoute, CmdShowMplsRouteTraits>::run();

fboss/cli/fboss2/CmdList.cpp

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "fboss/cli/fboss2/commands/config/CmdConfigAppliedInfo.h"
2424
#include "fboss/cli/fboss2/commands/config/CmdConfigReload.h"
2525
#include "fboss/cli/fboss2/commands/config/session/CmdConfigSessionCommit.h"
26+
#include "fboss/cli/fboss2/commands/config/session/CmdConfigSessionDiff.h"
2627
#include "fboss/cli/fboss2/commands/get/pcap/CmdGetPcap.h"
2728
#include "fboss/cli/fboss2/commands/help/CmdHelp.h"
2829
#include "fboss/cli/fboss2/commands/set/interface/CmdSetInterface.h"
@@ -538,11 +539,17 @@ const CommandTree& kCommandTree() {
538539
"session",
539540
"Manage config session",
540541
{{
541-
"commit",
542-
"Commit the current config session",
543-
commandHandler<CmdConfigSessionCommit>,
544-
argTypeHandler<CmdConfigSessionCommitTraits>,
545-
}},
542+
"commit",
543+
"Commit the current config session",
544+
commandHandler<CmdConfigSessionCommit>,
545+
argTypeHandler<CmdConfigSessionCommitTraits>,
546+
},
547+
{
548+
"diff",
549+
"Show diff between configs (session vs live, session vs revision, or revision vs revision)",
550+
commandHandler<CmdConfigSessionDiff>,
551+
argTypeHandler<CmdConfigSessionDiffTraits>,
552+
}},
546553
},
547554

548555
{"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)