跳到主要内容

3. 基础 IETF 服务保障 YANG Module

3. 基础 IETF 服务保障 YANG Module

3.1. 概念

"ietf-service-assurance" YANG module 假定存在一组需要独立保障的 subservice. subservice 是给定服务实例所依赖的网络系统功能或子部分. subservice 类型示例包括:

  • device: 设备是否健康, 如果不健康, 症状是什么? 这类 subservice 可以监控设备资源, 例如 CPU, RAM, 或三态内容寻址存储器 (Ternary Content-Addressable Memory, TCAM). 可能的症状包括 "CPU overloaded", "Out of RAM", 或 "Out of TCAM".

  • ip-connectivity: 给定绑定到两个设备的两个 IP 地址, 它们之间的 IP 连通性质量如何? 可能的症状包括 "No route available" 或 "Equal-Cost Multipaths (ECMPs) imbalance".

device subservice 的实例表示网络系统的一个子部分, 即某个具体设备. ip-connectivity subservice 的实例表示网络的一项功能, 即两个设备上两个特定 IP 地址之间的连通性. 在这两种情况下, 这些 subservice 都可能依赖其他 subservice. 例如, 连通性可能依赖表示路由系统的 subservice, 也可能依赖表示 ECMP 的 subservice.

上述两个示例 subservice 需要不同的参数集合才能完整刻画其实例. device subservice 的实例只需一个用于标识待监控设备的参数即可完整刻画. 对于 ip-connectivity subservice, 至少需要链路两端的设备和 IP 地址才能完整刻画一个实例.

本节给出的基础模型规定了一种 subservice 类型, 用于表示服务实例. 这类节点在保障图中具有特殊作用, 因为它们表示对应服务实例的保障图起点或根. 完整标识一个服务实例所需的参数是服务名称和服务实例名称. 为支持 device 或 ip-connectivity 等其他类型的 subservice, "ietf-service-assurance" module 预期被扩展.

依赖关系被建模为一个列表, 即每个 subservice 都包含一个指向其依赖项的引用列表. 如果该 subservice 实例没有任何依赖项, 该列表可以为空.

通过以 subservice 的形式指定服务实例及其依赖关系, 即可定义一个全局保障图. 该保障图是将所有受保障服务实例的单个保障图合并后的结果. 即使多个服务实例依赖同一个 subservice 实例, 每个 subservice 实例在全局保障图中也预期只出现一次. 例如, device subservice 的一个实例是依赖对应设备的每个服务实例的依赖项. 某个特定服务实例的保障图, 是从该特定服务实例开始沿依赖关系遍历全局保障图所得到的子图.

配置了这类图的保障 agent, 预期为每个已配置的 subservice 生成健康状态, 指示该 subservice 的健康程度. 如果 subservice 不健康, agent 预期生成一个症状列表, 说明该 subservice 不健康的原因.

3.2. 树视图

以下树图 [RFC8340] 概述了 "ietf-service-assurance" module.

module: ietf-service-assurance
+--ro assurance-graph-last-change yang:date-and-time
+--rw subservices
| +--rw subservice* [type id]
| +--rw type identityref
| +--rw id string
| +--ro last-change? yang:date-and-time
| +--ro label? string
| +--rw under-maintenance!
| | +--rw contact string
| +--rw (parameter)
| | +--:(service-instance-parameter)
| | +--rw service-instance-parameter
| | +--rw service string
| | +--rw instance-name string
| +--ro health-score int8
| +--ro symptoms-history-start? yang:date-and-time
| +--ro symptoms
| | +--ro symptom* [start-date-time agent-id symptom-id]
| | +--ro symptom-id leafref
| | +--ro agent-id -> /agents/agent/id
| | +--ro health-score-weight? uint8
| | +--ro start-date-time yang:date-and-time
| | +--ro stop-date-time? yang:date-and-time
| +--rw dependencies
| +--rw dependency* [type id]
| +--rw type
| | -> /subservices/subservice/type
| +--rw id leafref
| +--rw dependency-type? identityref
+--ro agents
| +--ro agent* [id]
| +--ro id string
| +--ro symptoms* [id]
| +--ro id string
| +--ro description string
+--ro assured-services
+--ro assured-service* [service]
+--ro service leafref
+--ro instances* [name]
+--ro name leafref
+--ro subservices* [type id]
+--ro type -> /subservices/subservice/type
+--ro id leafref

