摘要

  • SNMP 的 RowStatus 把 createAndGo、createAndWait 和 destroy 定义为只能写入的动作,把 active、notInService 和 notReady 定义为可以观察的状态;请求与事实因此不会混成一个字段。
  • createAndWait 允许概念行在资料尚不完整时存在,但设备仍决定何时足以尝试激活、何时拒绝、何时清理被遗弃的资源。一个 SetRequest 内的同时赋值与回滚,不能扩展成多次交互或多台设备的原子事务。

从 RMON 的遗留资源开始

1990 年 5 月的 RFC 1157 刻意把管理功能表示为对命名变量的读取和改变,而不是一套不断膨胀的命令目录。这个小接口帮助 SNMP 适用于多种设备,也把每次请求的技术边界保持得相对清楚。

但概念表的一行并不总能靠一个值完成。行的索引需要避免冲突;若干列可能没有默认值;代理可能要分配有限资源;列与列之间还可能有一致性条件。更重要的是,多个管理程序和设备自身都可能触碰同一张控制表。单纯说“把变量设成某值”,没有说明不完整的行怎样被看见、怎样变为可用,又由谁收拾中途放弃的创建。

这个问题先在远程网络监测中变得具体。1991 年 11 月的 RFC 1271 讨论 RMON 控制表时,直接面对多个管理者分享监测资源的情形。它的 EntryStatus 使用 createRequest、underCreation、valid 与 invalid,并讨论碰撞、所有者字符串和对未完成条目的处理。

所有者字符串有助于管理者理解谁曾配置一条记录,但它不是身份认证,也不授予权限。真正重要的历史变化是:创建不再被假定为一个瞬间完成的布尔动作,而成为可以观察和清理的过程。

1993 年 4 月的 RFC 1443 把这个做法推广成可复用的 SNMPv2 文本约定,并明确指出其源头是 RMON 的 EntryStatus。1996 年的 RFC 1903 又修订了约定及错误映射。1999 年的 RFC 2579 给出了本文采用的完整状态、交互与清理规则。它不是一次突然出现的抽象发明,而是一个控制表问题逐步被提炼成通用接口的结果。

六个数值并不是六种同类状态

假设管理程序要建立一条新的过滤规则、告警规则或远程监测记录。它可能知道索引,却还没有填齐所有必需列。此时,若状态列只是一个普通开关,程序会遇到两种糟糕选择:要么在资料不完整时把行投入使用,要么把“尚未完成”藏在实现内部,使另一个管理者无法判断眼前的行到底是什么。

RowStatus 的答案不是增加一个更长的命令,而是把动作和观察分成两组。RFC 2579 定义了六个整数值。active 与 notInService 可以读也可以写;notReady 可以读、不能写;createAndGo、createAndWait 与 destroy 可以写,却永远不会作为读取结果出现。

因此,写入 createAndWait 不是在给行贴一个持久标签。它要求代理创建该概念行,然后如实报告结果。如果已有足够资料去尝试投入使用,状态变成 notInService;如果还缺必要资料,状态就是 notReady。动作已经结束,状态才是接下来可观察的事实。

这一区分也限制了管理程序能够声称的内容。收到 notReady 不等于写入失败:行可能已经存在并占用资源。收到 notInService 也不等于配置正确或服务必然能启动:它只表示资料足以走到下一次判断。

一行占着资源时,代理不能永远等

多轮创建意味着管理程序可能在中途消失。网络中断、进程崩溃或操作员离开,都可能使行长期停在 notReady 或 notInService。它尚未服务,却可能已经占用内存、表项或监测资源。

RFC 2579 因此把清理责任明确放到代理端:代理必须发现异常久未推进的行并将其移除。具体多久应由状态列的 DESCRIPTION 说明;只有在没有说明时,文档才建议大约五分钟。把这个建议写成所有设备统一的五分钟定时器,会抹掉对象定义应承担的政策责任。

