グラフ
グラフの概要
Apstraは、グラフモデルを使用して、インフラストラクチャ、ポリシー、制約などに関する信頼できる単一の情報源を表します。このグラフモデルは常に変更される場合があり、さまざまな理由でクエリを実行できます。グラフとして表されます。ネットワークに関するすべての情報は、ノードとそれらの関係としてモデル化されます。
グラフ内のすべてのオブジェクトには一意の ID があります。ノードには、特定のタイプに基づいたタイプ(文字列)と一連の追加プロパティがあります。たとえば、システム内のすべてのスイッチはシステムタイプのノードで表され、割り当てられたネットワーク内のロール(スパイン/リーフ/サーバー)を決定するプロパティロールを持つことができます。物理および論理スイッチ ポートは、if_type と呼ばれるプロパティを持つインターフェイス ノードで表されます。
異なるノード間の関係は、関係と呼ばれるグラフエッジとして表されます。リレーションシップは向けられる。つまり、各関係にはソース ノードとターゲット ノードがあります。リレーションシップには、特定のリレーションシップに追加できるプロパティを決定するタイプもあります。例えば、システムノードには、インターフェイスノードに対するタイプhosted_interfacesの関係があります。
可能なノードと関係の型のセットは、グラフスキーマによって決定されます。スキーマは、特定の型のプロパティ ノードとリレーションシップを、それらのプロパティの型(文字列/整数/ブール/など)および制約と共に定義します。オープン ソースのスキーマ ライブラリ Lollipop を使用して維持し、値型の柔軟なカスタマイズを可能にします。
単一の真実の情報源を表すグラフに戻ると、最も困難な側面の1つは、オペレーターとマネージドシステムの両方から得られた変更の前に、それを推論する方法でした。これをサポートするために、3 つの重要なコンポーネントを持つ Live Query メカニズムと呼ばれるものを開発しました。
- クエリーの仕様
- 変更通知
- 通知処理
ドメインモデルをグラフとしてモデル化することで、グラフクエリで指定されたグラフ上で検索を実行し、グラフ内の特定のパターン(サブグラフ)を見つけることができます。クエリを表現する言語は、オープン ソース グラフのトラバーサル言語である Gremlin を概念的にベースにしています。また、他の言語で表現されたクエリ用のパーサを持っています - 暗号は、人気のあるグラフデータベース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='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という名前の2つのインターフェイスノードを受け取り、関数の所定の場所に渡し、関数がFalseを返すパスを除外します。制約をどこに配置する必要もありません。制約によって参照されるすべてのオブジェクトが使用可能になるとすぐに、この制約が検索中に適用されます。
これでパスが1つになり、それを使って検索を行うことができます。ただし、1 つのパスよりも複雑なクエリが必要な場合もあります。それをサポートするために、クエリ DSL では、同じクエリでコンマで区切って複数のパスを定義できます。
match( node('a').out().node('b', name='b').out().node('c'), node(name='b').out().node('d'), )
この関数は match()
、パスのグループ化を作成します。異なるパスで同じ名前を共有するすべてのオブジェクトが、実際に同じオブジェクトを参照しています。また、 match()
でオブジェクト where()
に対する制約を追加することもできます。特定のオブジェクトに対して個別の検索を行うと、値の各組み合わせが結果として 1 回だけ表示されるようになります。
match( node('a', name='a').out().node('b').out().node('c', name='c') ).distinct(['a', 'c'])
これは、-> b -> c ノードのチェーンと一致します。2 つのノード a と c が、タイプ b の 1 つ以上のノードを介して接続されている場合、結果には 1 つの (a, c) ペアのみが含まれます。
クエリを作成する際に使用する便利なパターンはもう 1 つあります。構造を基準から分離します。
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は、3つの要素で構成されています。
グラフ管理 - グラフ内の物を追加/更新/削除する方法。add_node()
、 、 del_node()
set_node()
、 get_node()
add_relationship()
、 set_relationship()
、 del_relationship()
、 get_relationship()
クエリ commit()
get_nodes()
get_relationships()
オブザーバブルインターフェイスadd_observer(),remove_observer()
グラフ管理 API は解説します。 add_node()
は、新しいノードを作成し、 set_node()
既存のノードのプロパティを更新して del_node()
、ノードを削除します。
commit()
は、グラフのすべての更新が完了し、すべてのリスナーに反映できることを通知するために使用されます。
関係は類似のAPIを持っています。
監視可能なインターフェイスを使用すると、コールバックインターフェイス通知を実装するオブザーバー - オブジェクトを追加/削除できます。通知のコールバックは、3 つのメソッドで構成されています。
on_node()
- ノード/関係が追加、削除、更新されたときに呼び出されるon_relationship()
- ノード/関係が追加、削除、更新されたときに呼び出されるon_graph()
- グラフがコミットされたときに呼び出される
クエリAPIはグラフAPIの中心であり、すべての検索に威力を発揮します。と の両方 get_nodes()
で get_relationships()
、グラフ内の対応するオブジェクトを検索できます。これらの関数の引数は、検索オブジェクトに対する制約です。
グラフ get_nodes()
内のすべてのノードを返し、 get_nodes(type='system')
すべてのシステムノードを返し、 get_nodes(type='system', role='spine')
返されたノードを特定のプロパティ値を持つノードに制約することができます。各引数の値は、プレーンな値または特別なプロパティのマッチするオブジェクトのいずれかです。値がプレーン値の場合、対応する結果オブジェクトのプロパティは、指定されたプレーン値と等しい必要があります。プロパティマッチャーを使用すると、より複雑な基準を表現できます。例えば、等しくない、与えられた値の1つ未満などです。
以下の例は、グラフPythonを直接使用した場合の例です。デモを行うため、グラフエクスプローラでgraph.get_nodesをノードに置き換えることができます。この特定の例は、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 を使用して、これらの結果セットに移動することもできます。例えば、メソッド)と in_()
を持つNodeIteratorオブジェクトが返されますout(
。 get_nodes()
これらの値を使用すると、元の結果セット内の各ノードからすべての発信または受信リレーションシップを反復子で取得できます。そして、それらを使用して、それらの関係の反対側のノードを取得し、それらから続行することができます。プロパティ制約は、 と の場合とget_relationships()
同じ方法でそれらのメソッドに渡すことも可能get_nodes()
です。
graph.get_nodes('system', role='spine') \ .out('interface').node('interface', if_type='loopback')
上記の例のコードは、システムとロールスパインタイプのすべてのノードを見つけ、すべてのループバックインターフェイスを見つけます。
すべてを統合
以下のクエリは、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 を選択します。
-
[ クエリの実行 ] ボタン (再生ボタンのように見えます) をクリックして、結果を表示します。
関数
クエリ エンジンには、次のような便利な機能が数多く搭載されています。
match(*path_queries)
この関数は、 QueryBuilder
一致したクエリの各結果を含むオブジェクトを返します。これは通常、複数の照会をグループ化する場合に便利なショートカットです。
これら 2 つのクエリは、共に「パス」ではありません(意図した関係ではありません)。引数を区切るためにコンマに注意してください。このクエリーでは、すべてのリーフデバイスとスパインデバイスが一緒に返されます。
match( node('system', name='leaf', role='leaf'), node('system', name='spine', role='spine'), )
node(self、type=None、name=None、id=None、**プロパティ)
- パラメーター
- タイプ (strまたは None)- 検索するノードのタイプ
- 名前 (str または None) - 結果のプロパティ マッチャーの名前を設定します。
- id (strまたはNone)- グラフ内のノードIDで特定のノードを照合します。
- プロパティ (dict または None) - 使用する追加のキーワード引数または追加のプロパティ マッチャー便利関数
- 戻り値 - クエリのチェーン設定用のクエリ ビルダー オブジェクト
- 戻り値の種類 - クエリ ビルダー
両方の関数の間、これは PathQueryBuilder ノードのエイリアスです。以下を参照してください。
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、**プロパティ)
この関数は、特定のグラフ ノードについて説明しますが、特定のノードからパス クエリを開始するためのショートカットでもあります。呼び出しの結果は `node()
、パス クエリ オブジェクトを返します。パスをクエリーする場合、通常はノード'type'を指定します。例えば node('system')
、システムノードを返します。
- パラメーター
- タイプ (strまたは None)- 検索するノードのタイプ
- 名前 (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(type=None,id=None, name=None, **プロパティ)
グラフスキーマに従って、「送信」方向のリレーションシップをトラバースします。受け入れ可能なパラメーターとは、リレーションシップのタイプ(インターフェイスなど)、リレーションシップの特定の名前、リレーションシップの id、またはキーワード引数として正確に一致する必要があるその他のプロパティの一致です。
- パラメーター
- タイプ (str または None) - 検索するノード関係のタイプ
- id (strまたはNone)- グラフ内の関係IDで特定の関係を照合します。
- 名前 (str または None) - 名前付きリレーションシップによって特定の関係を照合します。
例えば:
node('system', name='system') \ .out('hosted_interfaces')
in_(type=None、id=None、name=None、**プロパティ)
「in」方向の関係をトラバースします。現在のノードを関係ソース ノードに設定します。受け入れ可能なパラメーターとは、リレーションシップのタイプ(インターフェイスなど)、リレーションシップの特定の名前、リレーションシップの id、またはキーワード引数として正確に一致する必要があるその他のプロパティの一致です。
- パラメーター
- タイプ (str または None) - 検索するノード関係のタイプ
- id (strまたはNone)- グラフ内の関係IDで特定の関係を照合します。
- 名前 (str または None) - 名前付きリレーションシップによって特定の関係を照合します。
- プロパティ (dict または None) - それ以上の kwargs または関数による関係を照合
node('interface', name='interface') \ .in_('hosted_interfaces')
where(述部、names=None)
フィルターまたは制約としてグラフ結果に対してコールバック関数を指定できます。述部は、クエリ結果全体に対して実行されるコールバック (通常はラムダ関数) です。 where()
は、パス クエリ結果で直接使用できます。
- パラメーター
- predicate(コールバック)- グラフ内のすべてのノードに対して実行するコールバック関数
- 名前 (str または None) - 名前が与えられた場合、一致のコールバック関数に渡されます。
node('system', name='system') \ .where(lambda system: system.role in ('leaf', 'spine'))
enure_different(*名前)
グラフ内の 2 つの異なる名前付きノードが同じではないことをユーザーが確認できるようにします。これは、双方向で、独自のソースノードで一致する可能性のある関係に役立ちます。クエリーを検討してください。
- パラメーター
- 名前(タプルまたはリスト) - グラフから異なるノードや関係を確実に返すために使用する名前のリスト
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')
最後の行は、ラムダコールバック関数を 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(value)
Not-equals。ノードのプロパティ値が関数の結果と 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(value)
大きいまたは等しい。ノードのプロパティが の結果 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(リスト)- リスト値を使用して、プロパティマッチャーがに含まれていないか確認します。
node('system', name='system', role=not_in(['leaf', 'spine']))
次に似ています。
node('system', name='system').where(lambda system: system.role not in ['leaf', 'spine'])
is_none()
この特定の属性が具体的 None
is_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は、メイングラフのデータストアにクリーンアップツールを提供します。
有効なグラフデータストアの持続性ファイル グループには、4 つのファイルが含まれています。 log
log-valid
checkpoint
checkpoint-valid
有効なファイルは、ログ・ファイルとチェックポイント・ファイルの有効な標識です。各持続性ファイルの名前には、ベース名、ID、および拡張子の 3 つの部分があります。
# regex for sysdb persistence files. # e.g. # _Main-0000000059ba612e-00017938-checkpoint-valid # \--/ \-----------------------/ \--------------/ # basename id extension
- basename - メイングラフのデータストアパーティション名から派生します。
- id - gettimeofdayから取得したUNIXタイムスタンプ。タイムスタンプの秒とマイクロ秒は、「-」で区切ります。パーシステンス・ファイル・グループは ID で識別できます。タイムスタンプは、生成された永続ファイル グループの時間シーケンスの決定にも役立ちます。
- 拡張 -
log
、 、log-valid
、checkpoint
またはcheckpoint-valid
。