Help us improve your experience.

Let us know what you think.

Do you have time for a two-minute survey?

 
 

Graphique

Présentation graphique

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

Chaque objet d’un graphique possède 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 système et peuvent avoir un rôle de propriété qui détermine le rôle qui leur est assigné dans le réseau (spine/leaf/serveur). 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 différents nœuds sont représentées sous la forme de périphéries graphiques que nous appelons des 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 peut avoir. Par exemple, les nœuds système ont des relations de type hosted_interfaces avec les nœuds d’interface.

Un ensemble de types de nœuds et de relations possibles est déterminé par un schéma graphique. Le schéma définit les propriétés que les nœuds et les relations de type particulier peuvent avoir, ainsi que les types de ces propriétés (chaîne/entier/booléenne/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 revenir au graphique représentant une source unique de vérité, l’un des aspects les plus difficiles était de savoir comment la raisonner en cas de changement, provenant à la fois de l’opérateur et du système géré. Pour prendre en charge cela, nous avons développé ce que nous appelons le mécanisme Live Query qui comprend trois composants essentiels :

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

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

Spécifications de requête

Vous commencez par un nœud(), puis continuez à faire des appels de méthode de chaînage, en alternance entre les relations de correspondance et les nœuds :

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

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

Notez l’argument role='spine', il ne sélectionnera que les nœuds système dont la propriété de rôle est définie sur spine.

Idem pour if_type propriété pour les nœuds d’interface.

Cette requête sélectionnera tous les nœuds système qui ont un rôle de cœur de réseau ou de branche et d’interface qui ont if_type tout sauf ip (ne signifie pas égal).

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

Nommez les objets pour y faire référence et utilisez ces noms comme noms d’argument pour votre fonction de contrainte (bien sûr, vous pouvez remplacer cela, mais cela rend un comportement pratique par défaut). Ainsi, dans l’exemple ci-dessus, il faudra deux nœuds d’interface nommés if1 et if2, les passer dans la fonction et filtrer ces chemins, pour laquelle la fonction renvoie False. Ne vous inquiétez 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 contrainte seront disponibles.

Maintenant, vous disposez d’un chemin unique, vous pouvez l’utiliser pour effectuer des recherches. Cependant, vous pouvez parfois vouloir avoir une requête plus complexe qu’un seul chemin. Pour cela, le DSL de requête 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 vous assurez que chaque combinaison de valeurs n’est visible qu’une seule fois dans les résultats :

Cela correspond à une chaîne de nœuds -> b-> c. Si deux nœuds a et c sont connectés via plusieurs nœuds de type b, le résultat ne contiendra toujours qu’une seule paire (a, c).

Il existe un autre modèle pratique à utiliser pour écrire des requêtes : vous séparez votre structure de vos critères :

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

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

Notification de modification

Ok, vous avez maintenant une requête graphique définie. À quoi ressemble un résultat de 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 a pas <node type='b'> de 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 change. Plus tard, si quelqu’un modifie le graphique surveillé, il détecte que les nouvelles mises à jour graphiques ont entraîné l’apparition de nouveaux résultats de requête, ou que les anciens résultats ont disparu ou mis à jour. La réponse exécute le rappel associé à la requête. Le rappel reçoit l’ensemble du chemin de la requête en tant que 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), vous pouvez spécifier une logique de raisonnement. Cela peut être n’importe quoi, de la génération de journaux, d’erreurs, au rendu des configurations ou à l’exécution de validations sémantiques. Vous pouvez également modifier le graphique lui-même, en utilisant des API graphiques et d’autres éléments logiques peuvent réagir aux modifications que vous avez apportées. De cette façon, vous pouvez appliquer le graphique comme une source unique de vérité, tandis qu’il sert également de canal de communication logique entre des éléments de votre logique d’application. L’API Graph se compose de trois parties :

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

Les API de gestion graphique sont explicites. add_node() crée un nouveau nœud, set_node() met à jour les propriétés du nœud existant et del_node() supprime un nœud.

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

Les relations ont des API similaires.

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

  • on_node() - Appelé en cas d’ajout, de suppression ou de mise à jour d’un nœud/relation
  • on_relationship() - Appelé en cas d’ajout, de suppression ou de mise à jour d’un nœud/relation
  • on_graph() - appelé lorsque le graphique est engagé

L’API de requête est le cœur de notre API graphique et est ce qui alimente toutes les recherches. Les deux get_nodes() et get_relationships() vous permettent de rechercher des objets correspondants dans un graphique. Les arguments liés à ces fonctions sont des contraintes sur les objets recherchés.

Par exemple, get_nodes() vous renvoie tous les nœuds d’un graphique, get_nodes(type='system') vous renvoie tous les nœuds système, get_nodes(type='system', role='spine') vous permet de restreindre les nœuds retournés à ceux qui ont des valeurs de propriété particulières. Les valeurs de chaque argument peuvent être soit une valeur claire, soit un objet de correspondance de propriété spéciale. Si la valeur est une valeur neutre, l’objet résultat correspondant doit avoir sa propriété égale à la valeur neutre donnée. Les correspondances de propriétés vous permettent d’exprimer des critères plus complexes, par exemple pas égaux, inférieurs à un des valeurs données, etc. :

Note:

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

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

