数通平台软件:软件升级兼容性问题的一种解决方法
数通平台软件为了提高设备可靠性和可维护性,通常会支持进程重启,甚至是组件级重启。实现进程或组件重启的目的不是为了简单规避故障,而是为了软件升级。
数通平台系统的运作是依靠消息驱动的。如果需要支持进程或组件级重启升级,就必须考虑兼容性问题。兼容性问题通常发生在该(进程中的)组件与其他组件或模块的消息收发过程中。那么,通常如何解决兼容性问题呢?
考虑一种典型场景,组件A向组件B发起请求,B随后应答数据给A端,则A是消息请求端,B是消息应答端。消息通信(接收与发送各一条)管道的buffer长度通常是一个(可定制的)固定值,如4K,16K,32K字节长度等,而消息报文通常会定义成TLV格式,可以是内嵌式的,也可以是级联式的。
本文考虑级联式TLV应用场景,消息的第一个TLV是固定的MSG_HEAD,其后的TLV是消息数据块REQ_MSG或RSP_MSG、RSP_MSG_V2、……。消息的请求端和接收端的消息填充(或打包pack)方法一致。级联TLV格式如下所示:
+-------------------------------+
| TLV | TLV | TLV | ... |
+-------------------------------+
typedefstruct tlv_head_s //典型的TLV格式
{
uint32_t type;
uint32_t len; //len为data的字节长度,不包括type和len;若用uint16_t定义,需考虑字节对齐
uintptr_t data[0];
}TLV_HEAD;
typedefstruct msg_head_s //固定的消息头TLV
{
TLV_HEAD head;
uint32_t msg_type;
uint32_t msg_len; //msg_len表示除MSG_HEAD之外的消息数据TLV长度
uint32_t xxx;
uint32_t res;
}MSG_HEAD;
typedefstruct req_msg_s //请求消息数据TLV —A端
{
TLV_HEAD head;
uint32_t if_index;
uint32_t next_hop;
}REQ_MSG;
typedefstruct rsp_msg_s //应答消息数据TLV —B端
{
TLV_HEAD head;
uint32_t if_index;
uint32_t next_hop;
uint32_t label;
uint32_t yyy;
}RSP_MSG;
消息中TLV数据块的获取方法(通过当前TLV获取下一个TLV结构,第一个TLV即固定MSG_HEAD,直接对消息buffer头地址进行强制转换获取):
#defineTLV_GET_NEXT(tlv) (void *)((char *)(tlv)+ (size_t)((tlv)->head.len))
或者,
#defineTLV_GET_NEXT(tlv) (void*)((uintptr_t)(tlv) + (size_t)((tlv)->head.len))
假设组件B单独进行了不中断业务的重启升级,新版本支持了新特性zzz,需要应答给A端处理。有两种基本的兼容性解决方案,一种是在RSP_MSG数据TLV最后追加字段zzz,另一种是追加数据TLV。我们采用后一种,即在原有RSP_MSG数据TLV之后新增数据TLV RSP_MSG_V2的方法来处理兼容性问题:
typedefstruct rsp_msg_v2_s
{
TLV_HEAD head;
uint32_t zzz;
}RSP_MSG_V2;
在组件B逻辑代码中,对数据正常填充:
rsp_msg_v2= TLV_GET_NEXT(rsp_msg);
rsp_msg_v2->head.type= req_msg->head.type;
rsp_msg_v2->head.len= sizeof(RSP_MSG_V2) - sizeof(TLV_HEAD);
rsp_msg_v2->zzz = zzz;
msg_head->msg_len+= sizeof(RSP_MSG_V2); //累加所有的数据TLV长度
组件A接收到B的应答消息后,先处理RSP_MSG数据TLV,然后根据实际情况处理RSP_MSG_V2与否:
if(msg_head->msg_len > sizeof(RSP_MSG))
{
rsp_msg_v2 = TLV_GET_NEXT(rsp_msg);
ASSERT(rsp_msg_v2->head.type ==req_msg->head.type);
ASSERT(rsp_msg_v2->head.len ==sizeof(RSP_MSG_V2) - sizeof(MSG_HEAD));
/* 此处的校验可以根据实际情况更严格一些,比如说只有(msg_head->msg_len >= sizeof(RSP_MSG) + sizeof(RSP_MSG_V2))时,才处理RSP_MSG_V2 TLV数据;其次,若类型type、长度len不匹配,则跳出zzz的处理等 */
//接收处理 rsp_msg_v2->zzz;
}
最后,补充说明兼容性问题基本注意点和功能代码的测试用例需要覆盖的场景。
1、兼容性问题的基本要点:
-
消息TLV的原有成员字段不可变动,如调整顺序,修改字段长度;
-
消息TLV的原有字段不可删除;
-
消息TLV新增字段只能往后追加,在原有TLV之后追加,或新增TLV填充;
2、兼容性功能的测试用例点:
-
兼容性代码的基本功能测试;
-
旧版本,升级组件B;
-
新版本,降级组件B;