图表
图表概览
Apstra 使用图形模型来表示有关基础架构、策略、约束等的单一事实来源。这个 Graph 模型会不断变化,我们可以出于各种原因对其进行查询。它以图形的形式表示。有关网络的所有信息均被建模为节点以及节点之间的关系。
图形中的每个对象都有一个唯一的 ID。节点有一个类型(一个字符串)和一组基于特定类型的附加属性。例如,我们系统中的所有交换机都由系统类型的节点表示,并且可以具有属性角色,该角色决定在网络中分配它的角色(主干/叶/服务器)。物理和逻辑交换机端口由接口节点表示,该节点也具有称为 if_type 的属性。
不同节点之间的关系表示为图边,我们称之为关系。关系是定向的,这意味着每个关系都有一个源节点和一个目标节点。关系也具有一种类型,该类型确定特定关系可以具有哪些附加属性。例如,系统节点与接口节点具有 hosted_interfaces 类关系。
一组可能的节点和关系类型由图形架构确定。该模式定义了特定类型的节点和关系可以具有哪些属性,以及这些属性的类型(字符串/整数/布尔值/等)和约束。我们使用并维护一个开源模式库 Lollipop,它允许灵活地自定义值类型。
回到代表单一事实来源的图表,最具挑战性的方面之一是,当发生来自运维人员和管理系统的变化时,如何进行推理。为了支持这一点,我们开发了所谓的实时查询机制,它具有三个基本组件:
- 查询规范
- 变更通知
- 通知处理
将我们的域模型建模为图形后,您可以对图形查询指定的图形运行搜索,以查找图形中的特定模式(子图)。表达查询的语言在概念上基于 Gremlin,一种开源的图形遍历语言。我们还有用于用另一种语言表达的查询的解析器 - Cypher,这是一种流行的图形数据库 neo4j 使用的查询语言。
查询规范
您从 node() 开始,然后继续链接方法调用,在匹配关系和节点之间交替:
node('system', name='system').out().node('interface', name='interface').out().node('link', name='link')
上面用英文翻译的查询读起来是这样的:从系统类型的节点开始,遍历任何到达接口类型节点的传出关系,并从该节点遍历所有导致“链接”类型节点的传出关系。
您可以随时添加额外的约束:
node('system', role='spine', name='system').out().node('interface', if_type='ip', name='interface')
请注意 role='主干' 参数,它将仅选择将角色属性设置为主干的系统节点。
接口节点的if_type属性相同。
node('system', role=is_in(['spine', 'leaf']), name='system')
.out()
.node('interface', if_type=ne('ip'), name='interface')
该查询将选择具有 主干 或叶角色的所有系统节点,以及除 ip 之外if_type任何内容的接口节点(ne 表示不相等)。
您还可以添加可以是任意 Python 函数的跨对象条件:
node('system', name='system')
.out().node('interface', name='if1')
.out().node('link')
.in_().node('interface', name='if2')
.in_().node('system', name='remote_system')
.where(lambda if1, if2: if1.if_type != if2.if_type)
Name 对象来引用它们,并将这些名称用作约束函数的参数名称(当然,您可以覆盖它,但它会做出方便的默认行为)。因此,在上面的示例中,它将采用两个名为 if1 和 if2 的接口节点,将它们传递给给定的 where 函数并过滤掉这些路径,其中函数返回 False。不要担心将约束放置在何处:一旦约束引用的所有对象都可用,它就会在搜索过程中应用。
现在,你有一条路径,你可以用它来进行搜索。但是,有时您可能希望查询比单个路径更复杂。为了支持这一点,查询 DSL 允许您在同一查询中定义多个路径,用逗号分隔:
match(
node('a').out().node('b', name='b').out().node('c'),
node(name='b').out().node('d'),
)
此match()函数创建路径组。在不同路径中共享相同名称的所有对象实际上都将引用同一个对象。此外,允许使用 where()对对象添加更多约束。 match()您可以对特定对象进行不同的搜索,这将确保每个值组合在结果中只看到一次:
match(
node('a', name='a').out().node('b').out().node('c', name='c')
).distinct(['a', 'c'])
这匹配了 a -> b -> c 节点的链。如果两个节点 a 和 c 通过多个 b 类节点连接,则结果仍将仅包含一对 (a, c)。
编写查询时可以使用另一种方便的模式:将结构与条件分开:
match(
node('a', name='a').out().node('b').out().node('c', name='c'),
node('a', foo='bar'),
node('c', bar=123),
)
查询引擎会将该查询优化为:
match(
node('a', name='a', foo='bar')
.out().node('b')
.out().node('c', name='c', bar=123)
)
没有笛卡尔乘积,没有不必要的步骤。
变更通知
好的,现在您已经定义了一个图形查询。通知结果是什么样子的?每个结果都将是一个字典,将您为查询对象定义的名称映射到找到的对象。例如,对于以下查询
node('a', name='a').out().node('b').out().node('c', name='c')
结果将如下所示 {'a': <node type='a'>, 'c': <node type='c'>}。请注意,仅存在命名对象(结果中没有 <node type='b'> ,尽管该节点存在于查询中,因为它没有名称)。
您注册一个要监控的查询和一个回调,以便在发生更改时执行。稍后,如果有人修改正在监视的图形,它将检测到新图形更新导致出现新的查询结果,或者旧结果消失或更新。响应执行与查询关联的回调。回调接收来自查询的整个路径作为响应,以及要执行的特定操作(添加/更新/删除)。
通知处理
当结果传递给处理(回调)函数时,您可以从那里指定推理逻辑。这实际上可以是任何内容,从生成日志、错误到呈现配置,或是运行语义验证。您还可以使用图形 API 修改图形本身,其他一些逻辑可能会对您所做的更改做出反应。这样,您可以将图形强制用作单一事实来源,同时它还充当应用程序逻辑各个部分之间的逻辑通信通道。图形 API 由三个部分组成:
图形管理 - 在图形中添加/更新/删除内容的方法。add_node(), set_node(), set_relationship()get_relationship()del_relationship()add_relationship()del_node()get_node()查询 commit() get_nodes()get_relationships()可观察接口add_observer(),remove_observer()
图形管理 API 是不言自明的。 add_node() 创建新节点, set_node() 更新现有节点的属性,并 del_node() 删除节点。
commit() 用于表示对图形的所有更新都已完成,并且可以传播到所有侦听器。
关系具有类似的 API。
可观察接口允许您添加/删除观察者 - 实现通知的对象,回调接口。通知回调包含三种方法:
on_node()- 添加、删除或更新任何节点/关系时调用on_relationship()- 添加、删除或更新任何节点/关系时调用on_graph()- 在提交图表时调用
查询 API 是我们图形 API 的核心,是所有搜索的动力。两者都get_nodes()get_relationships()允许您在图形中搜索相应的对象。这些函数的参数是对搜索对象的约束。
例如, get_nodes() 返回图形中的所有节点, get_nodes(type='system') 返回所有系统节点, get_nodes(type='system', role='spine') 允许您将返回的节点限制为具有特定属性值的节点。每个参数的值可以是纯值,也可以是特殊属性匹配器对象。如果该值是纯值,则相应的结果对象的属性应等于给定的纯值。属性匹配器允许您表达更复杂的条件,例如不等于、小于给定值之一等:
下面的示例用于直接使用 Graph python。出于演示目的,可以在 Graph 资源管理器中将 graph.get_nodes 替换为 node。此特定示例不适用于 Apstra GUI。
graph.get_nodes(
type='system',
role=is_in(['spine', 'leaf']),
system_id=not_none(),
)
在图形架构中,可以为特定节点/关系类型和方法 get_nodes() 定义自定义索引,并为 get_relationships() 传递的每个特定约束组合选择最佳索引,以最大限度地减少搜索时间。
/get_relationships() 的结果get_nodes()是特殊的迭代器对象。您可以迭代它们,它们将产生所有找到的图形对象。您还可以使用这些迭代器提供的 API 来导航这些结果集。例如,get_nodes()返回一个 NodeIterator 对象,该对象具有方法 out() 和 in_().您可以使用它们从原始结果集中的每个节点获取所有传出或传入关系的迭代器。然后,您可以使用这些节点在这些关系的另一端获取节点并从它们继续。您还可以将属性约束传递给这些方法,就像 for get_nodes() 和 get_relationships()一样。
graph.get_nodes('system', role='spine') \
.out('interface').node('interface', if_type='loopback')
上面示例中的代码查找类型为系统和角色主干的所有节点,然后找到它们的所有环路接口。
汇而总之
下面的查询是 Apstra 可用来推导出遥测预期(例如,链路和接口状态)的内部规则示例。@rule 将插入一个回调到 process_主干_leaf_link,在这种情况下,我们按照遥测预期写入。
@rule(match(
node('system', name='spine_device', role='spine')
.out('hosted_interfaces')
.node('interface', name='spine_if')
.out('link')
.node('link', name='link')
.in_('link')
.node('interface', name='leaf_if')
.in_('hosted_interfaces')
.node('system', name='leaf_device', role='leaf')
))
def process_spine_leaf_link(self, path, action):
"""
Process link between spine and leaf
"""
spine = path['spine_device']
leaf = path['leaf_device']
if action in ['added', 'updated']:
# do something with added/updated link
pass
else:
# do something about removed link
pass
便利功能
为避免在生成图表查询时创建复杂的 where() 子句,请使用 Apstra GUI 中提供的便利函数。
-
从蓝图中导航到“ 暂存 ”视图或 “活动” 视图,然后单击“ GraphQL API 资源管理器” 按钮(右上角 >_)。图表浏览器将在新选项卡中打开。
-
在左侧键入图表查询。请参见下面的功能说明。
-
从 “操作” 下拉列表中,选择“ qe”。
-
单击 “执行查询 ”按钮(看起来像播放按钮)以查看结果。
功能
查询引擎描述了许多有用的函数:
匹配(*path_queries)
此函数返回一个 QueryBuilder 对象,其中包含匹配查询的每个结果。这通常是将多个匹配查询分组在一起的有用快捷方式。
这两个查询不是一起的“路径”(没有预期的关系)。请注意逗号以分隔参数。此查询将一起返回所有叶设备和主干设备。
match(
node('system', name='leaf', role='leaf'),
node('system', name='spine', role='spine'),
)
node(self, type=None, name=None, id=None, **properties)
- 参数
- type (str 或 None) - 要搜索的节点类型
- name (str 或 None) - 设置结果中属性匹配器的名称
- id (str 或 None) - 按图形中的节点 ID 匹配特定节点
- properties (dict 或 None) - 要使用的任何其他关键字参数或附加属性匹配器便利函数
- 返回 - 用于链接查询的查询生成器对象
- 返回类型 - QueryBuilder
虽然两者都是一个函数,但这是 PathQueryBuilder 节点的别名 -- 见下文。
iterate()
- 返回 - 生成器
- 返回类型:生成器
Iterate 为您提供了一个生成器函数,您可以使用该函数来迭代单个路径查询,就好像它是一个列表一样。例如:
def find_router_facing_systems_and_intfs(graph):
return q.iterate(graph, q.match(
q.node('link', role='to_external_router')
.in_('link')
.node('interface', name='interface')
.in_('hosted_interfaces')
.node('system', name='system')
))
PathQueryBuilder 节点
node(self, type=None, name=None, id=None, **properties)
该函数描述了特定的图形节点,但也是从特定节点开始路径查询的快捷方式。调用的结果 `node() 返回路径查询对象。查询路径时,通常需要指定节点 'type':例如 node('system') 返回系统节点。
- 参数
- type (str 或 None) - 要搜索的节点类型
- name (str 或 None) - 设置结果中属性匹配器的名称
- id (str 或 None) - 按图形中的节点 ID 匹配特定节点
- properties (dict 或 None) - 要使用的任何其他关键字参数或附加属性匹配器便利函数
- 返回 - 用于链接查询的查询生成器对象
- 返回类型 - QueryBuilder
如果要在查询结果中使用该节点,则需要将其命名为 --node('system', name='device')。此外,如果要匹配特定的 kwarg 属性,可以直接指定匹配要求 -
node('system', name='device', role='leaf')
node('system', name='device', role='leaf')
out(type=None, id=None, name=None, **properties)
根据图形模式在“出”方向上遍历关系。可接受的参数包括关系类型(例如接口)、关系的特定名称、关系的 ID 或其他属性匹配项,这些匹配项必须完全匹配为关键字参数。
- 参数
- type (str 或 None) - 要搜索的节点关系类型
- id (str 或 None) - 按图表中的关系 ID 匹配特定关系
- name (str 或 None) - 按命名关系匹配特定关系
例如:
node('system', name='system') \
.out('hosted_interfaces')
in_(type=None, id=None, name=None, **properties)
在“in”方向上遍历关系。将当前节点设置为关系源节点。可接受的参数包括关系类型(例如接口)、关系的特定名称、关系的 ID 或其他属性匹配项,这些匹配项必须完全匹配为关键字参数。
- 参数
- type (str 或 None) - 要搜索的节点关系类型
- id (str 或 None) - 按图表中的关系 ID 匹配特定关系
- name (str 或 None) - 按命名关系匹配特定关系
- properties (dict 或 None) - 通过任何其他 kwarg 或函数匹配关系
node('interface', name='interface') \
.in_('hosted_interfaces')
where(谓词,名称=无)
允许您针对图形结果指定回调函数作为过滤器或约束。谓词是针对整个查询结果运行的回调(通常是 lambda 函数)。 where() 可直接用于 A 路径查询结果。
- 参数
- predicate (callback) - 针对图形中所有节点运行的回调函数
- names(str 或 None) - 如果给出了名称,则将其传递给回调函数以进行匹配
node('system', name='system') \
.where(lambda system: system.role in ('leaf', 'spine'))
enure_different(*姓名)
允许用户确保图形中两个不同的命名节点不相同。这对于可能是双向且可能在其自己的源节点上匹配的关系很有帮助。考虑查询:
- 参数
- 名称(元组或列表) - 一个名称列表,以确保从图中返回不同的节点或关系
match(node('system', name='system', role='leaf') \
.out('hosted_interfaces') \
.node('interface', name='interface', ipv4_addr=not_none()) \
.out('link') \
.node('link', name='link') \
.in_('link') \
.node('interface', name='remote_interface', ipv4_addr=not_none())) \
.ensure_different('interface', 'remote_interface')
最后一行在功能上可以等同于具有 lambda 回调函数的 where() 函数
match(node('system', name='system', role='leaf') \
.out('hosted_interfaces') \
.node('interface', name='interface', ipv4_addr=not_none()) \
.out('link') \
.node('link', name='link') \
.in_('link') \
.node('interface', name='remote_interface', ipv4_addr=not_none())) \
.where(lambda interface, remote_interface: interface != remote_interface)
属性匹配器
属性匹配可以直接在图形查询对象上运行 - 通常在函数中使用 node() 。属性匹配允许一些函数。
eq(值)
确保节点的属性值与函数的结果 eq(value) 完全匹配。
- 参数
- value - 要匹配相等的属性
node('system', name='system', role=eq('leaf'))
这类似于简单地在节点对象上将值设置为 kwarg:
node('system', name='system', role='leaf')
node('system', name='system').where(lambda system: system.role == 'leaf')
返回:
{
"count": 4,
"items": [
{
"system": {
"tags": null,
"hostname": "l2-virtual-mlag-2-leaf1",
"label": "l2_virtual_mlag_2_leaf1",
"system_id": "000C29EE8EBE",
"system_type": "switch",
"deploy_mode": "deploy",
"position": null,
"role": "leaf",
"type": "system",
"id": "391598de-c2c7-4cd7-acdd-7611cb097b5e"
}
},
{
"system": {
"tags": null,
"hostname": "l2-virtual-mlag-2-leaf2",
"label": "l2_virtual_mlag_2_leaf2",
"system_id": "000C29D62A69",
"system_type": "switch",
"deploy_mode": "deploy",
"position": null,
"role": "leaf",
"type": "system",
"id": "7f286634-fbd1-43b3-9aed-159f1e0e6abb"
}
},
{
"system": {
"tags": null,
"hostname": "l2-virtual-mlag-1-leaf2",
"label": "l2_virtual_mlag_1_leaf2",
"system_id": "000C29CFDEAF",
"system_type": "switch",
"deploy_mode": "deploy",
"position": null,
"role": "leaf",
"type": "system",
"id": "b9ad6921-6ce3-4d05-a5c7-c31d96785045"
}
},
{
"system": {
"tags": null,
"hostname": "l2-virtual-mlag-1-leaf1",
"label": "l2_virtual_mlag_1_leaf1",
"system_id": "000C297823FD",
"system_type": "switch",
"deploy_mode": "deploy",
"position": null,
"role": "leaf",
"type": "system",
"id": "71bbd11c-ed0f-4a38-842f-341781c01c24"
}
}
]
}
ne(值)
不相等。确保节点的属性值与函数的结果 ne(value) 不匹配
- 参数
- value - 确保不等式条件的值
node('system', name='system', role=ne('spine'))
类似于:
node('system', name='system').where(lambda system: system != 'spine')
gt(值)
大于。确保节点的属性大于函数的结果 gt(value) 。
- 参数
- value - 确保属性函数大于此值
node('vn_instance', name='vlan', vlan_id=gt(200))
ge(值)
大于或等于。确保节点的属性大于或等于 的结果 ge()。
- 参数: value - 确保属性函数大于或等于此值
node('vn_instance', name='vlan', vlan_id=ge(200))
lt(值)
小于。确保节点的属性小于 lt(value)的结果。
- 参数
- value - 确保属性函数小于此值
node('vn_instance', name='vlan', vlan_id=lt(200))
类似于:
node('vn_instance', name='vlan').where(lambda vlan: vlan.vlan_id <= 200)
le(值)
小于或等于。确保属性小于或等于函数的结果 le(value) 。
- 参数
- value - 确保给定值小于或等于属性函数
node('vn_instance', name='vlan', vlan_id=le(200))
类似于:
node('vn_instance', name='vlan').where(lambda vlan: vlan.vlan_id < 200)
is_in(值)
在(列表)中。检查该属性是否在给定的列表或包含项目 is_in(value)的集合中。
- 参数
- value (list) - 确保给定的属性在此列表中
node('system', name='system', role=is_in(['leaf', 'spine']))
类似于:
node('system', name='system').where(lambda system: system.role in ['leaf', 'spine'])
not_in(值)
不在(列表)中。检查该属性是否不在给定的列表或包含项目 not_in(value)的集合中。
- 参数
- value (list) - 列出值以确保属性匹配器不在
node('system', name='system', role=not_in(['leaf', 'spine']))
类似于:
node('system', name='system').where(lambda system: system.role not in ['leaf', 'spine'])
is_none()
期望is_none期望此特定属性的特定属性 None的查询。
node('interface', name='interface', ipv4_addr=is_none()
类似于:
node('interface', name='interface').where(lambda interface: interface.ipv4_addr is None)
not_none()
期望此属性具有值的匹配器。
node('interface', name='interface', ipv4_addr=not_none()
类似于:
node('interface', name='interface').where(lambda interface: interface.ipv4_addr is not None)
Apstra 图形数据存储
Apstra 图形数据存储是一个内存图形数据库。日志文件大小会定期检查,并在提交蓝图更改时检查。如果图形数据存储达到 100MB 或更多,将生成新的图形数据存储检查点文件。数据库本身不会移除任何图形数据存储持久性日志或检查点文件。Apstra 为主图形数据存储提供了清理工具。
有效的图形数据存储持久性文件组包含四个文件: log、 log-valid、 checkpoint和 checkpoint-valid。有效文件是日志和检查点文件的有效指标。每个持久化文件的名称由三个部分组成:basename、id 和 extension。
# regex for sysdb persistence files. # e.g. # _Main-0000000059ba612e-00017938-checkpoint-valid # \--/ \-----------------------/ \--------------/ # basename id extension
- BaseName - 派生自主图形数据存储分区名称。
- id - 从 gettimeofday 获取的 UNIX 时间戳。时间戳中的秒和微秒用“-”分隔。持久性文件组可以通过 id 标识。时间戳还可以帮助确定持久性文件组的生成时间序列。
- 扩展名 -
log、 、log-validcheckpoint或checkpoint-valid。