"assurance-graph-last-change" 中的最后变更日期为只读. 每当通过添加或删除 subservice 和依赖关系, 或修改其可配置属性 (包括维护状态) 而改变图结构时, 都必须更新该日期. 这类修改对应图中的结构性变化. 最后变更日期有助于客户端快速检查是否需要更新图结构. 与服务或 subservice 关联的健康评分或症状发生变化并不会改变图结构, 因而不会影响最后变更日期.

"subservices" 列表包含服务器 (即 SAIN agent 或 SAIN collector) 当前已知的所有 subservice 实例. subservice 声明 MUST 提供以下内容:

  • subservice 类型 ("type"): 指向继承自 "subservice-base" 的 identity 的引用, 其中 "subservice-base" 是任意 subservice 类型的基础 identity

  • id ("id"): 一个字符串, 用于在相同类型的 subservice 中唯一标识该 subservice

type 和 id 共同唯一标识一个给定 subservice.

"last-change" 指示该特定 subservice 的依赖关系或维护状态最后一次被修改的时间.

"label" 是该 subservice 的人类可读描述.

"under-maintenance" container 的存在会抑制该 subservice 以及依赖它的 subservice 的症状发出. 在这种情况下, MUST 提供 "contact" 以指明由谁或哪个软件负责维护. 更详细讨论见 [RFC9417] 第 3.6 节.

"parameter" choice 预期被扩展, 以描述特定于当前 subservice 类型的参数. 该基础 module 仅定义表示服务实例的 subservice 类型. 服务实例 MUST 建模为一种特定类型的 subservice, 并带有两个参数: "service" 和 "instance-name". "service" 参数是网络 orchestrator 中定义的服务名称, 例如 "point-to-point-l2vpn". "instance-name" 参数是分配给特定受保障实例的名称, 例如使用该实例的客户名称.

"health-score" 包含通常介于 0 和 100 之间的值, 指示该 subservice 的健康程度. 如健康评分定义所述, 特殊值 -1 可用于表示无法计算该健康评分的值, 例如计算所需的某些 metric 无法收集时.

"symptoms-history-start" 是报告症状的截止日期. 在该日期之前已终止的症状不再在模型中报告.

每个 subservice 的状态都包含一个症状列表. 每个症状由以下内容指定:

  • 标识符 "symptom-id", 用于在 agent 本地标识该症状,

  • agent 标识符 "agent-id", 用于标识报告该症状的 agent,

  • "health-score-weight", 用于指定该症状对健康评分造成的影响,

  • "start-date-time", 指示该症状何时变为活动状态, 以及

  • "stop-date-time", 指示该症状何时停止活动 (如果症状仍处于活动状态, 则不存在此字段).

为使 "agent-id" 和 "symptom-id" 这一对值能够唯一标识一个症状, 需要满足以下条件:

  • "agent-id" MUST 在系统的所有 agent 中唯一.

  • "symptom-id" MUST 在该 agent 报告的所有症状中唯一.

注意, "agent-id" 和 "symptom-id" 是指向本文档后续定义对象的 leafref. 虽然 "symptom-id" 和 "agent-id" 的组合足以作为唯一键列表, 但第二个键 "start-date-time" 有助于排序和检索相关症状.

"dependency" 列表包含当前 subservice 的依赖项. 每个依赖项都通过指向目标依赖项 "type" 和 "id" 的 leafref 指定. 依赖项具有由 "dependency-type" 字段指示的类型. 模型中规定了两种类型:

  • Impacting: 这类依赖关系表示会影响依赖方的健康状态.

  • Informational: 这类依赖关系可能解释依赖方为何出现问题, 但不会影响其健康状态.

为说明 "impacting" 与 "informational" 的差异, 考虑表示网络接口的 interface subservice. 如果该网络接口所属的设备宕机, 网络接口也会转换到 "down" 状态. 因此, interface subservice 对 device subservice 的依赖关系是 "impacting". 另一方面, 对 ecmp-load subservice 的依赖关系只是 "informational", 该 subservice 检查 ECMP 之间的负载是否随时间保持稳定. 实际上, 即使 ECMP 之间的负载分布发生变化, 服务也可能完全健康. 但是, 这种不稳定性可能是诊断问题根因的相关症状.

