图
图表概述
Apstra 使用 Graph 模型来表示有关基础架构、策略、约束等的单一事实来源。这个图模型会不断变化,我们可以出于各种原因查询它。它表示为图形。有关网络的所有信息都建模为节点以及它们之间的关系。
图形中的每个对象都有一个唯一的 ID。节点具有一个类型(字符串)和一组基于特定类型的附加属性。例如,我们系统中的所有交换机都由 system 类型的节点表示,并且可以具有属性角色,该角色确定在网络中为其分配的角色(主干/叶/服务器)。物理和逻辑交换机端口由接口节点表示,该节点还具有一个名为 if_type 的属性。
不同节点之间的关系表示为图形边,我们称之为关系。关系是定向的,这意味着每个关系都有一个源节点和一个目标节点。关系还具有一种类型,该类型确定特定关系可以具有哪些附加属性。例如,系统节点与接口节点的关系为 hosted_interfaces。
一组可能的节点和关系类型由图形架构确定。架构定义特定类型的节点和关系可以具有哪些属性,以及这些属性的类型(字符串/整数/布尔值/等)和约束。我们使用并维护一个开源模式库 Lollipop,它允许灵活自定义值类型。
回到代表单一事实来源的图表,最具挑战性的方面之一是如何在发生来自运营商和托管系统的变化时对其进行推理。为了支持这一点,我们开发了所谓的实时查询机制,它有三个基本组件:
- 查询规范
- 变更通知
- 通知处理
将我们的域模型建模为图形后,您可以对图形查询指定的图形运行搜索,以查找图形中的特定模式(子图)。表达查询的语言在概念上基于 Gremlin,这是一种开源图形遍历语言。我们还有用另一种语言表达的查询解析器 - Cypher,这是流行的图形数据库neo4j使用的一种查询语言。
查询规范
你从 node() 开始,然后继续链接方法调用,在匹配关系和节点之间交替:
node('system', name='system').out().node('interface', name='interface').out().node('link', name='link')
上面翻译成英文的查询内容如下:从 system 类型的节点开始,遍历到达接口类型节点的任何传出关系,并从该节点遍历导致类型为 'link 的节点的所有传出关系。
您可以随时添加额外的约束:
node('system', role='spine', name='system').out().node('interface', if_type='ip', name='interface')
请注意 role='spine' 参数,它将仅选择角色属性设置为主干的系统节点。
与接口节点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)
命名对象以引用它们,并将这些名称用作约束函数的参数名称(当然,您可以覆盖它,但它是一种方便的默认行为)。因此,在上面的示例中,它将采用名为 if1 和 if2 的两个接口节点,将它们传递到给定的 where 函数中并过滤掉这些路径,该函数返回 False。不要担心你放置约束的位置:一旦约束引用的所有对象都可用,它就会在搜索期间应用。
现在,您有一个路径,您可以使用它进行搜索。但是,有时您可能希望查询比单个路径更复杂。为了支持这一点,查询 DSL 允许你在同一查询中定义多个路径,用逗号分隔:
match( node('a').out().node('b', name='b').out().node('c'), node(name='b').out().node('d'), )
此 match()
函数创建一组路径。在不同路径中共享相同名称的所有对象实际上都将引用同一个对象。此外, match()
还允许对使用 . where()
您可以对特定对象进行不同的搜索,这将确保每个值组合在结果中仅显示一次:
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()
、 del_node()
、 get_node()
add_relationship()
、 set_relationship()
del_relationship()
、 、 查询 commit()
get_relationship()
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()
返回一个具有方法out(
的 in_()
NodeIterator 对象和 。您可以使用它们来获取来自原始结果集中每个节点的所有传出或传入关系的迭代器。然后,您可以使用它们来获取这些关系另一端的节点并从它们继续。还可以将属性约束传递给这些方法,方法与 和 相同的get_nodes()
get_relationships()
方式。
graph.get_nodes('system', role='spine') \ .out('interface').node('interface', if_type='loopback')
上面示例中的代码查找类型为 system 和 role spine 的所有节点,然后查找其所有环路接口。
将一切整合在一起
下面的查询是一个内部规则示例,Apstra 可以使用该规则来派生遥测预期,例如链路和接口状态。@rule将插入对process_spine_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)
- 参数
- 类型 (str 或 None):要搜索的节点类型
- name (str 或 None)- 设置结果中属性匹配器的名称
- id (str 或 None)- 按图中的节点 ID 匹配特定节点
- 属性 (dict 或 None) - 要使用的任何其他关键字参数或其他属性匹配器便利函数
- 返回 - 用于链接查询的查询生成器对象
- 返回类型 - 查询生成器
虽然两者都是一个函数,但这是 PathQueryBuilder 节点的别名 - 见下文。
迭代()
- 退货 - 生成器
- 返回类型:发电机
迭代为您提供了一个生成器函数,您可以使用该函数对单个路径查询进行迭代,就像迭代列表一样。例如:
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()
返回路径查询对象。查询路径时,通常需要指定节点“类型”:例如 node('system')
将返回系统节点。
- 参数
- 类型 (str 或 None):要搜索的节点类型
- name (str 或 None)- 设置结果中属性匹配器的名称
- id (str 或 None)- 按图中的节点 ID 匹配特定节点
- 属性 (dict 或 None) - 要使用的任何其他关键字参数或其他属性匹配器便利函数
- 返回 - 用于链接查询的查询生成器对象
- 返回类型 - 查询生成器
如果要在查询结果中使用该节点,则需要将其命名为 --node('system', name='device')
。此外,如果要匹配特定的 kwarg 属性,可以直接指定匹配要求 -
node('system', name='device', role='leaf')
node('system', name='device', role='leaf')
out(类型=无,id=无,名称=无,**属性)
根据图形架构在“出”方向上遍历关系。可接受的参数包括关系的类型(例如接口)、关系的特定名称、关系的 ID 或其他属性匹配项,这些匹配项必须与作为关键字参数完全匹配。
- 参数
- 类型 (str 或 None):要搜索的节点关系的类型
- id (str 或 None) - 按关系 ID 匹配图形中的特定关系
- 名称 (str 或 None) - 按命名关系匹配特定关系
例如:
node('system', name='system') \ .out('hosted_interfaces')
in_(类型=无,id=无,名称=无,**属性)
在“内”方向上遍历关系。将当前节点设置为关系源节点。可接受的参数包括关系的类型(例如接口)、关系的特定名称、关系的 ID 或其他属性匹配项,这些匹配项必须与作为关键字参数完全匹配。
- 参数
- 类型 (str 或 None):要搜索的节点关系的类型
- id (str 或 None) - 按关系 ID 匹配图形中的特定关系
- 名称 (str 或 None) - 按命名关系匹配特定关系
- 属性 (字典或无) - 匹配任何其他 kwarg 或函数的关系
node('interface', name='interface') \ .in_('hosted_interfaces')
其中(谓词,名称=无)
允许您针对图形结果指定回调函数作为筛选器或约束。谓词是针对整个查询结果运行的回调(通常是 lambda 函数)。 where()
可以直接用于 A 路径查询结果。
- 参数
- 谓词(回调) - 针对图形中的所有节点运行的回调函数
- 名称(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(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()
。
- 参数:值 - 确保属性函数大于或等于此值
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)
的给定列表或集中。
- 参数
- 值(列表) - 确保给定属性在此列表中
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)
的集中。
- 参数
- 值(列表) - 用于确保属性匹配器不在中的列表值
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
。有效文件是日志和检查点文件的有效指示器。每个持久性文件的名称包含三个部分:基本名称、ID 和扩展名。
# regex for sysdb persistence files. # e.g. # _Main-0000000059ba612e-00017938-checkpoint-valid # \--/ \-----------------------/ \--------------/ # basename id extension
- 基本名称 - 派生自主图形数据存储分区名称。
- id - 从 gettimeofday 获得的 Unix 时间戳。时间戳中的秒和微秒用“-”分隔。持久性文件组可以通过 id 标识。时间戳还有助于确定生成的持久性文件组的时间序列。
- 扩展名 -
log
、log-valid
、checkpoint
或checkpoint-valid
。