Switch State Service (SWSS) ===== ###### tags: `SONiC` **[SAI, Switch State Service: Introduction](https://github.com/Azure/SONiC/blob/master/sourcecode.md#sai-switch-state-service)** This repository contains the source code for the swss container, teamd container & bgp container. # Entry Point src/sonic-swss/orchagent/main.cpp src/sonic-swss/orchagent/saihelper.cpp `sai_api_initialize` SAI initialization :::success SAI 3 basic ``` sai_api_initialize() sai_api_query() sai_api_uninitialize() ``` ::: src/sonic-swss/orchagent/orchdaemon.cpp The orchdaemon main loop: `OrchDaemon::start()` ![](https://i.imgur.com/bTpnkdC.png) [swss configuration and schema](https://github.com/Azure/sonic-swss/tree/master/doc) [2.2.1.1 Proposed optimizations for reducing the route programming time.](https://github.com/Azure/SONiC/blob/b16791f3ca16398153556f40b11831cfbb6aba79/doc/L3_performance_and_scaling_enchancements_HLD.md#2211-proposed-optimizations-for-reducing-the-route-programming-time) # Database [APPL_DB](https://github.com/Azure/sonic-swss/blob/master/doc/swss-schema.md) [CONFIG_DB](https://github.com/Azure/sonic-swss/blob/master/doc/Configuration.md) # Debug LOG Enable: The syncd container's daemon also use this. ```bash= swssloglevel -l DEBUG -c orchagent swssloglevel -l DEBUG -c syncd cat /var/log/syslog ``` # **[sonic-swss unit test](https://github.com/Azure/sonic-swss/tree/fa983d2e2be9fa007ac56dc3a6ef0c40e72e2ec9/tests)** [Tests folder Readme](https://github.com/Azure/sonic-swss/blob/master/tests/README.md) ssh swss@10.168.4.154 ## Using KVM to test swss ```bash= tar -zcvf target.tgz target/debs/buster/* target/docker-sonic-vs.gz tar -zcvf tests.tgz src/sonic-swss/tests/ scp target.tgz swss@10.168.4.154:/home/swss/ scp tests.tgz swss@10.168.4.154:/home/swss/ # Enter KVM apt update apt install build-essential -y add-apt-repository ppa:deadsnakes/ppa apt-get install -y python3.7 libpython3.7 libpython3.7-dev python3-pip net-tools ethtool vlan pip3 install docker pytest flaky redis distro dataclasses fstring pytest-json-report # mus intall by order sudo dpkg -i libhiredis0.14_0.14.0-3~bpo9+1_amd64.deb sudo dpkg -i libhiredis-dev_0.14.0-3~bpo9+1_amd64.deb sudo dpkg -i libnl-3-200_3.5.0-1_amd64.deb sudo dpkg -i libnl-3-dev_3.5.0-1_amd64.deb sudo dpkg -i libnl-cli-3-200_3.5.0-1_amd64.deb sudo dpkg -i libnl-cli-3-dev_3.5.0-1_amd64.deb sudo dpkg -i libnl-genl-3-200_3.5.0-1_amd64.deb sudo dpkg -i libnl-genl-3-dev_3.5.0-1_amd64.deb sudo dpkg -i libnl-nf-3-200_3.5.0-1_amd64.deb sudo dpkg -i libnl-nf-3-dev_3.5.0-1_amd64.deb sudo dpkg -i libnl-route-3-200_3.5.0-1_amd64.deb sudo dpkg -i libnl-route-3-dev_3.5.0-1_amd64.deb sudo dpkg -i libswsscommon_1.0.0_amd64.deb sudo dpkg -i python3-swsscommon_1.0.0_amd64.deb sudo modprobe team sudo cp tests/mock_tests/database_config.json /var/run/redis/sonic-db/database_config.json sudo docker load < target/docker-sonic-vs.gz sudo pytest ``` Clean up the old `.deb` before update: ```bash= sudo dpkg -r libswsscommon python3-swsscommon sudo dpkg --purge libswsscommon python3-swsscommon sudo rm -rf /usr/lib/python3/dist-packages/swsscommon/ ``` ## Using Docker to test swss ```bash= docker run -it --rm --privileged --name pytest-$USER \ -v `pwd`/target/debs/buster:/debs \ -v `pwd`/target/docker-sonic-vs.gz:/images/docker-sonic-vs.gz \ -v `pwd`/src/sonic-swss/tests:/tests \ -v /lib/modules:/lib/modules \ docker.clounix.com/software/sonic/clounix-build-tools/pytest:3.7 bash sudo modprobe team # for first time pytest -v --force-flaky --keeptb --pdb test_route.py::TestRoute::test_RouteAddRemoveIpv4Route ``` # test_mux.py ## ::test_acl ```python= self = <test_mux.TestMuxTunnel object at 0x7fd339971250> dvs = <conftest.DockerVirtualSwitch object at 0x7fd3393cfd90> dvs_acl = <dvslib.dvs_acl.DVSAcl object at 0x7fd339971410> testlog = <function testlog at 0x7fd33ad6a830> ``` ### Run command on 1st level (Host docker) ```python= # set ip address and default route dvs.servers[0].runcmd("ip address add 10.0.0.1/31 dev eth0") dvs.servers[0].runcmd("ip route add default via 10.0.0.0") dvs.servers[1].runcmd("ip address add 10.0.0.3/31 dev eth0") dvs.servers[1].runcmd("ip route add default via 10.0.0.2") ``` ### Run command on 2nd level (docker-sonic-vs) ```python dvs.runcmd("vtysh -c \"configure terminal\" -c \"ip route 2.2.2.0/24 10.0.0.1\"") ``` # Pytest Usage ### pytest testing framework src/sonic-swss/tests/conftest.py 1. Pytes find the `scope="module"` to construct the `dvs` object. 2. `debian:jessis` container will create. `VirtualServer` start to set up the linux network namespace and [veth](https://developers.redhat.com/blog/2018/10/22/introduction-to-linux-interfaces-for-virtual-networking/#veth) for each port. 4. virtual switch container create. 5. Initialize the SWSS service daemon, ASIC_DB and APPL_DB. ```python= @pytest.yield_fixture(scope="module") def dvs(request) -> DockerVirtualSwitch: class DockerVirtualSwitch: class VirtualServer: ``` src/sonic-swss/tests/test_route.py 5. Run the test case ```python= class TestRoute(TestRouteBase): def test_RouteAddRemoveIpv4Route(self, dvs, testlog): ``` ### Demo Ping ```bash= docker run -it --rm --privileged --name pytest-$USER \ -v `pwd`/target/debs/buster:/debs \ -v `pwd`/target/docker-sonic-vs.gz:/images/docker-sonic-vs.gz \ -v `pwd`/src/sonic-swss/tests:/tests \ -v /lib/modules:/lib/modules \ docker.clounix.com/software/sonic/clounix-build-tools/pytest:3.7 bash # Enter Level1 pytest -v test_route.py --keeptb ip netns # hungry_brattain- is namespace prefix ip netns exec hungry_brattain-srv0 ip addr add 10.0.0.1/31 dev eth0 ip netns exec hungry_brattain-srv0 ip route add default via 10.0.0.0 ip netns exec hungry_brattain-srv1 ip address add 10.0.0.3/31 dev eth0 ip netns exec hungry_brattain-srv1 ip route add default via 10.0.0.2 docker ps # confident_blackburn is vs-switch container name docker exec -it confident_blackburn bash # Enter Level2 config interface ip add Ethernet0 10.0.0.0/31 config interface ip add Ethernet4 10.0.0.2/31 config interface startup Ethernet0 config interface startup Ethernet4 exit # Back to Level1 to check ip netns exec hungry_brattain-srv0 ip route # default via 10.0.0.0 dev eth0 # 10.0.0.0/31 dev eth0 proto kernel scope link src 10.0.0.1 ip netns exec hungry_brattain-srv1 ip route # default via 10.0.0.2 dev eth0 # 10.0.0.2/31 dev eth0 proto kernel scope link src 10.0.0.3 ip netns exec hungry_brattain-srv0 ping 10.0.0.3 ip netns exec hungry_brattain-srv1 ping 10.0.0.1 ``` ### Allure webserver: :::success Please do the docker login: $ docker login docker.clounix.com Username: Password: The account same as the gitlab ::: ```bash= docker run -it --rm --privileged --name pytest-$USER \ -v `pwd`/target/debs/buster:/debs \ -v `pwd`/target/docker-sonic-vs.gz:/images/docker-sonic-vs.gz \ -v `pwd`/src/sonic-swss/tests:/tests \ -v /lib/modules:/lib/modules \ docker.clounix.com/software/sonic/clounix-build-tools/pytest:3.7 bash pytest -vs --force-flaky --alluredir=./allure-results test_route.py # Open another Terminal in project top folder docker run --rm -it --name=allure-$USER \ -p `id -u`:8080 \ -v $(pwd)/src/sonic-swss/tests/allure-results:/home/allure \ solutis/allure \ allure serve -p 8080 /home/allure #or docker run --rm -it --name=allure-$USER \ -p `id -u`:8080 \ -v $(pwd)/src/sonic-swss/tests/allure-results:/opt/allure/results \ docker.clounix.com/software/sonic/clounix-build-tools/allure # Go to the new website ``` `id -u`: your account uid `-v`: show test process `--keeptb`: Preserve the switch docker `-s`: Enable the print() in pytest `--pdb`: Enable python debugger ## How the level 2 docker get set https://www.slideshare.net/zuan0312/linux-network-namespace ```bash= === priceless_bell: priceless_bell-srv32, eth1 ==== ip netns add priceless_bell-srv32 ip netns exec priceless_bell-srv32 ip link add priceless_bel- type veth peer name eth1 //Construct a veth root@269aa41eaec6:/tests# ip netns exec priceless_bell-srv32 ip link 1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 2: eth1@priceless_bel-: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/ether 36:fc:8b:0d:cd:3c brd ff:ff:ff:ff:ff:ff 3: priceless_bel-@eth1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/ether ba:9a:8f:31:bd:7e brd ff:ff:ff:ff:ff:ff ip netns exec priceless_bell-srv32 ip link set eth1 netns 22182 // Move eth1 (veth) to namespace PID ip netns exec priceless_bell-srv32 ip link set dev priceless_bel- name eth0 root@269aa41eaec6:/tests# ip netns exec priceless_bell-srv32 ip link 1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 2: eth1@eth0: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/ether 36:fc:8b:0d:cd:3c brd ff:ff:ff:ff:ff:ff 3: eth0@eth1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/ether ba:9a:8f:31:bd:7e brd ff:ff:ff:ff:ff:ff ip netns exec priceless_bell-srv32 ip link set dev eth0 up ip netns exec priceless_bell-srv32 ethtool -K eth0 tx off nsenter -t 22182 -n ip link set dev eth1 up # disable arp, so no neigh on physical interfaces nsenter -t 22182 -n ip link set arp off dev eth1 nsenter -t 22182 -n sysctl -w net.ipv6.conf.eth1.disable_ipv6=1 ``` 1. Create a veth ![](https://i.imgur.com/pmPXqHh.png) 2. Move one veth interface to debian docker ![](https://i.imgur.com/aM1Lvbg.png) 3. Before run the switch container, conftest.py copy interface setting(Network mode) to virtual switch docker ```python= # create virtual switch container self.ctn = self.client.containers.run(imgname, privileged=True, detach=True, environment=self.environment, network_mode=f"container:{self.ctn_sw.name}", cpu_count=max_cpu, **kwargs) ``` ![](https://i.imgur.com/eFDzY6n.png) 4. Done ![](https://i.imgur.com/2ddASoC.png) ## Update debian package for Docker in Docker Firs time runing pytest ```bash docker run -it --rm --privileged --name pytest-$USER \ -v `pwd`/target/debs/buster:/debs \ -v `pwd`/target/docker-sonic-vs.gz:/images/docker-sonic-vs.gz \ -v `pwd`/src/sonic-swss/tests:/tests \ -v /lib/modules:/lib/modules \ docker.clounix.com/software/sonic/clounix-build-tools/pytest:3.7 bash pytest -v --keeptb test_route.py ``` When you wnat to update the partial file. You could use docker commit to update the DinD image. ```bash # In Level 1: docker run -it --rm -v /debs:/debs --entrypoint=/bin/bash docker-sonic-vs # Enter Level 2 Update your package: dpkg -i /debs/swss_1.0.0_amd64.deb ``` Package the running container. ```bash # Back to Level 1 check the container name: docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 0f6f63795ea8 docker-sonic-vs "/bin/bash" 4 minutes ago Up 4 minutes dreamy_solomon # Package running container and commit to the new image docker commit --change='ENTRYPOINT ["/usr/local/bin/supervisord"]' dreamy_solomon docker-sonic-vs:new docker images REPOSITORY TAG IMAGE ID CREATED SIZE docker-sonic-vs new 1f0380c4cf6b 3 seconds ago 836MB docker-sonic-vs latest 05b4e8f16034 3 days ago 835MB ``` Run pytest with new image ```bash pytest --imgname=docker-sonic-vs:new ``` # Orch main Loop ```clike= swss::Select s; for (Orch *o : cfgOrchList) { s.addSelectables(o->getSelectables()); } while (true) { Selectable *sel; s.select(&sel, SELECT_TIMEOUT); ... auto *c = (Executor *)sel; c->execute(); } ``` # class Select() ```clike= Select::Select() { m_epoll_fd = ::epoll_create1(0); } void Select::addSelectable(Selectable *selectable) { const int fd = selectable->getFd(); m_objects[fd] = selectable; m_ready.insert(selectable); .... struct epoll_event ev = { .events = EPOLLIN, .data = { .fd = fd, }, }; int res = ::epoll_ctl(m_epoll_fd, EPOLL_CTL_ADD, fd, &ev); } int Select::poll_descriptors(Selectable **c, unsigned int timeout) { std::vector<struct epoll_event> events(sz_selectables); ret = ::epoll_wait(m_epoll_fd, events.data(), sz_selectables, timeout); for (int i = 0; i < ret; ++i) { int fd = events[i].data.fd; Selectable* sel = m_objects[fd]; sel->readData(); m_ready.insert(sel); } .... *c = sel; } ``` # Orch swss::Selectable *m_selectable; ## Where is the file file descriptor be create ? - class Consumer : public Executor - class Executor : public swss::Selectable - class NotificationConsumer : public Selectable - class RedisSelect : public Selectable - class SelectableEvent : public Selectable - class SelectableTimer : public Selectable - class NfNetlink : public Selectable - class FpmLink : public Selectable ```mermaid classDiagram Executor<|--Consumer Selectable<|--Executor Selectable<|--NotificationConsumer Selectable<|--RedisSelect Selectable<|--SelectableEvent Selectable<|--SelectableTimer Selectable<|--NfNetlink Selectable<|--FpmLink ``` ### class Orch ```clike= Orch::Orch(DBConnector *db, const vector<table_name_with_pri_t> &tableNames_with_pri) { for (const auto& it : tableNames_with_pri) { addConsumer(db, it.first, it.second); } } ## DB Table and callback function hooking Consumer(swss::ConsumerTableBase *select, Orch *orch, const std::string &name) : Executor(select, orch, name) { } void Orch::addConsumer(DBConnector *db, string tableName, int pri) { if (db->getDbId() == CONFIG_DB || db->getDbId() == STATE_DB) { addExecutor(new Consumer(new SubscriberStateTable(db, tableName, TableConsumable::DEFAULT_POP_BATCH_SIZE, pri), this, tableName)); } else { addExecutor(new Consumer(new ConsumerStateTable(db, tableName, gBatchSize, pri), this, tableName)); } } ``` ### class Executor ```clike= typedef std::map<std::string, std::shared_ptr<Executor>> ConsumerMap; ConsumerMap m_consumerMap; void Orch::addExecutor(Executor* executor) { auto inserted = m_consumerMap.emplace(std::piecewise_construct, std::forward_as_tuple(executor->getName()), std::forward_as_tuple(executor)); } ``` ### doTask ```clike= void Consumer::execute() { SWSS_LOG_ENTER(); std::deque<KeyOpFieldsValuesTuple> entries; getConsumerTable()->pops(entries); addToSync(entries); drain(); } void Consumer::drain() { if (!m_toSync.empty()) m_orch->doTask(*this); } ```