在 container "agents" 中, 列表 "agent" 包含按 agent 划分的症状列表. 该列表的键是 "id", 它 MUST 在给定保障系统的各 agent 中唯一. 对于每个 agent, 列表 "symptoms-description" 将 "id" 映射到其 "description". "id" MUST 在该 agent 报告的症状中唯一.

在 container "assured-services" 中, 列表 "assured-service" 包含按受保障服务实例索引的 subservice. 对于由 "service" leaf 标识的每种服务类型, 该服务的所有实例都列在 "instances" 列表中. 对于由 "name" leaf 标识的每个实例, "subservices" 列表包含属于该特定实例保障图的所有后代 subservice. 这些嵌套列表提供了一种查询优化: 可以通过单次查询获取该保障图中的 subservice 列表, 而不需要从表示服务实例的节点开始递归查询每个 subservice 的依赖项.

本文档未显式定义健康评分 ("health-score") 与当前活动症状的 "health-score-weight" 之间的关系. 唯一要求是, 严格小于 100 (最大值) 的健康评分必须由至少一个症状解释. 强制满足该要求的一种方式是先检测症状, 再基于检测到的症状的 "health-score-weight" 计算健康评分. 例如, 这样的计算可以是将活动症状的 "health-score-weight" 求和, 从 100 中减去该值, 如果结果为负则将其改为 0. 健康评分与 "health-score-weight" 之间的关系留给 (agent [RFC9417] 的) 实现者决定.

保存图结构历史不属于该 YANG module 的范围. 只能获取保障图的当前版本. 为保存图结构历史, 必须使用某种时间序列数据库 (time-series database, TSDB) 或类似存储.

3.3. YANG Module

该模型包含对 [RFC6991] 的引用.

