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()`

[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

2. Move one veth interface to debian docker

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)
```

4. Done

## 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);
}
```