Help us improve your experience.

Let us know what you think.

Do you have time for a two-minute survey?

 
 

Graphique

Vue d’ensemble du graphique

Apstra utilise le modèle Graph pour représenter une source unique de vérité concernant l’infrastructure, les politiques, les contraintes, etc. Ce modèle de graphe est sujet à des changements constants et nous pouvons l’interroger pour diverses raisons. Il est représenté sous la forme d’un graphique. Toutes les informations relatives au réseau sont modélisées sous forme de nœuds et de relations entre eux.

Chaque objet d’un graphe a un ID unique. Les nœuds ont un type (une chaîne) et un ensemble de propriétés supplémentaires basées sur un type particulier. Par exemple, tous les commutateurs de notre système sont représentés par des nœuds de type system et peuvent avoir un rôle de propriété qui détermine le rôle qui leur est attribué dans le réseau (spine/leaf/server). Les ports de commutation physiques et logiques sont représentés par un nœud d’interface, qui possède également une propriété appelée if_type.

Les relations entre les différents nœuds sont représentées sous forme d’arêtes de graphe que nous appelons relations. Les relations sont dirigées, ce qui signifie que chaque relation a un nœud source et un nœud cible. Les relations ont également un type qui détermine les propriétés supplémentaires qu’une relation particulière peut avoir. Par exemple, les noeuds système ont des relations de type hosted_interfaces avec les noeuds d’interface.

Un ensemble de types de noeuds et de relations possibles est déterminé par un schéma de graphe. Le schéma définit les propriétés que les nœuds et les relations d’un type particulier peuvent avoir, ainsi que les types de ces propriétés (chaîne/entier/booléen/etc.) et les contraintes. Nous utilisons et maintenons une bibliothèque de schémas open source, Lollipop, qui permet une personnalisation flexible des types de valeur.

Pour en revenir au graphique représentant une source unique de vérité, l’un des aspects les plus difficiles a été de savoir comment raisonner à ce sujet en présence de changements, provenant à la fois de l’opérateur et du système géré. Pour ce faire, nous avons développé ce que nous appelons le mécanisme Live Query, qui comporte trois composants essentiels :

  • Spécification de la requête
  • Notification de modification
  • Traitement des notifications

Après avoir modélisé notre modèle de domaine sous forme de graphe, vous pouvez exécuter des recherches sur le graphe spécifié par les requêtes de graphe pour trouver des modèles particuliers (sous-graphes) dans un graphe. Le langage pour exprimer la requête est conceptuellement basé sur Gremlin, un langage de traversée de graphe open source. Nous avons également des analyseurs syntaxiques pour les requêtes exprimées dans un autre langage - Cypher, qui est un langage de requête utilisé par la base de données de graphes populaire neo4j.

Spécification de la requête

Vous commencez avec un node() et vous continuez à enchaîner les appels de méthode, en alternant entre les relations et les nœuds correspondants :

La requête ci-dessus traduite en anglais se lit comme suit : à partir d’un nœud du système de type, traverse toute relation sortante qui atteint le nœud de type interface, et à partir de ce nœud traverse toutes les relations sortantes qui mènent à un nœud de type 'link.

À tout moment, vous pouvez ajouter des contraintes supplémentaires :

Notez l’argument role='spine', il sélectionnera uniquement les noeuds système dont la propriété role est définie sur spine.

Il en va de même pour if_type propriété des noeuds d’interface.

Cette requête sélectionnera tous les noeuds système qui ont un rôle, soit spine, soit leaf, et les noeuds d’interface qui ont if_type autre chose que ip (ne signifie pas égal).

Vous pouvez également ajouter des conditions inter-objets qui peuvent être des fonctions Python arbitraires :

Nommez les objets pour qu’ils s’y réfèrent et utilisez ces noms comme noms d’argument pour votre fonction de contrainte (bien sûr, vous pouvez surcharger cela, mais c’est un comportement par défaut pratique). Ainsi, dans l’exemple ci-dessus, il prendra deux nœuds d’interface nommés if1 et if2, les passera dans une fonction où donnée et filtrera ces chemins, pour lesquels la fonction renvoie False. Ne vous souciez pas de l’endroit où vous placez votre contrainte : elle sera appliquée lors de la recherche dès que tous les objets référencés par la contrainte seront disponibles.

