sonic-db-cli/sonic-db-cli.cpp (328 lines of code) (raw):
#include <future>
#include <iostream>
#include <getopt.h>
#include <list>
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/find.hpp>
#include "common/redisreply.h"
#include "sonic-db-cli.h"
using namespace swss;
using namespace std;
void printUsage()
{
cout << "usage: sonic-db-cli [-h] [-s] [-n NAMESPACE] db_or_op [cmd [cmd ...]]" << endl;
cout << endl;
cout << "SONiC DB CLI:" << endl;
cout << endl;
cout << "positional arguments:" << endl;
cout << " db_or_op Database name Or Unary operation(only PING/SAVE/FLUSHALL supported)" << endl;
cout << " cmd Command to execute in database" << endl;
cout << endl;
cout << "optional arguments:" << endl;
cout << " -h, --help show this help message and exit" << endl;
cout << " -s, --unixsocket Override use of tcp_port and use unixsocket" << endl;
cout << " -n NAMESPACE, --namespace NAMESPACE" << endl;
cout << " Namespace string to use asic0/asic1.../asicn" << endl;
cout << endl;
cout << "**sudo** needed for commands accesing a different namespace [-n], or using unixsocket connection [-s]" << endl;
cout << endl;
cout << "Example 1: sonic-db-cli -n asic0 CONFIG_DB keys \\*" << endl;
cout << "Example 2: sonic-db-cli -n asic2 APPL_DB HGETALL VLAN_TABLE:Vlan10" << endl;
cout << "Example 3: sonic-db-cli APPL_DB HGET VLAN_TABLE:Vlan10 mtu" << endl;
cout << "Example 4: sonic-db-cli -n asic3 APPL_DB EVAL \"return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}\" 2 k1 k2 v1 v2" << endl;
cout << "Example 5: sonic-db-cli PING | sonic-db-cli -s PING" << endl;
cout << "Example 6: sonic-db-cli SAVE | sonic-db-cli -s SAVE" << endl;
cout << "Example 7: sonic-db-cli FLUSHALL | sonic-db-cli -s FLUSHALL" << endl;
}
string handleSingleOperation(
const string& netns,
const string& db_name,
const string& operation,
bool useUnixSocket)
{
shared_ptr<DBConnector> client;
auto host = SonicDBConfig::getDbHostname(db_name, netns);
string message = "Could not connect to Redis at " + host + ":";
try
{
auto db_id = SonicDBConfig::getDbId(db_name, netns);
if (useUnixSocket && host != "redis_chassis.server")
{
auto db_socket = SonicDBConfig::getDbSock(db_name, netns);
message += db_name + ": Connection refused";
client = make_shared<DBConnector>(db_id, db_socket, 0);
}
else
{
auto port = SonicDBConfig::getDbPort(db_name, netns);
message += port + ": Connection refused";
client = make_shared<DBConnector>(db_id, host, port, 0);
}
}
catch (const exception& e)
{
return message;
}
if (operation == "PING"
|| operation == "SAVE"
|| operation == "FLUSHALL")
{
RedisReply reply(client.get(), operation);
auto response = reply.getContext();
if (nullptr != response)
{
return string();
}
}
else
{
throw std::invalid_argument("Operation " + operation +" is not supported");
}
return message;
}
int handleAllInstances(
const string& netns,
const string& operation,
bool useUnixSocket)
{
auto db_names = SonicDBConfig::getDbList(netns);
// Operate All Redis Instances in Parallel
// TODO: if one of the operations failed, it could fail quickly and not necessary to wait all other operations
list<future<string>> responses;
for (auto& db_name : db_names)
{
future<string> response = std::async(std::launch::async, handleSingleOperation, netns, db_name, operation, useUnixSocket);
responses.push_back(std::move(response));
}
bool operation_failed = false;
for (auto& response : responses)
{
auto respstr = response.get();
if (respstr != "")
{
cout << respstr << endl;
operation_failed = true;
}
}
if (operation_failed)
{
return 1;
}
if (operation == "PING")
{
cout << "PONG" << endl;
}
else
{
cout << "OK" << endl;
}
return 0;
}
int executeCommands(
const string& db_name,
vector<string>& commands,
const string& netns,
bool useUnixSocket)
{
shared_ptr<DBConnector> client = nullptr;
try
{
int db_id = SonicDBConfig::getDbId(db_name, netns);
auto host = SonicDBConfig::getDbHostname(db_name, netns);
if (useUnixSocket && host != "redis_chassis.server")
{
auto db_socket = SonicDBConfig::getDbSock(db_name, netns);
client = make_shared<DBConnector>(db_id, db_socket, 0);
}
else
{
auto port = SonicDBConfig::getDbPort(db_name, netns);
client = make_shared<DBConnector>(db_id, host, port, 0);
}
}
catch (const exception& e)
{
cerr << "Invalid database name input : '" << db_name << "'" << endl;
cerr << e.what() << endl;
return 1;
}
try
{
RedisCommand command;
command.format(commands);
RedisReply reply(client.get(), command);
/*
sonic-db-cli output format mimic the non-tty mode output format from redis-cli
based on our usage in SONiC, None and list type output from python API needs to be modified
with these changes, it is enough for us to mimic redis-cli in SONiC so far since no application uses tty mode redis-cli output
*/
auto commandName = getCommandName(commands);
cout << RedisReply::to_string(reply.getContext(), commandName) << endl;
}
catch (const std::system_error& e)
{
cerr << e.what() << endl;
return 1;
}
return 0;
}
void parseCliArguments(
int argc,
char** argv,
Options &options)
{
// Parse argument with getopt https://man7.org/linux/man-pages/man3/getopt.3.html
const char* short_options = "hsn";
static struct option long_options[] = {
{"help", optional_argument, NULL, 'h' },
{"unixsocket", optional_argument, NULL, 's' },
{"namespace", optional_argument, NULL, 'n' },
// The last element of the array has to be filled with zeros.
{0, 0, 0, 0 }
};
// prevent getopt_long print "invalid option" message.
opterr = 0;
while(optind < argc)
{
int opt = getopt_long(argc, argv, short_options, long_options, NULL);
if (opt != -1)
{
switch (opt) {
case 'h':
options.m_help = true;
break;
case 's':
options.m_unixsocket = true;
break;
case 'n':
if (optind < argc)
{
options.m_namespace = argv[optind];
optind++;
}
else
{
throw invalid_argument("namespace value is missing.");
}
break;
default:
// argv contains unknown argument
throw invalid_argument("Unknown argument:" + string(argv[optind]));
}
}
else
{
// db_or_op and cmd are non-option arguments
options.m_db_or_op = argv[optind];
optind++;
while(optind < argc)
{
auto cmdstr = string(argv[optind]);
options.m_cmd.push_back(cmdstr);
optind++;
}
}
}
}
int sonic_db_cli(
int argc,
char** argv,
function<void()> initializeGlobalConfig,
function<void()> initializeConfig)
{
Options options;
try
{
parseCliArguments(argc, argv, options);
}
catch (invalid_argument const& e)
{
cerr << "Command Line Error: " << e.what() << endl;
printUsage();
return -1;
}
catch (logic_error const& e)
{
// getopt_long throw logic_error when found a unknown option without value.
cerr << "Unknown option without value: " << e.what() << endl;
printUsage();
return -1;
}
if (options.m_help)
{
printUsage();
return 0;
}
if (!options.m_db_or_op.empty())
{
auto dbOrOperation = options.m_db_or_op;
auto netns = options.m_namespace;
bool useUnixSocket = options.m_unixsocket;
// Load the database config for the namespace
if (!netns.empty())
{
initializeGlobalConfig();
// Use the unix domain connectivity if namespace not empty.
useUnixSocket = true;
}
if (options.m_cmd.size() != 0)
{
auto commands = options.m_cmd;
if (netns.empty())
{
initializeConfig();
}
return executeCommands(dbOrOperation, commands, netns, useUnixSocket);
}
else if (dbOrOperation == "PING"
|| dbOrOperation == "SAVE"
|| dbOrOperation == "FLUSHALL")
{
// redis-cli doesn't depend on database_config.json which could raise some exceptions
// sonic-db-cli catch all possible exceptions and handle it as a failure case which not return 'OK' or 'PONG'
try
{
if (netns.empty())
{
// When database_config.json does not exist, sonic-db-cli will ignore exception and return 1.
initializeConfig();
}
return handleAllInstances(netns, dbOrOperation, useUnixSocket);
}
catch (const exception& e)
{
cerr << "An exception of type " << e.what() << " occurred. Arguments:" << endl;
for (int idx = 0; idx < argc; idx++)
{
cerr << argv[idx] << " ";
}
cerr << endl;
return 1;
}
}
else
{
printUsage();
}
}
else
{
printUsage();
}
return 0;
}
int cli_exception_wrapper(
int argc,
char** argv,
function<void()> initializeGlobalConfig,
function<void()> initializeConfig)
{
try
{
return sonic_db_cli(
argc,
argv,
initializeGlobalConfig,
initializeConfig);
}
catch (const exception& e)
{
// sonic-db-cli is porting from python version.
// in python version, when any exception happen sonic-db-cli will crash but will not generate core dump file.
// catch all exception here to avoid a core dump file generated.
cerr << "An exception of type " << e.what() << " occurred. Arguments:" << endl;
for (int idx = 0; idx < argc; idx++)
{
cerr << argv[idx] << " ";
}
cerr << endl;
// when python version crash, exit code is 1.
return 1;
}
}
string getCommandName(vector<string>& commands)
{
if (commands.size() == 0)
{
return string();
}
return boost::to_upper_copy<string>(commands[0]);
}