diff --git a/.gitignore b/.gitignore index ba3fa90eb4..2f4a570da2 100644 --- a/.gitignore +++ b/.gitignore @@ -43,6 +43,8 @@ deps/ teamsyncd/teamsyncd fpmsyncd/fpmsyncd intfsyncd/intfsyncd +cfgagent/cfgagent +cfgagent/cfgmgr neighsyncd/neighsyncd portsyncd/portsyncd orchagent/orchagent diff --git a/Makefile.am b/Makefile.am index 20553983e1..3811bdbc2b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,4 +1,4 @@ -SUBDIRS = fpmsyncd neighsyncd intfsyncd portsyncd orchagent swssconfig +SUBDIRS = fpmsyncd neighsyncd intfsyncd portsyncd orchagent swssconfig cfgagent if HAVE_LIBTEAM SUBDIRS += teamsyncd diff --git a/cfgagent/Makefile.am b/cfgagent/Makefile.am new file mode 100644 index 0000000000..cee96e91eb --- /dev/null +++ b/cfgagent/Makefile.am @@ -0,0 +1,18 @@ +INCLUDES = -I $(top_srcdir) +CFLAGS_SAI = -I /usr/include/sai + +bin_PROGRAMS = cfgagentd + +if DEBUG +DBGFLAGS = -ggdb -DDEBUG +else +DBGFLAGS = -g +endif + +cfgagentd_SOURCES = cfgagentd.cpp vlancfgagent.cpp portcfgagent.cpp intfcfgagent.cpp switchcfgagent.cpp cfgorch.cpp + +cfgagentd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) +cfgagentd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) +cfgagentd_LDADD = -lswsscommon + +SUBDIRS = cfgmgrtest diff --git a/cfgagent/cfgagentd.cpp b/cfgagent/cfgagentd.cpp new file mode 100644 index 0000000000..645a3d875e --- /dev/null +++ b/cfgagent/cfgagentd.cpp @@ -0,0 +1,188 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "dbconnector.h" +#include "select.h" +#include "exec.h" +#include "netdispatcher.h" +#include "producerstatetable.h" +#include "cfgagent/vlancfgagent.h" +#include "cfgagent/portcfgagent.h" +#include "cfgagent/intfcfgagent.h" +#include "cfgagent/switchcfgagent.h" + +using namespace std; +using namespace swss; + +/* select() function timeout retry time, in millisecond */ +#define SELECT_TIMEOUT 1000 + +MacAddress gMacAddress; +bool gInitDone = false; +bool gSwssCfgRecord = true; +ofstream gCfgRecordOfs; +string gCfgRecordFile; +/* Global database mutex */ +mutex gDbMutex; + +SwitchCfgAgent *gSwtichcfgagent; + +void usage() +{ + cout << "usage: cfgagent [-h] [-r record_type] [-d record_location] [-b batch_size]" << endl; + cout << " -h: display this message" << endl; + cout << " -r record_type: record orchagent logs with type (default 3)" << endl; + cout << " 0: do not record logs" << endl; + cout << " 1: record SwSS task sequence as swss.cfg.*.rec" << endl; + cout << " -d record_location: set record logs folder location (default .)" << endl; +} + +int main(int argc, char **argv) +{ + Logger::linkToDbNative("cfgagent"); + SWSS_LOG_ENTER(); + + int opt; + string record_location = "."; + bool teamd_init_done = false; + + while ((opt = getopt(argc, argv, "b:r:d:h")) != -1) + { + switch (opt) + { + case 'r': + if (!strcmp(optarg, "0")) + { + gSwssCfgRecord = false; + } + else if (!strcmp(optarg, "1")) + { + continue; /* default behavior */ + } + else + { + usage(); + exit(EXIT_FAILURE); + } + break; + case 'd': + record_location = optarg; + if (access(record_location.c_str(), W_OK)) + { + SWSS_LOG_ERROR("Failed to access writable directory %s", record_location.c_str()); + exit(EXIT_FAILURE); + } + break; + case 'h': + usage(); + exit(EXIT_SUCCESS); + default: /* '?' */ + exit(EXIT_FAILURE); + } + } + + SWSS_LOG_NOTICE("--- Starting cfgagentd ---"); + + /* Disable/enable SwSS cfg recording */ + if (gSwssCfgRecord) + { + gCfgRecordFile = record_location + "/" + "swss.cfg.rec"; + gCfgRecordOfs.open(gCfgRecordFile, std::ofstream::out | std::ofstream::app); + + if (!gCfgRecordOfs.is_open()) + { + SWSS_LOG_ERROR("Failed to open SwSS recording file %s", gCfgRecordFile.c_str()); + exit(EXIT_FAILURE); + } + } + gMacAddress = swss::exec("ip link show eth0 | grep ether | awk '{print $2}'"); + + try + { + vector cfg_vlan_tables = { + CFG_VLAN_TABLE_NAME, + CFG_VLAN_MEMBER_TABLE_NAME, + }; + + DBConnector cfgDb(CONFIG_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); + DBConnector appDb(APPL_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); + + Table porttableconsumer(&appDb, APP_PORT_TABLE_NAME); + Table lagtableconsumer(&appDb, APP_LAG_TABLE_NAME); + + PortCfgAgent portcfgagent(&cfgDb, &appDb, CFG_PORT_TABLE_NAME); + VlanCfgAgent vlancfgagent(&cfgDb, &appDb, cfg_vlan_tables); + IntfCfgAgent intfcfgagent(&cfgDb, &appDb, CFG_INTF_TABLE_NAME); + gSwtichcfgagent = new SwitchCfgAgent(&cfgDb, &appDb, CFG_SWITCH_TABLE_NAME); + std::vector cfgOrchList = {&vlancfgagent, &portcfgagent, &intfcfgagent, gSwtichcfgagent}; + + swss::Select s; + for (CfgOrch *o : cfgOrchList) + { + s.addSelectables(o->getSelectables()); + } + + gSwtichcfgagent->syncCfgDB(); + SWSS_LOG_NOTICE("starting main loop"); + + while (true) + { + Selectable *sel; + int fd, ret; + + ret = s.select(&sel, &fd, SELECT_TIMEOUT); + if (ret == Select::ERROR) + { + SWSS_LOG_NOTICE("Error: %s!\n", strerror(errno)); + continue; + } + if (ret == Select::TIMEOUT) + { + if (gInitDone == false) + { + vector temp; + if (porttableconsumer.get("ConfigDone", temp)) + { + gInitDone = true; + SWSS_LOG_NOTICE("Physical ports hostif init done\n"); + } + } + else + { + if (teamd_init_done == false) + { + vector temp; + if (lagtableconsumer.get("ConfigDone", temp)) + { + teamd_init_done = true; + SWSS_LOG_NOTICE("Teamd lag init done\n"); + vlancfgagent.syncCfgDB(); + /* Sync interface config after VLAN */ + intfcfgagent.syncCfgDB(); + } + } + } + continue; + } + + for (CfgOrch *o : cfgOrchList) + { + if (o->hasSelectable((Subscriber *)sel)) + { + o->execute(((Subscriber *)sel)->getTableName()); + } + } + } + } + catch(const std::exception &e) + { + SWSS_LOG_ERROR("Runtime error: %s", e.what()); + } + return 0; +} diff --git a/cfgagent/cfgmgrtest/Makefile.am b/cfgagent/cfgmgrtest/Makefile.am new file mode 100644 index 0000000000..ed2ee8600d --- /dev/null +++ b/cfgagent/cfgmgrtest/Makefile.am @@ -0,0 +1,16 @@ +INCLUDES = -I $(top_srcdir) + +bin_PROGRAMS = cfgmgr + +if DEBUG +DBGFLAGS = -ggdb -DDEBUG +else +DBGFLAGS = -g +endif + + +cfgmgr_SOURCES = cfgmgr.cpp vlancfgmgr.cpp portcfgmgr.cpp \ + lagcfgmgr.cpp intfcfgmgr.cpp switchcfgmgr.cpp +cfgmgr_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) +cfgmgr_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) +cfgmgr_LDADD = -lswsscommon \ No newline at end of file diff --git a/cfgagent/cfgmgrtest/cfgmgr.cpp b/cfgagent/cfgmgrtest/cfgmgr.cpp new file mode 100644 index 0000000000..8e50940828 --- /dev/null +++ b/cfgagent/cfgmgrtest/cfgmgr.cpp @@ -0,0 +1,64 @@ +#include "cfgmgr.h" + +using namespace std; + +[[ noreturn ]] void usage() +{ + std::cout << "Usage: cfgmgr OBJECT { COMMAND | help }" << std::endl + << "\t where OBJECT := { port | lag | vlan | intf | switch }\n" << std::endl; + exit(0); +} + +void incomplete_command(void) +{ + std::cout << "Command line is not complete. Try option \"help\" " << std::endl; + exit(-1); +} + +int matches(const char *cmd, const char *pattern) +{ + size_t len = strlen(cmd); + + if (len > strlen(pattern)) + return -1; + return memcmp(pattern, cmd, len); +} + +static int do_help(int argc, char **argv) +{ + usage(); +} + +static const struct cmd { + const char *cmd; + int (*func)(int argc, char **argv); +} cmds[] = { + { "port", do_port }, + { "lag", do_lag }, + { "intf", do_intf }, + { "vlan", do_vlan }, + { "switch", do_switch }, + { "help", do_help }, + { NULL, NULL } +}; + +static int do_cmd(const char *argv0, int argc, char **argv) +{ + const struct cmd *c; + + for (c = cmds; c->cmd; ++c) { + if (matches(argv0, c->cmd) == 0) + return c->func(argc-1, argv+1); + } + + cout << "Object " << argv0 << " is unknown, try \"cfgmgr help\".\n" << std::endl; + return -1; +} + +int main(int argc, char **argv) +{ + if (argc > 1) + return do_cmd(argv[1], argc-1, argv+1); + + usage(); +} diff --git a/cfgagent/cfgmgrtest/cfgmgr.h b/cfgagent/cfgmgrtest/cfgmgr.h new file mode 100644 index 0000000000..8e8f9e7705 --- /dev/null +++ b/cfgagent/cfgmgrtest/cfgmgr.h @@ -0,0 +1,26 @@ +#ifndef __CFGMGR__ +#define __CFGMGR__ + +#include +#include +#include +#include +#include +#include +#include + +extern int do_port(int argc, char **argv); +extern int do_lag(int argc, char **argv); +extern int do_intf(int argc, char **argv); +extern int do_vlan(int argc, char **argv); +extern int do_switch(int argc, char **argv); + +#define NEXT_ARG() do { argv++; if (--argc <= 0) incomplete_command(); } while(0) +#define NEXT_ARG_OK() (argc - 1 > 0) +#define NEXT_ARG_FWD() do { argv++; argc--; } while(0) +#define PREV_ARG() do { argv--; argc++; } while(0) + +extern void incomplete_command(void); +extern int matches(const char *cmd, const char *pattern); + +#endif \ No newline at end of file diff --git a/cfgagent/cfgmgrtest/intfcfgmgr.cpp b/cfgagent/cfgmgrtest/intfcfgmgr.cpp new file mode 100644 index 0000000000..c54e7626bb --- /dev/null +++ b/cfgagent/cfgmgrtest/intfcfgmgr.cpp @@ -0,0 +1,169 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "schema.h" +#include "exec.h" +#include "cfgmgr.h" +#include "intfcfgmgr.h" + +using namespace std; +using namespace swss; + +[[ noreturn ]] static void usage(std::string program, int status, std::string message) +{ + if (message.size() != 0) + { + std::cout << message << std::endl << std::endl; + } + std::cout << "Usage: " << program << " intf { add | del } PREFIX dev IFNAME\n" << std::endl + << "\t" << program << " intf show [ dev IFNAME ]" << std::endl; + + exit(status); +} + +int IntfCfgMgr::intf_modify(Operation cmd, int argc, char **argv) +{ + char *dev = NULL; + string key; + string prefix; + string scope = "global"; + string family = IPV4_NAME; + + if (argc <= 0) { + usage("cfgmgr", -1, "Invalid option"); + } + + // TODO: more validity check on prefix. + prefix = *argv; + + NEXT_ARG(); + while (argc > 0) { + if (matches(*argv, "dev") == 0) { + NEXT_ARG(); + dev = *argv; + } else { + if (matches(*argv, "help") == 0) { + usage("cfgmgr", 0, ""); + } + } + argc--; argv++; + } + + if (dev == NULL) { + cout << "dev IFNAME is required arguments" << endl; + usage("cfgmgr", -1, "Invalid option"); + } + + if (if_nametoindex(dev) == 0) { + cout << "Cannot find device " << dev << " : " << std::strerror(errno) << endl; + return -1; + } + + key = dev; + key += ":"; + key += prefix; + + if (cmd == IntfCfgMgr::DELETE) + { + m_intfTableProducer.del(key); + } + else + { + std::vector fvVector; + FieldValueTuple f("family", family); + FieldValueTuple s("scope", scope); + fvVector.push_back(s); + fvVector.push_back(f); + m_intfTableProducer.set(key, fvVector); + } + return 0; +} + +int IntfCfgMgr::intf_show(int argc, char **argv) +{ + char *filter_dev = NULL; + unsigned int if_index; + string cmd = "ip address show "; + string redis_cmd_db = "redis-cli -n "; + string redis_cmd_keys = "\\*INTF\\*"; + string redis_cmd; + short vid = -1; + + while (argc > 0) { + if (matches(*argv, "dev") == 0) { + NEXT_ARG(); + filter_dev = *argv; + } + argc--; argv++; + } + + redis_cmd_db += std::to_string(CONFIG_DB) + " "; + + if (vid >= 0) { + redis_cmd_keys += std::to_string(vid) + "\\*"; + } + + if (filter_dev) { + if_index = if_nametoindex(filter_dev); + if (if_index == 0) { + cout << "Cannot find bridge device " << filter_dev << " : " << std::strerror(errno) << endl; + return -1; + } + cmd += " dev "; + cmd += filter_dev; + + redis_cmd_keys += filter_dev; + redis_cmd_keys += "\\*"; + } + redis_cmd = redis_cmd_db + " KEYS " + redis_cmd_keys; + redis_cmd += " | xargs -n 1 -I % sh -c 'echo %; "; + redis_cmd += redis_cmd_db + "hgetall %; echo'"; + + cout << "-----Redis CFGDB data---" << endl; + cout << redis_cmd < 0) + { + if (matches(*argv, "add") == 0) + return cfgmgr.intf_modify(IntfCfgMgr::ADD, argc-1, argv+1); + if (matches(*argv, "delete") == 0) + return cfgmgr.intf_modify(IntfCfgMgr::DELETE, argc-1, argv+1); + if (matches(*argv, "show") == 0) + return cfgmgr.intf_show(argc-1, argv+1); + if (matches(*argv, "help") == 0) + exitWithUsage(EXIT_SUCCESS, ""); + else + exitWithUsage(EXIT_FAILURE, "Invalid option"); + } + else { + exitWithUsage(EXIT_FAILURE, "Invalid option"); + } + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/cfgagent/cfgmgrtest/intfcfgmgr.h b/cfgagent/cfgmgrtest/intfcfgmgr.h new file mode 100644 index 0000000000..77cd310fdf --- /dev/null +++ b/cfgagent/cfgmgrtest/intfcfgmgr.h @@ -0,0 +1,30 @@ +#ifndef __INTFCFGMGR__ +#define __INTFCFGMGR__ + +#include "dbconnector.h" +#include "table.h" + +namespace swss { + +class IntfCfgMgr +{ +public: + enum Operation { + ADD, + DELETE, + SHOW, + } ; + enum { MAX_ADDR_SIZE = 64 }; + + IntfCfgMgr(DBConnector *db); + int intf_modify(Operation cmd, int argc, char **argv); + int intf_show(int argc, char **argv); + +private: + Table m_intfTableProducer; + Table m_intfTableConsumer; +}; + +} + +#endif diff --git a/cfgagent/cfgmgrtest/lagcfgmgr.cpp b/cfgagent/cfgmgrtest/lagcfgmgr.cpp new file mode 100644 index 0000000000..08bdfab3e2 --- /dev/null +++ b/cfgagent/cfgmgrtest/lagcfgmgr.cpp @@ -0,0 +1,20 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "schema.h" +#include "logger.h" +#include "dbconnector.h" +#include "redisclient.h" +#include "producerstatetable.h" +#include "exec.h" + +int do_lag(int argc, char **argv) +{ + return 0; +} \ No newline at end of file diff --git a/cfgagent/cfgmgrtest/portcfgmgr.cpp b/cfgagent/cfgmgrtest/portcfgmgr.cpp new file mode 100644 index 0000000000..88aded97ee --- /dev/null +++ b/cfgagent/cfgmgrtest/portcfgmgr.cpp @@ -0,0 +1,20 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "schema.h" +#include "logger.h" +#include "dbconnector.h" +#include "redisclient.h" +#include "producerstatetable.h" +#include "exec.h" + +int do_port(int argc, char **argv) +{ + return 0; +} \ No newline at end of file diff --git a/cfgagent/cfgmgrtest/switchcfgmgr.cpp b/cfgagent/cfgmgrtest/switchcfgmgr.cpp new file mode 100644 index 0000000000..cd557f029d --- /dev/null +++ b/cfgagent/cfgmgrtest/switchcfgmgr.cpp @@ -0,0 +1,162 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "schema.h" +#include "exec.h" +#include "cfgmgr.h" +#include "switchcfgmgr.h" + +using namespace std; +using namespace swss; + +#define CFG_SWITCH_FLOOD_CONTROL_KEY_NAME "FLOOD_CONTROL" + +[[ noreturn ]] static void usage(std::string program, int status, std::string message) +{ + if (message.size() != 0) + { + std::cout << message << std::endl << std::endl; + } + std::cout << "Usage: " << program << " switch update\n" << std::endl + << "\t\t [ unicast_miss_flood { true | false } ] \n" + << "\t\t [ multicast_miss_flood { true | false } ] \n" + << "\t\t [ broadcast_flood { true | false } ] \n" + << "\t" << program << " switch show [ dev DEV ]" << std::endl; + + exit(status); +} + +int SwitchCfgMgr::switch_update(int argc, char **argv) +{ + string key; + string unicast_miss_flood = "true"; + string multicast_miss_flood = "true"; + string broadcast_flood = "true"; + + if (argc <= 0) { + usage("cfgmgr", -1, "Invalid option"); + } + + while (argc > 0) { + if (matches(*argv, "unicast_miss_flood") == 0) { + NEXT_ARG(); + unicast_miss_flood = *argv; + } else if (matches(*argv, "multicast_miss_flood") == 0) { + NEXT_ARG(); + multicast_miss_flood = *argv; + } else if (matches(*argv, "broadcast_flood") == 0) { + NEXT_ARG(); + broadcast_flood = *argv; + } else { + if (matches(*argv, "help") == 0) { + usage("cfgmgr", 0, ""); + } + } + argc--; argv++; + } + + key = CFG_SWITCH_FLOOD_CONTROL_KEY_NAME; + + std::vector fvVector; + FieldValueTuple u("unicast_miss_flood", unicast_miss_flood); + FieldValueTuple m("multicast_miss_flood", multicast_miss_flood); + FieldValueTuple b("broadcast_flood", broadcast_flood); + fvVector.push_back(u); + fvVector.push_back(m); + fvVector.push_back(b); + m_switchTableProducer.set(key, fvVector); + + return 0; +} + +int SwitchCfgMgr::switch_show(int argc, char **argv) +{ + char *filter_dev = NULL; + unsigned int if_index; + string redis_cmd_db = "redis-cli -n "; + string redis_cmd_keys = "\\*SWITCH\\*"; + string redis_cmd; + string cmd ="ls /sys/class/net/Bridge/brif/*/*flood \ + | xargs -n 1 -I % sh -c 'echo -n % \" : \"; cat %;'"; + + while (argc > 0) { + if (matches(*argv, "dev") == 0) { + NEXT_ARG(); + filter_dev = *argv; + } + argc--; argv++; + } + + redis_cmd_db += std::to_string(CONFIG_DB) + " "; + + if (filter_dev) { + if_index = if_nametoindex(filter_dev); + if (if_index == 0) { + cout << "Cannot find bridge device " << filter_dev << " : " << std::strerror(errno) << endl; + return -1; + } + cmd += " | grep "; + cmd += filter_dev; + + // redis_cmd_keys += filter_dev; + // redis_cmd_keys += "\\*"; + } + + redis_cmd = redis_cmd_db + " KEYS " + redis_cmd_keys; + redis_cmd += " | xargs -n 1 -I % sh -c 'echo %; "; + redis_cmd += redis_cmd_db + "hgetall %'"; + + cout << "-----Redis CFGDB data---" << endl; + cout << redis_cmd < 0) + { + if (matches(*argv, "update") == 0) + return cfgmgr.switch_update(argc-1, argv+1); + if (matches(*argv, "show") == 0) + return cfgmgr.switch_show(argc-1, argv+1); + if (matches(*argv, "help") == 0) + exitWithUsage(EXIT_SUCCESS, ""); + else + exitWithUsage(EXIT_FAILURE, "Invalid option"); + } + else { + exitWithUsage(EXIT_FAILURE, "Invalid option"); + } + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/cfgagent/cfgmgrtest/switchcfgmgr.h b/cfgagent/cfgmgrtest/switchcfgmgr.h new file mode 100644 index 0000000000..d4daea4475 --- /dev/null +++ b/cfgagent/cfgmgrtest/switchcfgmgr.h @@ -0,0 +1,30 @@ +#ifndef __SWITCHCFGMGR__ +#define __SWITCHCFGMGR__ + +#include "dbconnector.h" +#include "table.h" + +namespace swss { + +class SwitchCfgMgr +{ +public: + enum Operation { + ADD, + DELETE, + SHOW, + } ; + enum { MAX_ADDR_SIZE = 64 }; + + SwitchCfgMgr(DBConnector *db); + int switch_update(int argc, char **argv); + int switch_show(int argc, char **argv); + +private: + Table m_switchTableProducer; + Table m_switchTableConsumer; +}; + +} + +#endif diff --git a/cfgagent/cfgmgrtest/vlancfgmgr.cpp b/cfgagent/cfgmgrtest/vlancfgmgr.cpp new file mode 100644 index 0000000000..f87952e1a2 --- /dev/null +++ b/cfgagent/cfgmgrtest/vlancfgmgr.cpp @@ -0,0 +1,261 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "schema.h" +#include "redisclient.h" +#include "exec.h" +#include "cfgmgr.h" +#include "vlancfgmgr.h" + +using namespace std; +using namespace swss; + +#define VLAN_PREFIX "Vlan" + +[[ noreturn ]] static void usage(std::string program, int status, std::string message) +{ + if (message.size() != 0) + { + std::cout << message << std::endl << std::endl; + } + //add/del vlan, add/del vlan member + std::cout << "Usage: " << program << " vlan { add | del } vlan VLAN_ID [ { down | up } ] \n" + << "\t\t [ mtu MTU ] \n" + << "\t\t [ unicast_miss_flood { true | false } ] \n" + << "\t\t [ multicast_miss_flood { true | false } ] \n" + << "\t\t [ broadcast_flood { true | false } ] \n" + << "\t\t [ desc DESCRIPTION] " << std::endl + << "\t" << program << " vlan { add | del } vlan VLAN_ID dev DEV [ pvid ] [ untagged ]" << std::endl + << "\t" << program << " vlan show [ dev DEV ] [ vlan VLAN_ID ]" << std::endl; + + exit(status); +} + + +#define BRIDGE_VLAN_INFO_MASTER (1<<0) /* Operate on Bridge device as well */ +#define BRIDGE_VLAN_INFO_PVID (1<<1) /* VLAN is PVID, ingress untagged */ +#define BRIDGE_VLAN_INFO_UNTAGGED (1<<2) /* VLAN egresses untagged */ +#define BRIDGE_VLAN_INFO_RANGE_BEGIN (1<<3) /* VLAN is start of vlan range */ +#define BRIDGE_VLAN_INFO_RANGE_END (1<<4) /* VLAN is end of vlan range */ +#define BRIDGE_VLAN_INFO_BRENTRY (1<<5) /* Global bridge VLAN entry */ + +int VlanCfgMgr::vlan_modify(Operation cmd, int argc, char **argv) +{ + char *dev = NULL; + int vid = -1; + unsigned short flags = 0; + unsigned int if_index; + string key = VLAN_PREFIX; + string admin = "up"; + unsigned int mtu = 1500; // for VLAN router interface. + string unicast_miss_flood = "true"; + string multicast_miss_flood = "true"; + string broadcast_flood = "true"; + string desc = ""; + + while (argc > 0) { + if (matches(*argv, "dev") == 0) { + NEXT_ARG(); + dev = *argv; + } else if (matches(*argv, "vlan") == 0) { + NEXT_ARG(); + vid = atoi(*argv); + } else if (matches(*argv, "mtu") == 0) { + NEXT_ARG(); + mtu = atoi(*argv); + } else if (matches(*argv, "pvid") == 0) { + flags |= BRIDGE_VLAN_INFO_PVID; + } else if (matches(*argv, "untagged") == 0) { + flags |= BRIDGE_VLAN_INFO_UNTAGGED; + } else if (matches(*argv, "down") == 0) { + admin = "down"; + } else if (matches(*argv, "unicast_miss_flood") == 0) { + NEXT_ARG(); + unicast_miss_flood = *argv; + } else if (matches(*argv, "multicast_miss_flood") == 0) { + NEXT_ARG(); + multicast_miss_flood = *argv; + } else if (matches(*argv, "broadcast_flood") == 0) { + NEXT_ARG(); + broadcast_flood = *argv; + } else if (matches(*argv, "desc") == 0) { + NEXT_ARG(); + desc = *argv; + } else { + if (matches(*argv, "help") == 0) { + usage("cfgmgr", 0, ""); + } + } + argc--; argv++; + } + + if (vid == -1) { + cout << "VLAN ID are required arguments" << endl; + return -1; + } + + if (vid >= 4096) { + cout << "Invalid VLAN ID "<< vid << endl; + return -1; + } + + key += std::to_string(vid); + if (dev == NULL) + { + if (cmd == VlanCfgMgr::DELETE) + { + m_vlanTableProducer.del(key); + } + else + { + vector fvVector; + FieldValueTuple a("admin_status", admin); + fvVector.push_back(a); + FieldValueTuple m("mtu", std::to_string(mtu)); + fvVector.push_back(m); + FieldValueTuple o("autostate", "disabled"); + fvVector.push_back(o); + + FieldValueTuple uf("unicast_miss_flood", unicast_miss_flood); + fvVector.push_back(uf); + FieldValueTuple mf("multicast_miss_flood", multicast_miss_flood); + fvVector.push_back(mf); + FieldValueTuple bf("broadcast_flood", broadcast_flood); + fvVector.push_back(bf); + + FieldValueTuple d("description", desc); + fvVector.push_back(d); + + FieldValueTuple s("config_status_code", "unknown"); + fvVector.push_back(s); + + m_vlanTableProducer.set(key, fvVector); + } + return 0; + } + + if_index = if_nametoindex(dev); + if (if_index == 0) { + cout << "Cannot find bridge device " << dev << " : " << std::strerror(errno) << endl; + return -1; + } + + key = key + ":" + dev; + + if( (flags & BRIDGE_VLAN_INFO_PVID) && !(flags & BRIDGE_VLAN_INFO_UNTAGGED )) + { + cout << "For Now: pvid should be set together with untagged mode!" << endl; + return -1; + } + + if (cmd == VlanCfgMgr::DELETE) + { + m_vlanMemberTableProducer.del(key); + } + else + { + vector fvVector; + FieldValueTuple t("tagging_mode", + (flags & BRIDGE_VLAN_INFO_UNTAGGED)? "untagged":"tagged"); + fvVector.push_back(t); + FieldValueTuple s("config_status_code", "unknown"); + fvVector.push_back(s); + + m_vlanMemberTableProducer.set(key, fvVector); + } + return 0; +} + +int VlanCfgMgr::vlan_show(int argc, char **argv) +{ + char *filter_dev = NULL; + unsigned int if_index; + string cmd = "bridge vlan show"; + string redis_cmd_db = "redis-cli -n "; + string redis_cmd_keys = "\\*VLAN\\*"; + string redis_cmd; + int vid = -1; + + while (argc > 0) { + if (matches(*argv, "dev") == 0) { + NEXT_ARG(); + filter_dev = *argv; + } else if (matches(*argv, "vlan") == 0) { + NEXT_ARG(); + vid = atoi(*argv); + } + argc--; argv++; + } + + redis_cmd_db += std::to_string(CONFIG_DB) + " "; + + if (vid >= 0) { + redis_cmd_keys += std::to_string(vid) + "\\*"; + } + + if (filter_dev) { + if_index = if_nametoindex(filter_dev); + if (if_index == 0) { + cout << "Cannot find bridge device " << filter_dev << " : " << std::strerror(errno) << endl; + return -1; + } + cmd += " dev "; + cmd += filter_dev; + + redis_cmd_keys += filter_dev; + redis_cmd_keys += "\\*"; + } + redis_cmd = redis_cmd_db + " KEYS " + redis_cmd_keys; + redis_cmd += " | xargs -n 1 -I % sh -c 'echo %; "; + redis_cmd += redis_cmd_db + "hgetall %; echo'"; + + cout << "-----Redis CFGDB data---" << endl; + cout << redis_cmd < 0) + { + if (matches(*argv, "add") == 0) + return cfgmgr.vlan_modify(VlanCfgMgr::ADD, argc-1, argv+1); + if (matches(*argv, "delete") == 0) + return cfgmgr.vlan_modify(VlanCfgMgr::DELETE, argc-1, argv+1); + if (matches(*argv, "show") == 0) + return cfgmgr.vlan_show(argc-1, argv+1); + if (matches(*argv, "help") == 0) + exitWithUsage(EXIT_SUCCESS, ""); + else + exitWithUsage(EXIT_FAILURE, "Invalid option"); + } + else { + exitWithUsage(EXIT_FAILURE, "Invalid option"); + } + + return EXIT_SUCCESS; +} diff --git a/cfgagent/cfgmgrtest/vlancfgmgr.h b/cfgagent/cfgmgrtest/vlancfgmgr.h new file mode 100644 index 0000000000..bb4d1da3c5 --- /dev/null +++ b/cfgagent/cfgmgrtest/vlancfgmgr.h @@ -0,0 +1,42 @@ +#ifndef __VLANCFGMGR__ +#define __VLANCFGMGR__ + +#include "dbconnector.h" +#include "table.h" +#include +#include +#include + +namespace swss { + +/* + * Record the vlan tagging setting for each VLAN member + */ +typedef std::map Vlan_Master_Mode; // ex. <"Vlan1003", "tagged"> +typedef std::map Vlan_Members; //ex. <"Ethernet8", <"Vlan1003", "tagged">> + +class VlanCfgMgr +{ +public: + enum Operation { + ADD, + DELETE, + SHOW, + } ; + enum { MAX_ADDR_SIZE = 64 }; + + VlanCfgMgr(DBConnector *db); + int vlan_modify(Operation cmd, int argc, char **argv); + int vlan_show(int argc, char **argv); + +private: + Table m_vlanTableProducer, m_vlanMemberTableProducer; + Table m_vlanMemberTableConsumer, m_vlanTableConsumer; + + std::map> m_vlanMap; + Vlan_Members m_vlanMemberMap; +}; + +} + +#endif diff --git a/cfgagent/cfgorch.cpp b/cfgagent/cfgorch.cpp new file mode 100644 index 0000000000..fc1206a33d --- /dev/null +++ b/cfgagent/cfgorch.cpp @@ -0,0 +1,228 @@ +#include +#include +#include +#include + +#include "cfgorch.h" +#include "tokenize.h" +#include "logger.h" + +using namespace swss; + +extern int gBatchSize; + +extern mutex gDbMutex; + +extern bool gSwssCfgRecord; +extern ofstream gCfgRecordOfs; +extern string gCfgRecordFile; +extern bool gInitDone; + +CfgOrch::CfgOrch(DBConnector *db, string tableName) : + m_db(db) +{ + Consumer consumer(new Subscriber(m_db, tableName)); + m_consumerMap.insert(ConsumerMapPair(tableName, consumer)); +} + +CfgOrch::CfgOrch(DBConnector *db, vector &tableNames) : + m_db(db) +{ + for(auto it : tableNames) + { + Consumer consumer(new Subscriber(m_db, it)); + m_consumerMap.insert(ConsumerMapPair(it, consumer)); + } +} + +CfgOrch::~CfgOrch() +{ + delete(m_db); + for(auto &it : m_consumerMap) + delete it.second.m_consumer; + + if (gCfgRecordOfs.is_open()) + { + gCfgRecordOfs.close(); + } +} + +vector CfgOrch::getSelectables() +{ + vector selectables; + for(auto it : m_consumerMap) { + selectables.push_back(it.second.m_consumer); + } + return selectables; +} + +bool CfgOrch::hasSelectable(Subscriber *selectable) const +{ + for(auto it : m_consumerMap) { + if (it.second.m_consumer == selectable) { + return true; + } + } + return false; +} + +bool CfgOrch::syncCfgDB(string tableName, Table &tableConsumer) +{ + SWSS_LOG_ENTER(); + + lock_guard lock(gDbMutex); + + auto consumer_it = m_consumerMap.find(tableName); + if (consumer_it == m_consumerMap.end()) + { + SWSS_LOG_ERROR("Unrecognized tableName:%s\n", tableName.c_str()); + return false; + } + Consumer& consumer = consumer_it->second; + + KeyOpFieldsValuesTuple tuple; + vector tuples; + + tableConsumer.getTableContent(tuples); + for (auto tuple : tuples) + { + string key = kfvKey(tuple); + + /* Record incoming tasks, for init op is empty in the record */ + if (gSwssCfgRecord) + { + recordTuple(consumer, tuple); + } + + /* Directly put it into consumer.m_toSync map */ + if (consumer.m_toSync.find(key) == consumer.m_toSync.end()) + { + consumer.m_toSync[key] = make_tuple(key, SET_COMMAND, kfvFieldsValues(tuple)); + } + /* + * Syncing from DB directly, don't expect duplicate keys. + * Or there is pending task from consumber state pipe, in this case just skip it. + */ + else + { + SWSS_LOG_WARN("Duplicate key %s found in tableName:%s\n", key.c_str(), tableName.c_str()); + continue; + } + doTask(consumer); + } + return true; +} + +bool CfgOrch::execute(string tableName) +{ + SWSS_LOG_ENTER(); + + lock_guard lock(gDbMutex); + + auto consumer_it = m_consumerMap.find(tableName); + if (consumer_it == m_consumerMap.end()) + { + SWSS_LOG_ERROR("Unrecognized tableName:%s\n", tableName.c_str()); + return false; + } + Consumer& consumer = consumer_it->second; + + int data_popped = 0; + while (1) + { + KeyOpFieldsValuesTuple new_data; + consumer.m_consumer->pop(new_data); + + string key = kfvKey(new_data); + string op = kfvOp(new_data); + /* + * Done with all new data. Or + * possible nothing popped, ie. the oparation is already merged with other operations + */ + if (op.empty()) + { + SWSS_LOG_DEBUG("Number of kfv data popped: %d\n", data_popped); + break; + } + data_popped++; + + /* Record incoming tasks */ + if (gSwssCfgRecord) + { + recordTuple(consumer, new_data); + } + + /* If a new task comes or if a DEL task comes, we directly put it into consumer.m_toSync map */ + if (consumer.m_toSync.find(key) == consumer.m_toSync.end() || op == DEL_COMMAND) + { + consumer.m_toSync[key] = new_data; + } + /* If an old task is still there, we combine the old task with new task */ + else + { + KeyOpFieldsValuesTuple existing_data = consumer.m_toSync[key]; + + auto new_values = kfvFieldsValues(new_data); + auto existing_values = kfvFieldsValues(existing_data); + + + for (auto it : new_values) + { + string field = fvField(it); + string value = fvValue(it); + + auto iu = existing_values.begin(); + while (iu != existing_values.end()) + { + string ofield = fvField(*iu); + if (field == ofield) + iu = existing_values.erase(iu); + else + iu++; + } + existing_values.push_back(FieldValueTuple(field, value)); + } + consumer.m_toSync[key] = KeyOpFieldsValuesTuple(key, op, existing_values); + } + } + if (!consumer.m_toSync.empty()) + doTask(consumer); + + return true; +} + +void CfgOrch::doTask() +{ + if (!gInitDone) + return; + + for(auto &it : m_consumerMap) + { + if (!it.second.m_toSync.empty()) + doTask(it.second); + } +} + +void CfgOrch::recordTuple(Consumer &consumer, KeyOpFieldsValuesTuple &tuple) +{ + string s = consumer.m_consumer->getTableName() + ":" + kfvKey(tuple) + + "|" + kfvOp(tuple); + for (auto i = kfvFieldsValues(tuple).begin(); i != kfvFieldsValues(tuple).end(); i++) + { + s += "|" + fvField(*i) + ":" + fvValue(*i); + } + + gCfgRecordOfs << getTimestamp() << "|" << s << endl; +} + +string CfgOrch::dumpTuple(Consumer &consumer, KeyOpFieldsValuesTuple &tuple) +{ + string s = consumer.m_consumer->getTableName() + ":" + kfvKey(tuple) + + "|" + kfvOp(tuple); + for (auto i = kfvFieldsValues(tuple).begin(); i != kfvFieldsValues(tuple).end(); i++) + { + s += "|" + fvField(*i) + ":" + fvValue(*i); + } + + return s; +} diff --git a/cfgagent/cfgorch.h b/cfgagent/cfgorch.h new file mode 100644 index 0000000000..c14bcf5599 --- /dev/null +++ b/cfgagent/cfgorch.h @@ -0,0 +1,50 @@ +#ifndef SWSS_CFGORCH_H +#define SWSS_CFGORCH_H + +#include +#include + +#include "dbconnector.h" +#include "table.h" +#include "subscriber.h" +#include "gettimestamp.h" + +using namespace std; +using namespace swss; + +typedef map SyncMap; +struct Consumer { + Consumer(Subscriber* consumer) :m_consumer(consumer) { } + Subscriber* m_consumer; + /* Store the latest 'golden' status */ + SyncMap m_toSync; +}; +typedef pair ConsumerMapPair; +typedef map ConsumerMap; + +class CfgOrch +{ +public: + CfgOrch(DBConnector *db, string tableName); + CfgOrch(DBConnector *db, vector &tableNames); + virtual ~CfgOrch(); + + vector getSelectables(); + bool hasSelectable(Subscriber* s) const; + + bool execute(string tableName); + /* Iterate all consumers in m_consumerMap and run doTask(Consumer) */ + void doTask(); + +protected: + DBConnector *m_db; + ConsumerMap m_consumerMap; + + /* Run doTask against a specific consumer */ + virtual void doTask(Consumer &consumer) = 0; + void recordTuple(Consumer &consumer, KeyOpFieldsValuesTuple &tuple); + string dumpTuple(Consumer &consumer, KeyOpFieldsValuesTuple &tuple); + bool syncCfgDB(string tableName, Table &tableConsumer); +}; + +#endif /* SWSS_CFGORCH_H */ diff --git a/cfgagent/intfcfgagent.cpp b/cfgagent/intfcfgagent.cpp new file mode 100644 index 0000000000..9ba22e16aa --- /dev/null +++ b/cfgagent/intfcfgagent.cpp @@ -0,0 +1,74 @@ +#include +#include +#include +#include +#include +#include "logger.h" +#include "dbconnector.h" +#include "producerstatetable.h" +#include "tokenize.h" +#include "ipprefix.h" +#include "cfgagent/intfcfgagent.h" +#include "exec.h" + +using namespace std; +using namespace swss; + +IntfCfgAgent::IntfCfgAgent(DBConnector *cfgDb, DBConnector *appDb, string tableName) : + CfgOrch(cfgDb, tableName), + m_cfgIntfTableConsumer(cfgDb, tableName), + m_intfTableProducer(appDb, APP_INTF_TABLE_NAME) +{ + +} + +void IntfCfgAgent::syncCfgDB() +{ + CfgOrch::syncCfgDB(CFG_INTF_TABLE_NAME, m_cfgIntfTableConsumer); +} + +bool IntfCfgAgent::setIntfIp(string &alias, string &opCmd, string &ipPrefixStr) +{ + string cmd; + + cmd = "ip address " + opCmd + " "; + cmd += ipPrefixStr + " dev " + alias; + swss::exec(cmd.c_str()); + return true; +} + +void IntfCfgAgent::doTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + + auto it = consumer.m_toSync.begin(); + while (it != consumer.m_toSync.end()) + { + KeyOpFieldsValuesTuple t = it->second; + + vector keys = tokenize(kfvKey(t), ':'); + string alias(keys[0]); + IpPrefix ip_prefix(kfvKey(t).substr(kfvKey(t).find(':')+1)); + + SWSS_LOG_DEBUG("intfs doTask: %s", (dumpTuple(consumer, t)).c_str()); + + string op = kfvOp(t); + if (op == SET_COMMAND) + { + string opCmd("add"); + string ipPrefixStr = ip_prefix.to_string(); + setIntfIp(alias, opCmd, ipPrefixStr); + } + else if (op == DEL_COMMAND) + { + string opCmd("del"); + string ipPrefixStr = ip_prefix.to_string(); + setIntfIp(alias, opCmd, ipPrefixStr); + } + + SWSS_LOG_DEBUG("%s", (dumpTuple(consumer, t)).c_str()); + + it = consumer.m_toSync.erase(it); + continue; + } +} \ No newline at end of file diff --git a/cfgagent/intfcfgagent.h b/cfgagent/intfcfgagent.h new file mode 100644 index 0000000000..58c70183a9 --- /dev/null +++ b/cfgagent/intfcfgagent.h @@ -0,0 +1,29 @@ +#ifndef __INTFCFGAGENT__ +#define __INTFCFGAGENT__ + +#include "dbconnector.h" +#include "cfgorch.h" + +#include +#include + +namespace swss { + + +class IntfCfgAgent : public CfgOrch +{ +public: + IntfCfgAgent(DBConnector *cfgDb, DBConnector *appDb, string tableName); + void syncCfgDB(); + +private: + ProducerStateTable m_intfTableProducer; + Table m_cfgIntfTableConsumer; + + bool setIntfIp(string &alias, string &opCmd, string &ipPrefixStr); + void doTask(Consumer &consumer); +}; + +} + +#endif diff --git a/cfgagent/portcfgagent.cpp b/cfgagent/portcfgagent.cpp new file mode 100644 index 0000000000..faee61a50c --- /dev/null +++ b/cfgagent/portcfgagent.cpp @@ -0,0 +1,124 @@ +#include +#include +#include +#include +#include +#include "logger.h" +#include "dbconnector.h" +#include "producerstatetable.h" +#include "cfgagent/portcfgagent.h" +#include "exec.h" + +using namespace std; +using namespace swss; + +#define DEFAULT_PORT_VLAN_ID 1 + +const string INTFS_PREFIX = "Ethernet"; +extern bool gInitDone; + +PortCfgAgent::PortCfgAgent(DBConnector *cfgDb, DBConnector *appDb, string tableName) : + CfgOrch(cfgDb, tableName), + m_portTableProducer(appDb, APP_PORT_TABLE_NAME) +{ + +} + +bool PortCfgAgent::setHostPortAdminState(string &alias, string &admin_status) +{ + string cmd; + + cmd = "ip link set "; + cmd += alias + " " + admin_status; + swss::exec(cmd.c_str()); + return true; +} + +bool PortCfgAgent::setHostPortMtu(string &alias, uint32_t mtu) +{ + string cmd; + + cmd = "ip link set "; + cmd += alias + " mtu " + to_string(mtu); + swss::exec(cmd.c_str()); + return true; +} + + +bool PortCfgAgent::setHostPortPvid(string &alias, uint32_t pvid) +{ + // TODO: should be set with bridge vlan + + return true; +} + +void PortCfgAgent::doTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + + // Don't start port processing until hostifs for physical ports are ready + if (!gInitDone) + return; + + auto it = consumer.m_toSync.begin(); + while (it != consumer.m_toSync.end()) + { + auto &t = it->second; + + // Do we need to check validity of port name here? + string alias = kfvKey(t); + string op = kfvOp(t); + + if (alias.compare(0, INTFS_PREFIX.length(), INTFS_PREFIX)) + { + SWSS_LOG_NOTICE("Invalid port name %s", alias.c_str()); + return; + } + + if (op == SET_COMMAND) + { + vector fvVector; + string admin_status; + uint32_t mtu = 0; + uint32_t pvid = DEFAULT_PORT_VLAN_ID; + + // set up host env .... + for (auto i : kfvFieldsValues(t)) + { + /* Set port admin status */ + if (fvField(i) == "admin_status") { + admin_status = fvValue(i); + setHostPortAdminState(alias, admin_status); + } + + /* Set port mtu */ + if (fvField(i) == "mtu") { + mtu = (uint32_t)stoul(fvValue(i)); + setHostPortMtu(alias, mtu); + } + + if (fvField(i) == "pvid") { + pvid = (uint32_t)stoul(fvValue(i)); + setHostPortPvid(alias, pvid); + // set APPDB directly for pvid config + m_portTableProducer.set(alias, kfvFieldsValues(t)); + } + } + + SWSS_LOG_DEBUG("port doTask: %s", (dumpTuple(consumer, t)).c_str()); + it = consumer.m_toSync.erase(it); + } + else if (op == DEL_COMMAND) + { + // Physical port can not be removed ? + SWSS_LOG_ERROR("port doTask: %s", (dumpTuple(consumer, t)).c_str()); + it = consumer.m_toSync.erase(it); + } + else + { + SWSS_LOG_ERROR("Unknown operation type %s", op.c_str()); + SWSS_LOG_ERROR("port doTask: %s", (dumpTuple(consumer, t)).c_str()); + it = consumer.m_toSync.erase(it); + } + } +} \ No newline at end of file diff --git a/cfgagent/portcfgagent.h b/cfgagent/portcfgagent.h new file mode 100644 index 0000000000..0f915d7b45 --- /dev/null +++ b/cfgagent/portcfgagent.h @@ -0,0 +1,29 @@ +#ifndef __PORTCFGAGENT__ +#define __PORTCFGAGENT__ + +#include "dbconnector.h" +#include "cfgorch.h" + +#include +#include + +namespace swss { + + +class PortCfgAgent : public CfgOrch +{ +public: + PortCfgAgent(DBConnector *cfgDb, DBConnector *appDb, string tableName); + +private: + ProducerStateTable m_portTableProducer; + + void doTask(Consumer &consumer); + bool setHostPortAdminState(string &alias, string &admin_status); + bool setHostPortMtu(string &alias, uint32_t mtu); + bool setHostPortPvid(string &alias, uint32_t pvid); +}; + +} + +#endif diff --git a/cfgagent/switchcfgagent.cpp b/cfgagent/switchcfgagent.cpp new file mode 100644 index 0000000000..11fac6c64f --- /dev/null +++ b/cfgagent/switchcfgagent.cpp @@ -0,0 +1,128 @@ +#include +#include +#include +#include +#include +#include +#include "logger.h" +#include "dbconnector.h" +#include "producerstatetable.h" +#include "tokenize.h" +#include "ipprefix.h" +#include "cfgagent/switchcfgagent.h" +#include "exec.h" + +using namespace std; +using namespace swss; + +#define DOT1Q_BRIDGE_NAME "Bridge" +#define CFG_SWITCH_FLOOD_CONTROL_KEY_NAME "FLOOD_CONTROL" + +SwitchCfgAgent::SwitchCfgAgent(DBConnector *cfgDb, DBConnector *appDb, string tableName) : + CfgOrch(cfgDb, tableName), + m_cfgSwitchTableConsumer(cfgDb, tableName), + m_switchTableProducer(appDb, APP_SWITCH_TABLE_NAME) +{ + +} + +void SwitchCfgAgent::syncCfgDB() +{ + CfgOrch::syncCfgDB(CFG_SWITCH_TABLE_NAME, m_cfgSwitchTableConsumer); +} + +// ls /sys/class/net/Bridge/brif/ | xargs -n 1 -I % sh -c 'echo %; echo 0 > /sys/class/net/Bridge/brif/%/unicast_flood' +void SwitchCfgAgent::updateHostFloodControl(string brif) +{ + string brif_path = "/sys/class/net/"; + string cmd; + string unicast_op, multicast_op, broadcast_op; + struct stat sb; + + brif_path += DOT1Q_BRIDGE_NAME; + brif_path += "/brif/"; + + if (stat(brif_path.c_str(), &sb) || !S_ISDIR(sb.st_mode)) + { + SWSS_LOG_INFO("updateHostFloodControl: %s doens't exist", brif_path.c_str()); + return; + } + + unicast_op = (m_unicast_miss_flood ? " 1 > " : " 0 > "); + multicast_op = (m_multicast_miss_flood ? " 1 > " : " 0 > "); + broadcast_op = (m_broadcast_flood ? " 1 > " : " 0 > "); + + // TODO: optimize the repeated CLI processing + if (brif.empty()) + { + string cmd_prefix; + //Apply to all bridge ports in 1q bridge + cmd_prefix = "ls " + brif_path; + cmd_prefix += " | xargs -n 1 -I % sh -c 'echo"; + + cmd = cmd_prefix + unicast_op; + cmd += brif_path + "%/unicast_flood'"; + swss::exec(cmd.c_str()); + + cmd = cmd_prefix + multicast_op; + cmd += brif_path + "%/multicast_flood'"; + swss::exec(cmd.c_str()); + + cmd = cmd_prefix + broadcast_op; + cmd += brif_path + "%/broadcast_flood'"; + swss::exec(cmd.c_str()); + } + else + { + cmd = "echo" + unicast_op; + cmd += brif_path + brif + "/unicast_flood"; + exec(cmd.c_str()); + + cmd = "echo" + multicast_op; + cmd += brif_path + brif + "/multicast_flood"; + swss::exec(cmd.c_str()); + + cmd = "echo" + broadcast_op; + cmd += brif_path + brif + "/broadcast_flood"; + swss::exec(cmd.c_str()); + } +} + +void SwitchCfgAgent::doTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + + auto it = consumer.m_toSync.begin(); + while (it != consumer.m_toSync.end()) + { + KeyOpFieldsValuesTuple t = it->second; + + string key = kfvKey(t); + + SWSS_LOG_DEBUG("Switch doTask: %s", (dumpTuple(consumer, t)).c_str()); + + if (key == CFG_SWITCH_FLOOD_CONTROL_KEY_NAME) + { + for (auto i : kfvFieldsValues(t)) + { + if (fvField(i) == "unicast_miss_flood") { + m_unicast_miss_flood = (fvValue(i)=="false" ? false:true); + } + else if (fvField(i) == "multicast_miss_flood") { + m_multicast_miss_flood = (fvValue(i)=="false" ? false:true); + } + else if (fvField(i) == "broadcast_flood") { + m_broadcast_flood = (fvValue(i)=="false" ? false:true); + } + } + string all_brif; + updateHostFloodControl(all_brif); + } + + m_switchTableProducer.set(key, kfvFieldsValues(t)); + SWSS_LOG_DEBUG("%s", (dumpTuple(consumer, t)).c_str()); + + it = consumer.m_toSync.erase(it); + continue; + } +} \ No newline at end of file diff --git a/cfgagent/switchcfgagent.h b/cfgagent/switchcfgagent.h new file mode 100644 index 0000000000..59dfea544b --- /dev/null +++ b/cfgagent/switchcfgagent.h @@ -0,0 +1,33 @@ +#ifndef __SWITCHCFGAGENT__ +#define __SWITCHCFGAGENT__ + +#include "dbconnector.h" +#include "cfgorch.h" + +#include +#include + +namespace swss { + + +class SwitchCfgAgent : public CfgOrch +{ +public: + SwitchCfgAgent(DBConnector *cfgDb, DBConnector *appDb, string tableName); + + void updateHostFloodControl(string brif); + void syncCfgDB(); +private: + bool m_unicast_miss_flood = true; + bool m_multicast_miss_flood = true; + bool m_broadcast_flood = true; + + ProducerStateTable m_switchTableProducer; + Table m_cfgSwitchTableConsumer; + + void doTask(Consumer &consumer); +}; + +} + +#endif diff --git a/cfgagent/vlancfgagent.cpp b/cfgagent/vlancfgagent.cpp new file mode 100644 index 0000000000..505bce4a88 --- /dev/null +++ b/cfgagent/vlancfgagent.cpp @@ -0,0 +1,349 @@ +#include +#include +#include +#include +#include +#include "logger.h" +#include "dbconnector.h" +#include "producerstatetable.h" +#include "tokenize.h" +#include "cfgagent/vlancfgagent.h" +#include "cfgagent/switchcfgagent.h" +#include "exec.h" + + +using namespace std; +using namespace swss; + +#define DOT1Q_BRIDGE_NAME "Bridge" +#define VLAN_PREFIX "Vlan" +#define DEFAULT_VLAN_ID 1 + +extern bool gInitDone; +extern MacAddress gMacAddress; +extern SwitchCfgAgent *gSwtichcfgagent; + +VlanCfgAgent::VlanCfgAgent(DBConnector *cfgDb, DBConnector *appDb, vector tableNames) : + CfgOrch(cfgDb, tableNames), + m_cfgVlanTableConsumer(cfgDb, CFG_VLAN_TABLE_NAME), + m_cfgVlanMemberTableConsumer(cfgDb, CFG_VLAN_MEMBER_TABLE_NAME), + m_vlanTableProducer(appDb, APP_VLAN_TABLE_NAME), + m_vlanMemberTableProducer(appDb, APP_VLAN_MEMBER_TABLE_NAME) + +{ + SWSS_LOG_ENTER(); + + // Initialize Linux dot1q bridge and enable vlan filtering + string cmd; + + cmd = "ip link del "; + cmd += DOT1Q_BRIDGE_NAME; + swss::exec(cmd.c_str()); + cmd = "ip link add "; + cmd += DOT1Q_BRIDGE_NAME; + cmd += " up type bridge"; + swss::exec(cmd.c_str()); + cmd = "echo 1 > /sys/class/net/"; + cmd += DOT1Q_BRIDGE_NAME; + cmd += "/bridge/vlan_filtering"; + swss::exec(cmd.c_str()); + cmd = "bridge vlan del vid " + std::to_string(DEFAULT_VLAN_ID) + + " dev " + DOT1Q_BRIDGE_NAME + " self"; + swss::exec(cmd.c_str()); +} + +void VlanCfgAgent::syncCfgDB() +{ + CfgOrch::syncCfgDB(CFG_VLAN_TABLE_NAME, m_cfgVlanTableConsumer); + CfgOrch::syncCfgDB(CFG_VLAN_MEMBER_TABLE_NAME, m_cfgVlanMemberTableConsumer); +} + +bool VlanCfgAgent::addHostVlan(int vlan_id) +{ + string cmd; + + cmd = "bridge vlan add vid " + to_string(vlan_id) + " dev " + + DOT1Q_BRIDGE_NAME + " self"; + swss::exec(cmd.c_str()); + cmd = "ip link add link "; + cmd += DOT1Q_BRIDGE_NAME; + cmd += " name "; + cmd += VLAN_PREFIX + to_string(vlan_id) + + " type vlan id " + to_string(vlan_id); + swss::exec(cmd.c_str()); + + cmd = "ip link set "; + cmd += VLAN_PREFIX + to_string(vlan_id); + cmd += " address " + gMacAddress.to_string(); + swss::exec(cmd.c_str()); + return true; +} + +bool VlanCfgAgent::removeHostVlan(int vlan_id) +{ + string cmd; + + cmd = "ip link del "; + cmd += VLAN_PREFIX + to_string(vlan_id); + swss::exec(cmd.c_str()); + + cmd = "bridge vlan del vid " + to_string(vlan_id) + " dev " + + DOT1Q_BRIDGE_NAME + " self"; + swss::exec(cmd.c_str()); + + return true; +} + +bool VlanCfgAgent::setHostVlanAdminState(int vlan_id, string &admin_status) +{ + string cmd; + + cmd = "ip link set "; + cmd += VLAN_PREFIX + to_string(vlan_id) + " " + admin_status; + swss::exec(cmd.c_str()); + return true; +} + +bool VlanCfgAgent::setHostVlanMtu(int vlan_id, uint32_t mtu) +{ + string cmd; + + cmd = "ip link set "; + cmd += VLAN_PREFIX + to_string(vlan_id) + " mtu " + to_string(mtu); + swss::exec(cmd.c_str()); + return true; +} + +bool VlanCfgAgent::addHostVlanMember(int vlan_id, string &port_alias, string& tagging_mode) +{ + string cmd; + + // Should be ok to run set master command more than one time. + cmd = "ip link set " + port_alias + " master " + DOT1Q_BRIDGE_NAME; + swss::exec(cmd.c_str()); + if (tagging_mode == "untagged" || tagging_mode == "priority_tagged") + { + // We are setting pvid as untagged vlan id. + cmd = "bridge vlan add vid " + to_string(vlan_id) + " dev " + + port_alias + " pvid untagged"; + } + else + { + cmd = "bridge vlan add vid " + to_string(vlan_id) + " dev " + + port_alias; + } + swss::exec(cmd.c_str()); + // Apply switch level flood control to this port + gSwtichcfgagent->updateHostFloodControl(port_alias); + return true; +} + + +bool VlanCfgAgent::removeHostVlanMember(int vlan_id, string &port_alias) +{ + string cmd, res; + + cmd = "bridge vlan del vid " + to_string(vlan_id) + " dev " + + port_alias; + swss::exec(cmd.c_str()); + + // When port is not member of any VLAN, it shall be detached from Dot1Q bridge! + cmd = "bridge vlan show dev " + port_alias + " | grep None"; + res = swss::exec(cmd.c_str()); + if (! res.empty()) + { + cmd = "ip link set " + port_alias + " nomaster"; + swss::exec(cmd.c_str()); + } + + return true; +} + +void VlanCfgAgent::doVlanTask(Consumer &consumer) +{ + auto it = consumer.m_toSync.begin(); + + while (it != consumer.m_toSync.end()) + { + auto &t = it->second; + + string key = kfvKey(t); + + /* Ensure the key starts with "Vlan" otherwise ignore */ + if (strncmp(key.c_str(), VLAN_PREFIX, 4)) + { + SWSS_LOG_ERROR("Invalid key format. No 'Vlan' prefix: %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + int vlan_id; + vlan_id = stoi(key.substr(4)); // FIXME: might raise exception + + string vlan_alias, port_alias; + string op = kfvOp(t); + + if (op == SET_COMMAND) + { + vector fvVector; + string admin_status; + uint32_t mtu = 0; + + // Add host VLAN when it has not been created. + if (m_vlans.find(key) == m_vlans.end()) + { + addHostVlan(vlan_id); + } + + // set up host env .... + for (auto i : kfvFieldsValues(t)) + { + /* Set port admin status */ + if (fvField(i) == "admin_status") { + admin_status = fvValue(i); + setHostVlanAdminState(vlan_id, admin_status); + } + /* Set port mtu */ + else if (fvField(i) == "mtu") { + mtu = (uint32_t)stoul(fvValue(i)); + setHostVlanMtu(vlan_id, mtu); + } + /* + * fields: unicast_miss_flood, multicast_miss_flood, + * broadcast_flood, and autostate are for lower + * layer procesing. VLAN scope flood control mechanism + * is not available yet. + * + * fileds: description, for upper layer only? + * + * fileds: config_status_code, not implmentated yet. + */ + /* Set port autostate */ + // string autostate = false; + // if (fvField(i) == "autostate") + // autostate = stoul(fvValue(i)); + } + + SWSS_LOG_DEBUG("%s", (dumpTuple(consumer, t)).c_str()); + m_vlanTableProducer.set(key, kfvFieldsValues(t)); + m_vlans.insert(key); + + it = consumer.m_toSync.erase(it); + } + else if (op == DEL_COMMAND) + { + if (m_vlans.find(key) != m_vlans.end()) + { + removeHostVlan(vlan_id); + m_vlans.erase(key); + m_vlanTableProducer.del(key); + } + else + { + SWSS_LOG_ERROR("%s doesn't exist", key.c_str()); + } + SWSS_LOG_DEBUG("%s", (dumpTuple(consumer, t)).c_str()); + it = consumer.m_toSync.erase(it); + } + else + { + SWSS_LOG_ERROR("Unknown operation type %s", op.c_str()); + SWSS_LOG_DEBUG("%s", (dumpTuple(consumer, t)).c_str()); + it = consumer.m_toSync.erase(it); + } + } +} + +void VlanCfgAgent::doVlanMemberTask(Consumer &consumer) +{ + auto it = consumer.m_toSync.begin(); + while (it != consumer.m_toSync.end()) + { + auto &t = it->second; + + string key = kfvKey(t); + + /* Ensure the key starts with "Vlan" otherwise ignore */ + if (strncmp(key.c_str(), VLAN_PREFIX, 4)) + { + SWSS_LOG_ERROR("Invalid key format. No 'Vlan' prefix: %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + key = key.substr(4); + size_t found = key.find(':'); + int vlan_id; + string vlan_alias, port_alias; + if (found != string::npos) + { + vlan_id = stoi(key.substr(0, found)); + port_alias = key.substr(found+1); + } + else + { + SWSS_LOG_ERROR("Invalid key format. No member port is presented: %s", + kfvKey(t).c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + vlan_alias = VLAN_PREFIX + to_string(vlan_id); + string op = kfvOp(t); + + // TODO: store port/lag/VLAN data in local data structure and perform more validations. + if (op == SET_COMMAND) + { + string tagging_mode = "untagged"; + + for (auto i : kfvFieldsValues(t)) + { + if (fvField(i) == "tagging_mode") + tagging_mode = fvValue(i); + } + + if (tagging_mode != "untagged" && + tagging_mode != "tagged" && + tagging_mode != "priority_tagged") + { + SWSS_LOG_ERROR("Wrong tagging_mode '%s' for key: %s", tagging_mode.c_str(), kfvKey(t).c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + addHostVlanMember(vlan_id, port_alias, tagging_mode); + m_vlanMemberTableProducer.set(kfvKey(t), kfvFieldsValues(t)); + SWSS_LOG_DEBUG("%s", (dumpTuple(consumer, t)).c_str()); + it = consumer.m_toSync.erase(it); + } + else if (op == DEL_COMMAND) + { + removeHostVlanMember(vlan_id, port_alias); + m_vlanMemberTableProducer.del(kfvKey(t)); + SWSS_LOG_DEBUG("%s", (dumpTuple(consumer, t)).c_str()); + it = consumer.m_toSync.erase(it); + } + else + { + SWSS_LOG_ERROR("Unknown operation type %s", op.c_str()); + it = consumer.m_toSync.erase(it); + } + } +} + +void VlanCfgAgent::doTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + + string table_name = consumer.m_consumer->getTableName(); + + if (table_name == CFG_VLAN_TABLE_NAME) + doVlanTask(consumer); + else if (table_name == CFG_VLAN_MEMBER_TABLE_NAME) + doVlanMemberTask(consumer); + else + { + SWSS_LOG_ERROR("Unknown config table %s ", table_name.c_str()); + throw runtime_error("VlanCfgAgent doTask failure."); + } +} diff --git a/cfgagent/vlancfgagent.h b/cfgagent/vlancfgagent.h new file mode 100644 index 0000000000..746c83e9d1 --- /dev/null +++ b/cfgagent/vlancfgagent.h @@ -0,0 +1,38 @@ +#ifndef __VLANCFGAGENT__ +#define __VLANCFGAGENT__ + +#include "dbconnector.h" +#include "cfgorch.h" + +#include +#include +#include +#include "macaddress.h" + +namespace swss { + +class VlanCfgAgent : public CfgOrch +{ +public: + VlanCfgAgent(DBConnector *cfgDb, DBConnector *appDb, vector tableNames); + void syncCfgDB(); +private: + ProducerStateTable m_vlanTableProducer, m_vlanMemberTableProducer; + Table m_cfgVlanTableConsumer, m_cfgVlanMemberTableConsumer; + std::set m_vlans; + + void doTask(Consumer &consumer); + void doVlanTask(Consumer &consumer); + void doVlanMemberTask(Consumer &consumer); + + bool addHostVlan(int vlan_id); + bool removeHostVlan(int vlan_id); + bool setHostVlanAdminState(int vlan_id, string &admin_status); + bool setHostVlanMtu(int vlan_id, uint32_t mtu); + bool addHostVlanMember(int vlan_id, string &port_alias, string& tagging_mode); + bool removeHostVlanMember(int vlan_id, string &port_alias); +}; + +} + +#endif diff --git a/configure.ac b/configure.ac index 145a0a1d6a..272f56d3bd 100644 --- a/configure.ac +++ b/configure.ac @@ -83,6 +83,8 @@ AC_CONFIG_FILES([ fpmsyncd/Makefile neighsyncd/Makefile intfsyncd/Makefile + cfgagent/Makefile + cfgagent/cfgmgrtest/Makefile portsyncd/Makefile teamsyncd/Makefile swssconfig/Makefile diff --git a/orchagent/orch.cpp b/orchagent/orch.cpp index fcb29aceaa..8fe5856df9 100644 --- a/orchagent/orch.cpp +++ b/orchagent/orch.cpp @@ -19,7 +19,6 @@ extern bool gSwssRecord; extern ofstream gRecordOfs; extern bool gLogRotate; extern string gRecordFile; -extern string getTimestamp(); Orch::Orch(DBConnector *db, string tableName) : m_db(db) diff --git a/orchagent/orch.h b/orchagent/orch.h index 096eea8361..985baa8d9a 100644 --- a/orchagent/orch.h +++ b/orchagent/orch.h @@ -12,6 +12,7 @@ extern "C" { #include "dbconnector.h" #include "consumerstatetable.h" #include "producerstatetable.h" +#include "gettimestamp.h" using namespace std; using namespace swss; diff --git a/orchagent/saihelper.cpp b/orchagent/saihelper.cpp index 15cb600a6f..9ff3e62a28 100644 --- a/orchagent/saihelper.cpp +++ b/orchagent/saihelper.cpp @@ -5,11 +5,9 @@ extern "C" { #include #include -#include - #include #include - +#include "gettimestamp.h" #include "saihelper.h" using namespace std; @@ -152,18 +150,6 @@ void initSaiApi() sai_log_set(SAI_API_ACL, SAI_LOG_LEVEL_NOTICE); } -string getTimestamp() -{ - char buffer[64]; - struct timeval tv; - gettimeofday(&tv, NULL); - - size_t size = strftime(buffer, 32 ,"%Y-%m-%d.%T.", localtime(&tv.tv_sec)); - snprintf(&buffer[size], 32, "%06ld", tv.tv_usec); - - return string(buffer); -} - void initSaiRedis(const string &record_location) { /** diff --git a/portsyncd/linksync.cpp b/portsyncd/linksync.cpp index 46ed80607c..07c9d9c52a 100644 --- a/portsyncd/linksync.cpp +++ b/portsyncd/linksync.cpp @@ -29,6 +29,7 @@ const string LAG_PREFIX = "PortChannel"; extern set g_portSet; extern map> g_vlanMap; extern bool g_init; +extern bool g_minigraphVlan; LinkSync::LinkSync(DBConnector *db) : m_portTableProducer(db, APP_PORT_TABLE_NAME), @@ -90,6 +91,14 @@ void LinkSync::onMsg(int nlmsg_type, struct nl_object *obj) return; } + if (!g_minigraphVlan && key.compare(0, VLAN_PREFIX.length(), VLAN_PREFIX) == 0) + { + /* + * VLAN APP config doesn't rely on netlink when VLAN not configured from minigraph + */ + return; + } + unsigned int flags = rtnl_link_get_flags(link); bool admin = flags & IFF_UP; bool oper = flags & IFF_LOWER_UP; diff --git a/portsyncd/portsyncd.cpp b/portsyncd/portsyncd.cpp index f10c03ba6b..717eb74058 100644 --- a/portsyncd/portsyncd.cpp +++ b/portsyncd/portsyncd.cpp @@ -5,6 +5,7 @@ #include #include #include +#include "exec.h" #include "dbconnector.h" #include "select.h" #include "netdispatcher.h" @@ -30,7 +31,7 @@ using namespace swss; set g_portSet; map> g_vlanMap; bool g_init = false; - +bool g_minigraphVlan = false; void usage() { cout << "Usage: portsyncd [-p port_config.ini]" << endl; @@ -40,6 +41,7 @@ void usage() void handlePortConfigFile(ProducerStateTable &p, string file); void handleVlanIntfFile(string file); +void minigraphVlan(); int main(int argc, char **argv) { @@ -63,6 +65,7 @@ int main(int argc, char **argv) } } + minigraphVlan(); DBConnector db(0, DBConnector::DEFAULT_UNIXSOCKET, 0); ProducerStateTable p(&db, APP_PORT_TABLE_NAME); @@ -165,3 +168,15 @@ void handlePortConfigFile(ProducerStateTable &p, string file) infile.close(); } + +void minigraphVlan() +{ + string vlan; + + vlan = swss::exec("sonic-cfggen -m /etc/sonic/minigraph.xml -v 'minigraph_vlans.keys()'"); + if(vlan.compare(0, 2, "[]")) + { + g_minigraphVlan = true; + cout << "VLAN configured from minigraph" << endl; + } +} diff --git a/teamsyncd/teamsync.cpp b/teamsyncd/teamsync.cpp index 39a9337c72..cb86dab1d5 100644 --- a/teamsyncd/teamsync.cpp +++ b/teamsyncd/teamsync.cpp @@ -156,11 +156,14 @@ int TeamSync::TeamPortSync::onChange() team_ifindex2ifname(m_team, ifindex, ifname, MAX_IFNAME); /* Skip the member that is removed from the LAG */ - if (team_is_port_removed(port)) + if (team_is_port_removed(port)) { + SWSS_LOG_DEBUG("TeamSync::TeamPortSync::onChange ifname %s removed", ifname); continue; + } team_get_port_enabled(m_team, ifindex, &enabled); tmp_lag_members[string(ifname)] = enabled; + SWSS_LOG_DEBUG("TeamSync::TeamPortSync::onChange ifname %s enabled:%d", ifname, enabled); } /* Compare old and new LAG members and set/del accordingly */