Maintenant que vous disposez d’un seul chemin, vous pouvez l’utiliser pour effectuer des recherches. Cependant, il peut arriver que vous souhaitiez avoir une requête plus complexe qu’un seul chemin d’accès. Pour prendre en charge cela, query DSL vous permet de définir plusieurs chemins dans la même requête, séparés par des virgules :

Cette match() fonction crée un regroupement de chemins. Tous les objets qui partagent le même nom dans des chemins différents feront en fait référence au même objet. match() Permet également d’ajouter plus de contraintes sur les objets avec where(). Vous pouvez effectuer une recherche distincte sur des objets particuliers et cela garantira que chaque combinaison de valeurs n’est vue qu’une seule fois dans les résultats :

Cela correspond à une chaîne de noeuds a -> b -> c. Si deux noeuds a et c sont connectés via plus d’un noeud de type b, le résultat ne contiendra toujours qu’une seule paire (a, c).

Il existe un autre modèle pratique à utiliser lors de l’écriture de requêtes : vous séparez votre structure de vos critères :

Le moteur de requête optimise cette requête en :

Pas de produit cartésien, pas d’étapes inutiles.

Notification de modification

Ok, vous avez maintenant une requête de graphe définie. À quoi ressemble le résultat d’une notification ? Chaque résultat sera un dictionnaire mappant un nom que vous avez défini pour un objet de requête à un objet trouvé. Par exemple, pour la requête suivante

Les résultats ressembleront à {'a': <node type='a'>, 'c': <node type='c'>}. Notez que seuls les objets nommés sont présents (il n’y en a pas <node type='b'> dans les résultats, bien que ce nœud soit présent dans la requête car il n’a pas de nom).

Vous enregistrez une requête à surveiller et un rappel à exécuter si quelque chose doit changer. Par la suite, si quelqu’un modifie le graphique surveillé, il détectera que les nouvelles mises à jour du graphique ont entraîné l’apparition de nouveaux résultats de requête, ou la disparition ou la mise à jour des anciens résultats. La réponse exécute le rappel associé à la requête. Le callback reçoit l’ensemble du chemin de la requête en réponse, et une action spécifique (ajoutée/mise à jour/supprimée) à exécuter.

Traitement des notifications

Lorsque le résultat est transmis à la fonction de traitement (rappel), à partir de là, vous pouvez spécifier la logique de raisonnement. Il peut s’agir de n’importe quoi, de la génération de journaux, d’erreurs, du rendu des configurations ou de l’exécution de validations sémantiques. Vous pouvez également modifier le graphique lui-même, à l’aide d’API de graphe et d’autres éléments de logique peuvent réagir aux modifications que vous avez apportées. De cette façon, vous pouvez appliquer le graphique en tant que source unique de vérité tout en servant également de canal de communication logique entre les éléments de la logique de votre application. L’API Graph se compose de trois parties :

Gestion des graphes - méthodes pour ajouter/mettre à jour/supprimer des éléments dans un graphique. add_node(), set_node(), del_node(), get_node()get_relationship()set_relationship()add_relationship()del_relationship()Interface commit() observable de requête get_nodes()get_relationships()add_observer(),remove_observer()

Les API de gestion des graphes s’expliquent d’elles-mêmes. add_node() crée un nouveau noeud, set_node() met à jour les propriétés du noeud existant et del_node() supprime un noeud.

commit() est utilisé pour signaler que toutes les mises à jour du graphique sont terminées et qu’elles peuvent être propagées à tous les écouteurs.

Les relations ont une API similaire.

L’interface observable vous permet d’ajouter/supprimer des observateurs - des objets qui implémentent une interface de notification et de rappel. Le rappel de notification se compose de trois méthodes :

  • on_node() - appelé lorsqu’un nœud/relation est ajouté, supprimé ou mis à jour
  • on_relationship() - appelé lorsqu’un nœud/relation est ajouté, supprimé ou mis à jour
  • on_graph() - appelé lorsque le graphe est validé