清理规则也适用于被停用后长期遗留的旧行,不只针对刚创建的新行。可见的中间状态既方便恢复,也会积累资源债务。协议没有假装客户端一定会回来,所以必须给持有资源的一侧一个有限的退出办法。

先暴露缺失,再讨论启用

createAndWait 最有意思的地方,是允许管理程序在不知道所有必需值时先建立行。代理返回 notReady 后,管理程序可以读取各列。对于尚不存在、又必须初始化的实例,它会看到 noSuchInstance,随后再写入所需内容。资料达到能够尝试激活的门槛后,状态转为 notInService。

这种流程把“缺什么”留在同一个可检查的管理界面内,而不是只在某个厂商工具的临时会话里保存。另一个管理程序也能看见行尚未服务。自动化失败之后,操作员不必仅凭一个客户端日志猜测设备是否已经留下半成品。

不过,notInService 只跨过了资料门槛,不是质量证明。管理程序再写入 active 时,代理仍可因为值彼此不一致而返回 inconsistentValue。资源在创建后可能变得不足,设备状态也可能改变。RowStatus 让阶段清楚,却没有替代理预先承诺最后的决定。

不同表还可以在自身的 DESCRIPTION 中限定哪些列能在 active 或 notInService 时修改。不能从某一张表可以停用后编辑,推导出所有 RowStatus 表都采用同样政策。通用约定提供骨架,具体对象定义仍提供约束。

强保证只包住一条请求

SNMP 对单次写入的保证比“逐列随便改”更强。2002 年的 RFC 3416 要求代理先验证 SetRequest 中的变量绑定,再进行赋值。同一个请求里的赋值相对于彼此要表现得像同时发生。如果验证之后某项赋值失败,其他赋值应被撤销,并返回 commitFailed;若无法全部撤销,则返回 undoFailed。

这条边界很重要,也很窄。管理程序可以把创建所需的若干列与 createAndGo 放在一个请求中,得到该请求范围内的共同校验与提交处理。但 createAndWait、补齐列、重新读取和激活通常跨越多次交换。前一次请求已经完成,后一次才开始。两者之间可能断线、竞争或发生设备侧变化。

更不能把一个代理对一个 PDU 的处理写成跨多台路由器的分布式事务。RFC 3416 的“仿佛同时”只比较同一请求里的赋值。commitFailed 与 undoFailed 暴露了本地提交和回滚边界,并没有创造全网协调器。

看得见的等待仍然不是锁

暂存行很容易被误读为预约。RFC 2579 特别警告:在管理程序写入 createAndWait 与后来写入 active 之间,受管设备可能自行创建同一实例。到激活时,代理持有的值可能覆盖管理程序先前提供的值。

这不是一个附带的实现怪癖,而是多轮交互边界的直接后果。管理程序拥有可观察的中间状态,却没有因此获得排他锁。它应在激活前重新读取关键列,并把冲突或值变化当作需要裁决的事实,而不是把第一次写入当成永久保留。

同样,destroy 是动作,不是墓碑状态。若删除成功,属于该概念行的实例会被立即移除;无论此前为 active、notInService 或 notReady 都可以提出这个请求。后来读不到 destroy,并非设备隐藏了状态,而是命令已经以删除行的方式完成。

把“成功”拆成可核验的小结论

RowStatus 的价值,不在于保证复杂配置从此不会失败。它让失败发生在哪个阶段、谁还掌握决定、哪些资源需要处理,变得更容易观察。

管理程序提出创建动作,提供列值,并选择何时请求激活。代理判断实例是否能够创建、资料是否足够、值是否一致、资源是否可用,并清理被遗弃的中间状态。具体表的定义者说明可修改性和超时政策。每一方的职责都比“远程设置成功”这个笼统结论小,也更可验证。

这种设计保留了 SNMP 原有的小接口,而没有假装一行配置等于一个普通变量。行可以存在而尚未就绪,正是因为存在、完整、可激活与已经服务是不同事实。把它们压成一个成功位,会使自动化显得简洁,却把最需要处理的中间世界藏起来。

来源