Les résultats de get_nodes()/get_relationships() sont des objets itérateurs spéciaux. Vous pouvez les itérer et ils donneront tous les objets graphiques trouvés. Vous pouvez également utiliser des API que ces itérateurs fournissent pour parcourir ces ensembles de résultats. Par exemple, get_nodes() vous renvoie un objet NodeIterator avec 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 bout de ces relations et continuer à partir d’eux. Vous pouvez également transmettre les 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 trouve tous les nœuds avec type système et cœur de rôle, puis trouve toutes leurs interfaces de bouclage.

Tout est réuni

La requête ci-dessous est un exemple de règle interne qu’Apstra peut utiliser pour calculer les attentes de télémétrie , par exemple, l’état de la liaison et de l’interface. Le @rule insère un rappel à 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 des fonctions de commodité, disponibles à partir de l’interface graphique Apstra.

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

  2. Saisissez une requête graphique sur la gauche. Voir les descriptions 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 voir 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 sont pas un « chemin » ensemble (aucune relation prévue). Notez la virgule pour séparer les arguments. Cette requête renvoie tous les équipements de branche et les équipements de cœur de réseau ensemble.

nœud(self, type=None, name=None, id=None, **propriétés)

  • Paramètres
    • type (str ou Aucun) : type de nœud à rechercher
    • name (str ou None) : définit le nom du partenaire de propriété dans les résultats
    • id (str ou None) : correspond à un nœud spécifique par ID de nœud dans le graphique
    • properties (dict ou None) - Arguments de mots-clés supplémentaires ou fonctions de commodité de correspondance de propriétés supplémentaires à utiliser
  • Retours - Objet générateur de requêtes pour les requêtes de chaînage
  • Type de retour - QueryBuilder

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

itérer()

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

Iterate vous fournit une fonction de générateur 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

nœud(self, type=None, name=None, id=None, **propriétés)

Cette fonction décrit un nœud graphique 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. Lorsque vous interrogez un chemin, vous souhaitez généralement spécifier un « type » de nœud : par exemple node('system') , renvoyer un nœud système.

  • Paramètres
    • type (str ou Aucun) : type de nœud à rechercher
    • name (str ou None) : définit le nom du partenaire de propriété dans les résultats
    • id (str ou None) : correspond à un nœud spécifique par ID de nœud dans le graphique
    • properties (dict ou None) - Arguments de mots-clés supplémentaires ou fonctions de commodité de correspondance de propriétés supplémentaires à utiliser
  • Retours - Objet générateur de requêtes pour les requêtes de chaînage
  • 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'). En outre, si vous souhaitez 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=None, id=None, name=None, **propriétés)

Traverse une relation dans le sens « out » selon un schéma 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é qui doivent correspondre exactement comme arguments de mot-clé.

  • Paramètres
    • type (str ou None) : type de relation de nœud à rechercher
    • id (str ou None) : correspond à une relation spécifique par ID de relation dans le graphique
    • name (str ou None) : correspond à une relation spécifique par relation nommée

Par exemple :

in_(type=None, id=None, name=None, **propriétés)

Traverse une relation dans le sens « dans ». Définit le nœud actuel vers le nœud source. 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é qui doivent correspondre exactement comme arguments de mot-clé.

  • Paramètres
    • type (str ou None) : type de relation de nœud à rechercher
    • id (str ou None) : correspond à une relation spécifique par ID de relation dans le graphique
    • name (str ou None) : correspond à une relation spécifique par relation nommée
    • propriétés (dict ou None) : correspondance entre les relations de kwargs ou de fonctions supplémentaires

où(prédicat, noms=Aucun)

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

  • Paramètres
    • prédicat (rappel) - Fonction de rappel pour s’exécuter sur tous les nœuds du graphique
    • names (str ou None) - Si des noms sont donnés, ils sont transférés à la fonction de rappel pour la correspondance

enure_different(*noms)

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

  • Paramètres
    • noms (tuple ou liste) : liste de noms permettant de renvoyer différents nœuds ou relations à partir du graphique

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

Correspondances de propriétés

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

eq(valeur)

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

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

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

Retourne:

ne(valeur)

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

  • Paramètres
    • valeur - Valeur à garantir pour les conditions d’inégalité

Similaire à :

gt(valeur)

Plus que. Garantit que la propriété du nœud 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érieure ou égale à. Garantit que la propriété du nœud est supérieure ou égale aux résultats de ge().

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

lt(valeur)

Moins que. Garantit que la propriété du nœud 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

Similaire à :

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 : garantit que la valeur donnée est inférieure ou égale à la fonction de propriété

Similaire à :

is_in(valeur)

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

  • Paramètres
    • value (list) - Assurez-vous que la propriété donnée figure dans cette liste

Similaire à :

not_in(valeur)

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

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

Similaire à :

is_none()

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

Similaire à :

not_none()

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

Similaire à :

Magasin de données Apstra Graph

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

Les groupes de fichiers de persistance des magasins de données graphiques valides contiennent quatre fichiers : log, log-valid, checkpointet checkpoint-valid. Les fichiers valides sont les indicateurs efficaces des fichiers journaux et de points de contrôle. Le nom de chaque fichier de persistance est en trois parties : nom de base, id et extension.

  • basename - dérivé du nom de la partition de magasin de données 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 persistants peut être identifié par id. L’horodatage peut également aider à déterminer la séquence temporelle générée des groupes de fichiers persistants.
  • extension - log, log-valid, checkpointou checkpoint-valid.