L’API Query est le cœur de notre API graphique et c’est ce qui alimente toute la recherche. Les deux get_nodes() et get_relationships() vous permettent de rechercher les objets correspondants dans un graphique. Les arguments de ces fonctions sont des contraintes sur les objets recherchés.

Par exemple get_nodes() , renvoie tous les noeuds d’un graphe, get_nodes(type='system') vous renvoie tous les noeuds système, get_nodes(type='system', role='spine') vous permet de contraindre les noeuds retournés à ceux qui ont des valeurs de propriété particulières. Les valeurs de chaque argument peuvent être soit une valeur simple, soit un objet de correspondance de propriété spécial. S’il s’agit d’une valeur simple, la propriété de l’objet résultat correspondant doit être égale à la valeur brute donnée. Les appariements de propriétés vous permettent d’exprimer un critère plus complexe, par exemple pas égal, inférieur à, l’une des valeurs données et ainsi de suite :

Note:

L’exemple ci-dessous permet d’utiliser directement Graph python. À des fins de démonstration, vous pouvez remplacer graph.get_nodes par un nœud dans l’explorateur de graphes. Cet exemple spécifique ne fonctionnera pas sur l’interface graphique d’Apstra.

Dans votre schéma de graphe, vous pouvez définir des index personnalisés pour des types de nœuds/relations particuliers et les méthodes get_nodes() , et get_relationships() choisir le meilleur index pour chaque combinaison particulière de contraintes passées afin de minimiser le temps de recherche.

Les résultats de get_nodes()/get_relationships() sont des objets itérateurs spéciaux. Vous pouvez itérer dessus et ils produiront tous les objets graphiques trouvés. Vous pouvez également utiliser les API fournies par ces itérateurs pour parcourir ces jeux de résultats. Par exemple, get_nodes() vous renvoie un objet NodeIterator qui a des méthodes out() et in_(). Vous pouvez les utiliser pour obtenir un itérateur sur toutes les relations sortantes ou entrantes de chaque nœud du jeu de résultats d’origine. Ensuite, vous pouvez les utiliser pour obtenir des nœuds à l’autre extrémité de ces relations et continuer à partir d’eux. Vous pouvez également passer des contraintes de propriété à ces méthodes de la même manière que vous pouvez le faire pour get_nodes() et get_relationships().

Le code de l’exemple ci-dessus recherche tous les nœuds avec un système de types et un rôle spine, puis trouve toutes leurs interfaces de bouclage.

Assembler le tout

La requête ci-dessous est un exemple de règle interne qu’Apstra peut utiliser pour dériver des attentes de télémétrie, par exemple, l’état d’une liaison et d’une interface. Le @rule insère un rappel vers process_spine_leaf_link, auquel cas nous écrivons aux attentes de télémétrie.

Fonctions de commodité

Pour éviter de créer des clauses complexes where() lors de la création d’une requête graphique, utilisez les fonctions pratiques, disponibles à partir de l’interface graphique d’Apstra.

  1. À partir du plan, accédez à la vue planifiée ou à la vue active , puis cliquez sur le bouton Explorateur d’API GraphQL (en haut à droite >_). L’explorateur de graphes s’ouvre dans un nouvel onglet.

  2. Tapez une requête graphique sur la gauche. Voir la description des fonctions ci-dessous.

  3. Dans la liste déroulante Action , sélectionnez qe.

  4. Cliquez sur le bouton Exécuter la requête (qui ressemble à un bouton de lecture) pour afficher les résultats.

Fonctions

Le moteur de requête décrit un certain nombre de fonctions utiles :

match(*path_queries)

Cette fonction renvoie un QueryBuilder objet contenant chaque résultat d’une requête correspondante. Il s’agit généralement d’un raccourci utile pour regrouper plusieurs requêtes de correspondance.

Ces deux requêtes ne forment pas un « chemin » ensemble (pas de relation intentionnelle). Remarquez la virgule pour séparer les arguments. Cette requête renvoie tous les équipements de branche et les équipements de cur de réseau ensemble.