<CODE BEGINS> file "[email protected]"
module ietf-service-assurance {
yang-version 1.1;
namespace "urn:ietf:params:xml:ns:yang:ietf-service-assurance";
prefix sain;

import ietf-yang-types {
prefix yang;
reference
"RFC 6991: Common YANG Data Types";
}

organization
"IETF OPSAWG Working Group";
contact
"WG Web: <https://datatracker.ietf.org/wg/opsawg/>
WG List: <mailto:[email protected]>
Author: Benoit Claise <mailto:[email protected]>
Author: Jean Quilbeuf <mailto:[email protected]>";
description
"This module defines objects for assuring services based on their
decomposition into so-called subservices, according to the
Service Assurance for Intent-based Networking (SAIN)
architecture.

The subservices hierarchically organized by dependencies
constitute an assurance graph. This module should be supported
by an assurance agent that is able to interact with the devices
in order to produce the health status and symptoms for each
subservice in the assurance graph.

This module is intended for the following use cases:
* Assurance graph configuration:
- Subservices: Configure a set of subservices to assure by
specifying their types and parameters.
- Dependencies: Configure the dependencies between the
subservices, along with their type.
* Assurance telemetry: Export the health statuses of the
subservices, along with the observed symptoms.

Copyright (c) 2023 IETF Trust and the persons identified as
authors of the code. All rights reserved.

Redistribution and use in source and binary forms, with or
without modification, is permitted pursuant to, and subject
to the license terms contained in, the Revised BSD License
set forth in Section 4.c of the IETF Trust's Legal Provisions
Relating to IETF Documents
(https://trustee.ietf.org/license-info).

This version of this YANG module is part of RFC 9418; see the
RFC itself for full legal notices. ";

revision 2023-07-11 {
description
"Initial version.";
reference
"RFC 9418: YANG Modules for Service Assurance";
}

identity subservice-base {
description
"Base identity for subservice types.";
}

identity service-instance-type {
base subservice-base;
description
"Specific type of subservice that represents a service
instance. Instance of this type will depend on other
subservices to build the top of the assurance graph.";
}

identity dependency-type {
description
"Base identity for representing dependency types.";
}

identity informational {
base dependency-type;
description
"Indicates that symptoms of the dependency might be of interest
for the dependent, but the status of the dependency should not
have any impact on the dependent.";
}

identity impacting {
base dependency-type;
description
"Indicates that the status of the dependency directly impacts
the status of the dependent.";
}

grouping subservice-reference {
description
"Reference to a specific subservice identified by its type and
identifier. This grouping is only for internal use in this
module.";
leaf type {
type leafref {
path "/subservices/subservice/type";
}
description
"The type of the subservice to refer to (e.g., device).";
}
leaf id {
type leafref {
path "/subservices/subservice[type=current()/../type]/id";
}
description
"The identifier of the subservice to refer to.";
}
}

grouping subservice-dependency {
description
"Represents a dependency to another subservice. This grouping
is only for internal use in this module";
uses subservice-reference;
leaf dependency-type {
type identityref {
base dependency-type;
}
description
"Represents the type of dependency (e.g., informational or
impacting).";
}
}

leaf assurance-graph-last-change {
type yang:date-and-time;
config false;
mandatory true;
description
"Time and date at which the assurance graph last changed after
any structural changes (dependencies and/or maintenance
windows parameters) are applied to the subservice(s). The
time and date must be the same or more recent than the most
recent value of any changed subservices last-change time and
date.";
}
container subservices {
description
"Root container for the subservices.";
list subservice {
key "type id";
description
"List of configured subservices.";
leaf type {
type identityref {
base subservice-base;
}
description
"Type of the subservice identifying the type of the part
or functionality that is being assured by this list
entry, for instance, interface, device, or
ip-connectivity.";
}
leaf id {
type string;
description
"Identifier of the subservice instance. Must be unique
among subservices of the same type.";
}
leaf last-change {
type yang:date-and-time;
config false;
description
"Date and time at which the structure for this
subservice instance last changed, i.e., dependencies
and/or maintenance windows parameters.";
}
leaf label {
type string;
config false;
description
"Label of the subservice, i.e., text describing what the
subservice is to be displayed on a human interface.

It is not intended for random end users but for
network/system/software engineers that are able to
interpret it. Therefore, no mechanism for language
tagging is needed.";
}
container under-maintenance {
presence "true";
description
"The presence of this container indicates that the current
subservice is under maintenance.";
leaf contact {
type string;
mandatory true;
description
"A string used to model an administratively assigned name
of the resource that is performing maintenance.

It is suggested that this freeform field, which could be
a URI, contains one or more of the following: IP
address, management station name, network manager's
name, location, and/or phone number. It might even
contain the expected maintenance time.

In some cases, the agent itself will be the owner of an
entry. In these cases, this string shall be set to a
string starting with 'monitor'.";
}
}
choice parameter {
mandatory true;
description
"Specify the required parameters per subservice type. Each
module augmenting this module with a new subservice type
that is a new identity based on subservice-base should
augment this choice as well by adding a container
available only if the current subservice type is
the newly added identity.";
container service-instance-parameter {
when "derived-from-or-self(../type,
'sain:service-instance-type')";
description
"Specify the parameters of a service instance.";
leaf service {
type string;
mandatory true;
description
"Name of the service.";
}
leaf instance-name {
type string;
mandatory true;
description
"Name of the instance for that service.";
}
}
// Other modules can augment their own cases into here.
}
leaf health-score {
type int8 {
range "-1 .. 100";
}
config false;
mandatory true;
description
"Score value of the subservice health. A value of 100
means that the subservice is healthy. A value of 0 means
that the subservice is broken. A value between 0 and 100
means that the subservice is degraded. The special value
-1 means that the health score could not be computed.";
}
leaf symptoms-history-start {
type yang:date-and-time;
config false;
description
"Date and time at which the symptom's history starts for
this subservice instance, either because the subservice
instance started at that date and time or because the
symptoms before that were removed due to a garbage
collection process.";
}
container symptoms {
config false;
description
"Symptoms for the subservice.";
list symptom {
key "start-date-time agent-id symptom-id";
unique "agent-id symptom-id";
description
"List of symptoms of the subservice. While the
start-date-time key is not necessary per se, this would
get the entries sorted by start-date-time for easy
consumption.";
leaf symptom-id {
type leafref {
path "/agents/agent[id=current()/../agent-id]"
+ "/symptoms/id";
}
description
"Identifier of the symptom to be interpreted according
to the agent identified by the agent-id.";
}
leaf agent-id {
type leafref {
path "/agents/agent/id";
}
description
"Identifier of the agent raising the current symptom.";
}
leaf health-score-weight {
type uint8 {
range "0 .. 100";
}
description
"The weight to the health score incurred by this
symptom. The higher the value, the more of an impact
this symptom has. If a subservice health score is not
100, there must be at least one symptom with a
health-score-weight larger than 0.";
}
leaf start-date-time {
type yang:date-and-time;
description
"Date and time at which the symptom was detected.";
}
leaf stop-date-time {
type yang:date-and-time;
description
"Date and time at which the symptom stopped being
detected. Must be after the start-date-time. If the
symptom is ongoing, this field should not be
populated.";
}
}
}
container dependencies {
description
"Indicates the set of dependencies of the current
subservice, along with their types.";
list dependency {
key "type id";
description
"List of dependencies of the subservice.";
uses subservice-dependency;
}
}
}
}
container agents {
config false;
description
"Container for the list of agents' symptoms.";
list agent {
key "id";
description
"Contains symptoms of each agent involved in computing the
health status of the current graph. This list acts as a
glossary for understanding the symptom ids returned by each
agent.";
leaf id {
type string;
description
"Id of the agent for which we are defining the symptoms.
This identifier must be unique among all agents.";
}
list symptoms {
key "id";
description
"List of symptoms raised by the current agent that is
identified by the symptom-id.";
leaf id {
type string;
description
"Id of the symptom for the current agent. The agent must
guarantee the unicity of this identifier.";
}
leaf description {
type string;
mandatory true;
description
"Description of the symptom, i.e., text describing what
the symptom is, is to be computer consumable and
displayed on a human interface.

It is not intended for random end users but for
network/system/software engineers that are able to
interpret it. Therefore, no mechanism for language
tagging is needed.";
}
}
}
}
container assured-services {
config false;
description
"Container for the index of assured services.";
list assured-service {
key "service";
description
"Service instances that are currently part of the assurance
graph. The list must contain an entry for every service
that is currently present in the assurance graph. This list
presents an alternate access to the graph stored in
subservices that optimizes querying the assurance graph of
a specific service instance.";
leaf service {
type leafref {
path "/subservices/subservice/service-instance-parameter/"
+ "service";
}
description
"Name of the service.";
}
list instances {
key "name";
description
"Instances of the service. The list must contain
an entry for every instance of the parent service.";
leaf name {
type leafref {
path "/subservices/subservice/service-instance-parameter"
+ "/instance-name";
}
description
"Name of the service instance. The leafref must point to
a service-instance-parameter whose service leaf matches
the parent service.";
}
list subservices {
key "type id";
description
"Subservices that appear in the assurance graph of the
current service instance.

The list must contain the subservice corresponding to
the service instance, i.e., the subservice that
matches the service and instance-name keys.

For every subservice in the list, all subservices listed
as dependencies must also appear in the list.";
uses subservice-reference;
}
}
}
}
}
<CODE ENDS>

3.4. 拒绝循环依赖

服务和 subservice 的状态取决于其依赖项的状态, 因而它们之间的循环依赖会阻止状态计算. SAIN 架构文档 [RFC9417] 第 3.1.1 节讨论了这类依赖关系如何出现以及如何移除. 避免这类依赖关系的责任由 SAIN orchestrator 承担. 但是, 本节规定支持 "ietf-service-assurance" module 的服务器在收到包含循环依赖的数据实例时的预期行为.

将不存在循环依赖作为 YANG 约束来强制执行, 最终会退化为使用 XPath 实现图遍历算法, 并检查当前节点无法从其依赖项到达. 即使存在这类约束, 也不能保证合并两个无依赖环的图后所得图仍无依赖环. 实际上, [RFC9417] 第 3.1.1 节给出了一个示例, 其中两个无依赖环的图合并后产生了带依赖环的图.

因此, 实现 "ietf-service-assurance" module 的服务器在每次修改图时 MUST 检查不存在依赖环. 会创建依赖环的修改 MUST 被拒绝.