1. 基本流程
这个的主要流程比较简单,直接通过代码来看。
的命令处理入口:
config acl add table
基于上述代码分析cli命令与redis数据库的交互过程实际上就只有两个步骤:
命令行输入
命令,处理入口位于
config acl add ...
:
src/sonic-utilities/config/main.py:table()
解析命令行参数连接config_db数据库,向config_db的acl_table表中写入数据,索引为:ACL_TABLE|table_name – 这个过程不会对参数信息做任何检查与验证
创建的ACL_TABLE表在CONFIG_DB中,表信息如下:
在ACL_TABLE表中只有四个字段:type、policy_desc、ports、stage
1.2 函数调用
下图是从
开始的函数调用,调用过程比较长,下面只分析一些主要部分
src/sonic-utilities/config/main.py:table()
2. 命令解析
解析从命令获取的参数,并且构建成字典格式:
table_type不做任何检查,直接写入字典description只做判空检查,默认为table_nameports只做判空检查,默认为所有的ports – 从config_db的port表中获取所有portsstage只做判空检查,默认为ingress最终字典形式为:
table_info{"type":"","policy_desc":"","ports":"","stage":""}
table_name: 必要的参数table_type: 必要的参数description: 可选参数,为空则默认为table_nameports: 可选参数,为空则默认为所有的端口stage: 可选参数,为空则默认为入口流量ingress
3. 数据库操作–CONFIG_DB
如上所述,就是三个步骤:初始化数据库连接对象–>连接数据库–>写入数据库。
3.1 初始化数据库连接对象
3.1.1 对象初始化流程
ConfigDBConnector类是使用python编写的。看一下它的类图:
ConfigDBConnector: CONFIG_DB数据库操作的抽象SonicV2ConnectorConfigDBConnector_Native:CONFIG_DB数据库操作的实际执行函数SonicV2Connector_Native
按照python的菱形继承依次调用父类初始化方法,为对象设置数据库基本信息。其中有一个获取当前数据库列表的操作,从
文件中获取初始化的数据库信息,并以属性的方式(set_attr)存入当前创建的连接对象中。
database_config.json
3.1.2 获取数据库列表操作
从
文件中获取信息。文件位置(sonic系统上):
database_config.json
,文件信息如下:
/var/run/redis/sonic-database/
初始化SonicDBConfig对象
验证namespace
验证当前namespace是否在配置文件中:
在单个asic芯片环境中,namespace默认为’’在多个asic芯片环境中才会存在多个namespace
返回对象
格式形如:
["APPL_DB","CONFIG_DB","ASIC_DB"...]
3.2 连接CONFIG_DB
根据python菱形继承,最终连接过程定位到
,连接步骤:
ConfigDBConnector_Native::db_connect()
获取
的分隔符
CONFIG_DB
调用
库与
hiredis
进行连接:使用
redis
文件;或者使用
socket
、
host
、
port
信息 – 得到
dbname
对象,是一个提供所有
DBConnector
命令操作的接口
redis
配置
库的键空间通知模式,默认为
CONIFG_DB
,会发布所有支持的通知
"KEA"
从
中获取初始化指示值 – 怎么算初始化完成了,在哪里写入的这个值?
CONFIG_DB
如果已经初始化了,则结束
否则死循环并且设置超时阻塞监听这个值变化,直到这个值显示初始化完成,并取消事件订阅
订阅事件:
– 模式订阅开启循环监听匹配该模式的所有通知得到一个对应通知,查询
__keyspace@4__:CONFIG_DB_INITIALIZED
中
CONFIG_DB
的值,判断是否初始化如果已经初始化,那么退出循环,取消该订阅事件;否则继续第iii步进行监听
CONFIG_DB_INITIALIZED
3.2.1 connect
该流程对应连接过程第2步,具体代码对应到
方法,得到一个新的DBConnector对象:
DBInterface::_onetime_connect()
DBConnector: 一个连接redis的数据库对象,提供对数据库所有操作的接口,如HGET/HGETALL/PUBSCRIBE/DEL等。RedisContext: 调用hiredis库的redisConnect()方法连接数据库,保存redisContext对象,存储的是连接信息
得到数据库index以及分隔符信息初始化redisContext: 调用hiredis库的redisConnect()方法连接对应数据库,返回的是redisContext对象,redisContext是hiredis库中的一个结构体,保存的是连接信息,比较重要的成员变量是连接的fd选择数据库:执行
命令 – 一个基本的命令写入过程
select index
初始化RedisReply对象,构造函数: 调用redisAppendCommand()向redis发送命令,redisCommand是hiredis库中的一个执行redis命令的方法;调用getRedisReply()方法得到响应结果检查上个步骤命令执行结果 – 不会反馈到命令界面
3.2.2 订阅流程
该流程对应连接过程的第5步的订阅流程。 – 这是比较直接的订阅,也可以说是一次性订阅流程,得到结果就取消订阅。
根据当前连接创建一个PubSub对象,主要是得到连接的fd;初始化Select对象,创建一个epoll_fd – Select对象的初始化暂时没找到在哪里,应该是一个全局共享变量
PubSub: 扩充订阅、取消订阅、读取消息通知等操作,添加订阅对象的管理,如添加、删除等RedisSelect: 实现Selectable中的方法,并且提供订阅、取消订阅、读取消息通知等操作Selectable: 一个接口类,保存且操作events,比如是否有events、读取events等
判断是否已订阅当前fd,如果已经订阅:
从Select对象的订阅对象列表中移除从Select对象的待处理列表中移除从epoll红黑树中移除
向redis订阅事件通知:
根据当前连接创建一个新的连接,得到一个新的fd,专门用于接收订阅的事件通知使用当前连接向redis发布订阅命令
将当前处理订阅事件通知的socket加入监控列表等待处理
将新的fd加入Select对象进行监听将fd添加到epoll红黑树
3.2.3 监听流程
该流程对应连接过程的第5步的监听流程。 – 这是比较直接的监听,也可以说是一次性监听,和上面的订阅配合使用,得到结果就不再处理事件。
for循环获取监听事件 – 这一步是具体的监听过程,后面会再详细的叙述,主要与Select类相关判断事件通知是否是模式订阅信息,如果是,则继续,否则回到第1步判断通知中的key是否是当前订阅的信息,如果是,则继续,否则回到第1步从config_db中获取对应属性:
通过该value判断是否已经初始化完成,如果是则结束,否则回到第1步
GET CONFIG_DB_INITIALIZED
3.3 写入CONFIG_DB
根据python菱形继承,最终连接过程定位到
,步骤如下:
ConfigDBConnector_Native::set_entry()
构建hash值:
从命令行解析的表信息是否为空,为空则执行删除操作,
ACL_TABLE|table_name
,结束;不为空则继续从redis中获取hash对应的数据:
DEL hash
创建或者覆盖hash对应的数据:
HGETALL hash
如果hash值原来就在redis数据库中,那么比较原来表字段信息与新的表字段信息:如果字段只存在于原来的表中,那么删除该字段,
HMSET hash ....
HDEL hash field