node(self, type=None, name=None, id=Aucun, **propriétés)

  • Paramètres
    • type (str ou None) - Type de noeud à rechercher
    • name (str ou None) - Définit le nom du matcher de propriété dans les résultats
    • id (str ou None) - Correspond à un noeud spécifique par noeud ID dans le graphe
    • properties (dict ou None) : tous les arguments de mots-clés supplémentaires ou les fonctions de commodité supplémentaires de la correspondance de propriétés à utiliser
  • Returns - Objet générateur de requêtes pour le chaînage de requêtes
  • Type de retour - QueryBuilder

Bien qu’il s’agisse d’une fonction, il s’agit d’un alias pour les nœuds PathQueryBuilder (voir ci-dessous).

itérate()

  • Retours - générateur
  • Type de retour : générateur

Itération vous donne une fonction génératrice que vous pouvez utiliser pour itérer sur des requêtes de chemin individuelles comme s’il s’agissait d’une liste. Par exemple:

Nœuds PathQueryBuilder

node(self, type=None, name=None, id=Aucun, **propriétés)

Cette fonction décrit un nœud de graphe spécifique, mais est également un raccourci pour commencer une requête de chemin à partir d’un nœud spécifique. Le résultat d’un `node() appel renvoie un objet de requête de chemin. Lors de l’interrogation d’un chemin, vous voulez généralement spécifier un 'type' de nœud : par exemple node('system') , renverrait un nœud système.

  • Paramètres
    • type (str ou None) - Type de noeud à rechercher
    • name (str ou None) - Définit le nom du matcher de propriété dans les résultats
    • id (str ou None) - Correspond à un noeud spécifique par noeud ID dans le graphe
    • properties (dict ou None) : tous les arguments de mots-clés supplémentaires ou les fonctions de commodité supplémentaires de la correspondance de propriétés à utiliser
  • Returns - Objet générateur de requêtes pour le chaînage de requêtes
  • Type de retour - QueryBuilder

Si vous souhaitez utiliser le nœud dans les résultats de votre requête, vous devez le nommer --.node('system', name='device') De plus, si vous souhaitez faire correspondre des propriétés kwarg spécifiques, vous pouvez spécifier directement les exigences de correspondance -

node('system', name='device', role='leaf')

out(type=Aucun, id=Aucun, name=Aucun, **propriétés)

Traverse une relation dans la direction « sortante » selon un schéma de graphe. Les paramètres acceptables sont le type de relation (par exemple, les interfaces), le nom spécifique d’une relation, l’id d’une relation ou d’autres correspondances de propriétés qui doivent correspondre exactement données en tant qu’arguments de mot-clé.

  • Paramètres
    • type (str ou None) - Type de relation de noeud à rechercher
    • id (str ou None) : correspond à une relation spécifique par l’ID de relation dans le graphe
    • name (str ou None) - Correspond à une relation spécifique par relation nommée

Par exemple:

in_(type=Aucun, id=Aucun, nom=Aucun, **propriétés)

Traverse une relation dans le sens « entrant ». Définit le noeud actuel sur le noeud source de la relation. Les paramètres acceptables sont le type de relation (par exemple, les interfaces), le nom spécifique d’une relation, l’id d’une relation ou d’autres correspondances de propriétés qui doivent correspondre exactement données en tant qu’arguments de mot-clé.

  • Paramètres
    • type (str ou None) - Type de relation de noeud à rechercher
    • id (str ou None) : correspond à une relation spécifique par l’ID de relation dans le graphe
    • name (str ou None) - Correspond à une relation spécifique par relation nommée
    • properties (dict ou None) - Correspond aux relations par d’autres kwargs ou fonctions

où(prédicat, names=Aucun)

Permet de spécifier une fonction de rappel par rapport aux résultats du graphique en tant que filtre ou contrainte. Le prédicat est un rappel (généralement une fonction lambda) exécuté sur l’ensemble du résultat de la requête. where() peut être utilisé directement sur le résultat d’une requête de chemin d’accès.

  • Paramètres
    • predicat (callback) - Fonction de rappel à exécuter sur tous les noeuds du graphe
    • names (str ou None) - Si des noms sont donnés, ils sont passés à la fonction de rappel pour la correspondance

enure_different(*noms)

Permet à un utilisateur de s’assurer que deux noeuds nommés différents dans le graphe ne sont pas identiques. Cela est utile pour les relations qui peuvent être bidirectionnelles et qui peuvent correspondre sur leurs propres nœuds source. Considérons la requête suivante :

  • Paramètres
    • names (tuple ou list) : liste de noms pour s’assurer qu’ils renvoient des nœuds ou des relations différents à partir du graphe

La dernière ligne pourrait être fonctionnellement équivalente à la where() fonction avec une fonction de rappel lambda

Comparateurs de propriétés

Les correspondances de propriétés peuvent être exécutées directement sur des objets de requête de graphe - généralement utilisés dans une node() fonction. Les correspondances de propriétés permettent d’utiliser quelques fonctions.

eq(valeur)

Garantit que la valeur de propriété du noeud correspond exactement aux résultats de la eq(value) fonction.

  • Paramètres
    • value - Propriété à faire correspondre pour l’égalité

Ce qui est similaire à la simple définition d’une valeur en tant que kwarg sur un objet noeud :

Retourne:

ne(valeur)

Différent de égal. Garantit que la valeur de propriété du nœud ne correspond PAS aux résultats de ne(value) la fonction

  • Paramètres
    • value - Valeur à assurer pour la condition d’inégalité

Comme:

gt(valeur)

Plus grand que. Garantit que la propriété du noeud est supérieure aux résultats de gt(value) la fonction.

  • Paramètres
    • value - Assurez-vous que la fonction de propriété est supérieure à cette valeur

ge(valeur)

supérieur ou égal à. Garantit que la propriété du noeud est supérieure ou égale aux résultats de ge().

  • Paramètres : value - Assurez-vous que la fonction de propriété est supérieure ou égale à cette valeur

lt(valeur)

Inférieur à. Garantit que la propriété du noeud est inférieure aux résultats de lt(value).

  • Paramètres
    • value - Assurez-vous que la fonction de propriété est inférieure à cette valeur

Comme:

le(valeur)

Inférieur ou égal à. Garantit que la propriété est inférieure ou égale aux résultats de le(value) la fonction.

  • Paramètres
    • value - S’assure que la valeur donnée est inférieure ou égale à la fonction de propriété

Comme:

is_in(valeur)

Se trouve dans (liste). Vérifiez si la propriété se trouve dans une liste donnée ou un ensemble contenant des éléments is_in(value).

  • Paramètres
    • value (list) - S’assurer que la propriété donnée est dans cette liste

Comme:

not_in(valeur)

N’est pas dans (liste). Vérifiez si la propriété n’est PAS dans une liste donnée ou un ensemble contenant des éléments not_in(value).

  • Paramètres
    • value (list) - Valeur de la liste pour s’assurer que le correspondant de propriétés n’est pas dans

Comme:

is_none()

Une requête qui attend is_none s’attend à ce que cet attribut particulier soit spécifiquement None.

Comme:

not_none()

Un matcher qui s’attend à ce que cet attribut ait une valeur.

Comme:

Banque de données Apstra Graph

La banque de données graphiques Apstra est une base de données graphiques en mémoire. La taille du fichier journal est vérifiée périodiquement et lorsqu’une modification du blueprint est validée. Si la banque de données de graphe atteint 100 Mo ou plus, un nouveau fichier de point de contrôle de banque de données de graphe est généré. La base de données elle-même ne supprime pas les journaux de persistance de la banque de données de graphe ni les fichiers de points de contrôle. Apstra fournit des outils de nettoyage pour la banque de données graphique principale.

Les groupes de fichiers de persistance de banque de données de graphe valides contiennent quatre fichiers : log, log-valid, checkpointet checkpoint-valid. Les fichiers valides sont les indicateurs efficaces pour les fichiers journaux et les fichiers de points de contrôle. Le nom de chaque fichier de persistance se compose de trois parties : basename, id et extension.

  • basename : dérivé du nom de la partition de la banque de données du graphe principal.
  • id - un horodatage UNIX obtenu à partir de gettimeofday. Les secondes et les microsecondes dans l’horodatage sont séparées par un « -« . Un groupe de fichiers de persistance peut être identifié par id. L’horodatage peut également aider à déterminer la séquence temporelle générée des groupes de fichiers de persistance.
  • extension - log, log-valid, checkpoint, ou checkpoint-valid.