- Introduction
- Get Started
- play_arrow Apstra GUI
- play_arrow Blueprints and Dashboard
- play_arrow Analytics (Blueprints)
- Analytics Introduction
- play_arrow Dashboards
- play_arrow Anomalies
- play_arrow Widgets
- play_arrow Probes
- play_arrow Predefined Reports (Tech Preview)
- play_arrow Root Causes
- play_arrow Staged (Datacenter Blueprints)
- Blueprint-Wide Search
- play_arrow Physical
- play_arrow Build
- play_arrow Selection
- play_arrow Topology
- play_arrow Nodes
- Nodes (Datacenter)
- Unassign Device (Datacenter)
- Update Deploy Mode (Datacenter)
- Generic Systems vs. External Generic Systems
- Create Generic System
- Create External Generic System
- Create Access Switch
- Update Node Tag (Datacenter)
- Update Port Channel ID Range
- Update Hostname (Datacenter)
- Edit Generic System Name
- Edit Device Properties (Datacenter)
- View Node's Static Routes
- Delete Node
- play_arrow Links
- Links (Datacenter)
- Add Links to Leaf
- Add Links to Spine
- Add Links to Generic System
- Add Links to External Generic System
- Add Leaf Peer Links
- Add Link per Superspine (5-Stage)
- Form LAG
- Create Link in LAG
- Break LAG
- Update LAG Mode
- Update Link Tag (Datacenter)
- Update Link Speed
- Update Link Speed per Superspine (5-Stage)
- Mixed Link Speeds between Leaf and Spine
- Update Link Properties
- Delete Link (Datacenter)
- Export Cabling Map (Datacenter)
- Import Cabling Map (Datacenter)
- Edit Cabling Map (Datacenter)
- Fetch LLDP Data (Datacenter)
- play_arrow Interfaces
- play_arrow Racks
- play_arrow Pods
- play_arrow Planes
-
- play_arrow Virtual
- play_arrow Virtual Networks
- play_arrow Routing Zones
- Static Routes (Virtual)
- Protocol Sessions (Virtual)
- play_arrow Virtual Infrastructure
- play_arrow Statistics
-
- play_arrow Policies
- play_arrow Endpoints
- Security Policies
- Interface Policies
- Routing Policies
- Routing Zone (VRF) Constraints
- play_arrow Routing Zone Policy (4.2.0)
-
- play_arrow Data Center Interconnect (DCI)
- play_arrow Catalog
- play_arrow Logical Devices
- play_arrow Interface Maps
- play_arrow Property Sets
- play_arrow Configlets
- play_arrow AAA Servers
- play_arrow Tags
-
- play_arrow Tasks
- play_arrow Connectivity Templates
- Connectivity Templates Introduction
- play_arrow Primitives
- Primitive: Virtual Network (Single)
- Primitive: Virtual Network (Multiple)
- Primitive: IP Link
- Primitive: Static Route
- Primitive: Custom Static Route
- Primitive: BGP Peering (IP Endpoint)
- Primitive: BGP Peering (Generic System)
- Primitive: Dynamic BGP Peering
- Primitive: Routing Policy
- Primitive: Routing Zone Constraint
- User-defined
- Pre-defined
- Create Connectivity Template for Multiple VNs on Same Interface (Example)
- Create Connectivity Template for Layer 2 Connected External Router (Example)
- Update Connectivity Template Assignments
- Edit Connectivity Template
- Delete Connectivity Template
- play_arrow Fabric Settings (4.2.1)
- play_arrow Fabric Policy (4.2.1)
- play_arrow Severity Preferences (4.2.1)
-
- play_arrow Fabric Settings (4.2.0)
- play_arrow Fabric Policy (4.2.0)
- play_arrow Virtual Network Policy (4.2.0)
- play_arrow Anti-Affinity Policy (4.2.0)
- play_arrow Validation Policy (4.2.0)
-
- BGP Route Tagging
- play_arrow Staged (Freeform Blueprints)
- Freeform Introduction
- play_arrow Blueprints
- play_arrow Physical
- play_arrow Selection
- play_arrow Topology
- play_arrow Systems
- Systems Introduction (Freeform)
- Create Internal System (Freeform)
- Create External System (Freeform)
- Update Config Template Assignment (Freeform)
- Update System Name (Freeform)
- Update Hostname (Freeform)
- Update Device Profile Assignment (Freeform)
- Update System ID Assignment (Freeform)
- Update Deploy Mode (Freeform)
- Update System Tag Assignment (Freeform)
- Delete System (Freeform)
- Device Context (Freeform)
- play_arrow Links
-
- play_arrow Resource Management
- Resource Management Introduction (Freeform)
- play_arrow Blueprint Resources
- play_arrow Allocation Groups
- play_arrow Local Pools
- play_arrow Catalog
- play_arrow Config Templates
- play_arrow Device Profiles
- play_arrow Property Sets
- play_arrow Tags
-
- play_arrow Tasks
- play_arrow Uncommitted (Blueprints)
- play_arrow Active (Datacenter Blueprints)
- play_arrow Time Voyager (Blueprints)
- play_arrow Devices
- Device Configuration Lifecycle
- play_arrow Managed Devices
- play_arrow System Agents
- play_arrow Pristine Config
- play_arrow Telemetry
- play_arrow Apstra ZTP
- Apstra ZTP Introduction
- Create User Profile for Communicating with ZTP Server
- Download and Deploy Apstra ZTP Server VM
- Configure Static Management IP Address for Apstra ZTP Server
- Replace SSL Certificate for Apstra ZTP Server GUI
- Configure Credentials for Apstra ZTP Server GUI
- Create Vendor-specific Custom Configuration
- Configure Apstra Server Connection Details
- Configure DHCP Server for Apstra ZTP
- ztp.json Keys
- Configure ztp.json with Configurator
- Configure ztp.json with CLI
- Onboard Devices with Apstra ZTP
- Check ZTP Status of Devices and Services
- Reset Apstra ZTP GUI Admin Password
- play_arrow Device Profiles
- play_arrow Design
- play_arrow Logical Devices
- play_arrow Interface Maps
- play_arrow Rack Types
- play_arrow Templates
- play_arrow Config Templates
- play_arrow Configlets (Datacenter)
- play_arrow Property Sets (Datacenter)
- play_arrow TCP/UDP Ports
- play_arrow Tags
-
- play_arrow Resources
- play_arrow Analytics
- play_arrow Apstra Flow
- Apstra Flow Introduction
- System Requirements
- play_arrow Dashboards
- play_arrow Supported Flow Records
- play_arrow Flow Enrichment
- play_arrow Monitor Flow Data
- play_arrow Configuration Reference
- play_arrow API
- play_arrow Additional Documentation
- play_arrow Knowledge Base
-
- play_arrow External Systems (RBAC Providers)
- play_arrow Providers
- play_arrow Provider Role Mapping
-
- play_arrow Platform
- play_arrow User / Role Management
- play_arrow Security
- Syslog Configuration (Platform)
- Receivers (Platform)
- Global Statistics (Platform)
- Event Log (Audit Log)
- play_arrow Apstra VM Clusters
- play_arrow Developers
- play_arrow Technical Support
- Check Apstra Versions and Patent Numbers
-
- Favorites & User
- play_arrow Apstra Server Management
- Apstra Server Introduction
- Monitor Apstra Server via CLI
- Restart Apstra Server
- Reset Apstra Server VM Password
- Reinstall Apstra Server
- Apstra Database Overview
- Back up Apstra Database
- Restore Apstra Database
- Reset Apstra Database
- Migrate Apstra Database
- Replace SSL Certificate on Apstra Server with Signed One
- Replace SSL Certificate on Apstra Server with Self-Signed One
- Change Apstra Server Hostname
- Apstra CLI Utility
- play_arrow Guides
Graph
Graph Overview
Apstra uses the Graph model to represent a single source of truth regarding infrastructure, policies, constraints etc. This Graph model is subject to constant change and we can query it for various reasons. It is represented as a graph. All information about the network is modeled as nodes and relationships between them.
Every object in a graph has a unique ID. Nodes have a type (a string) and a set of additional properties based on a particular type. For example, all switches in our system are represented by nodes of type system and can have a property role which determines which role in the network it is assigned (spine/leaf/server). Physical and logical switch ports are represented by an interface node, which also has a property called if_type.
Relationships between different nodes are represented as graph edges which we call relationships. Relationships are directed, meaning each relationship has a source node and a target node. Relationships also have a type which determines which additional properties particular relationship can have. E.g. system nodes have relationships of type hosted_interfaces towards interface nodes.
A set of possible node and relationship types is determined by a graph schema. The schema defines which properties nodes and relationships of particular type can have along with types of those properties (string/integer/boolean/etc) and constraints. We use and maintain an open source schema library, Lollipop, that allows flexible customization of value types.
Going back to the graph representing a single source of truth, one of the most challenging aspects was how to reason about it in the presence of change, coming from both the operator and the managed system. In order to support this we developed what we call Live Query mechanism which has three essential components:
- Query Specification
- Change Notification
- Notification Processing
Having modeled our domain model as a graph, you can run searches on the graph specified by graph queries to find particular patterns (subgraphs) in a graph. The language to express the query is conceptually based on Gremlin, an open source graph traversal language. We also have parsers for queries expressed in another language - Cypher, which is a query language used by popular graph database neo4j.
Query Specification
You start with a node() and then keep chaining method calls, alternating between matching relationships and nodes:
node('system', name='system').out().node('interface', name='interface').out().node('link', name='link')
The query above translated in english reads something like: starting from a node of type system, traverse any outgoing relationship that reaches node of type interface, and from that node traverse all outgoing relationship that lead to node of type `link.
At any point you can add extra constraints:
node('system', role='spine', name='system').out().node('interface', if_type='ip', name='interface')
Notice role=`spine` argument, it will select only system nodes that have role property set to spine.
Same with if_type property for interface nodes.
node('system', role=is_in(['spine', 'leaf']), name='system') .out() .node('interface', if_type=ne('ip'), name='interface')
That query will select all system nodes that have role either spine or leaf and interface nodes that have if_type anything but ip (ne means not equal).
You can also add cross-object conditions which can be arbitrary Python functions:
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 objects to refer to them and use those names as argument names for your constraint function (of course you can override that but it makes a convenient default behavior). So, in example above it will take two interface nodes named if1 and if2, pass them into given where function and filter out those paths, for which function returns False. Don't worry about where you place your constraint: it will be applied during search as soon as all objects referenced by constraint are available.
Now, you have a single path, you can use it to do searches. However, sometimes you might want to have a query more complex than a single path. To support that, query DSL allows you to define multiple paths in the same query, separated by comma(s):
match( node('a').out().node('b', name='b').out().node('c'), node(name='b').out().node('d'), )
This match()
function creates a grouping of paths. All objects
that share the same name in different paths will actually be referring to the
same object. Also, match()
allows adding more constraints on
objects with where()
. You can do a distinct search on
particular objects and it will ensure that each combination of values is seen
only once in results:
match( node('a', name='a').out().node('b').out().node('c', name='c') ).distinct(['a', 'c'])
This matches a chain of a -> b -> c nodes. If two nodes a and c are connected through more than one node of type b, the result will still contain only one (a, c) pair.
There is another convenient pattern to use when writing queries: you separate your structure from your criteria:
match( node('a', name='a').out().node('b').out().node('c', name='c'), node('a', foo='bar'), node('c', bar=123), )
Query engine will optimize that query into:
match( node('a', name='a', foo='bar') .out().node('b') .out().node('c', name='c', bar=123) )
No cartesian product, no unnecessary steps.
Change Notification
Ok, now you have a graph query defined. What does a notification result look like? Each result will be a dictionary mapping a name that you have defined for a query object to object found. E.g. for following query
node('a', name='a').out().node('b').out().node('c', name='c')
results will look like {'a': <node type='a'>, 'c': <node
type='c'>}
. Notice, only named objects are present (there is no
<node type='b'>
in results, although that node is
present in query because it does not have a name).
You register a query to be monitored and a callback to execute if something will change. Later, if someone will modify the graph being monitored, it will detect that new graph updates caused new query results to appear, or old results to disappear or update. The response executes the callback that is associated with the query. The callback receives the whole path from the query as a response, and a specific action (added/updated/removed) to execute.
Notification Processing
When the result is passed to the processing (callback) function, from there you can specify reasoning logic. This could really be anything, from generating logs, errors, to rendering configurations, or running semantic validations. You could also modify the graph itself, using graph APIs and some other piece of logic may react to changes you made. This way, you can enforce the graph as a single source of truth while it also serves as a logical communication channel between pieces of your application logic. The Graph API consists of three parts:
Graph management - methods to add/update/remove stuff in a graph.
add_node()
, set_node()
,
del_node()
,
get_node()
add_relationship()
,
set_relationship()
, del_relationship()
,
get_relationship()
, commit()
Query
get_nodes()
get_relationships()
Observable
interface add_observer(),remove_observer()
Graph management APIs are self-explanatory. add_node()
creates
new node, set_node()
updates properties of existing node, and
del_node()
deletes a node.
commit()
is used to signal that all updates to the graph are
complete and they can be propagated to all listeners.
Relationships have similar API.
The observable interface allows you to add/remove observers - objects that implement notification a callback interface. Notification callback consists of three methods:
on_node()
- called when any node/relationship is added, removed or updatedon_relationship()
- called when any node/relationship is added, removed or updatedon_graph()
- called when the graph is committed
The Query API is the heart of our graph API and is what powers all searching.
Both get_nodes()
and get_relationships()
allow
you to search for corresponding objects in a graph. Arguments to those functions
are constraints on searched objects.
E.g. get_nodes()
returns you all nodes in a graph,
get_nodes(type='system')
returns you all system nodes,
get_nodes(type='system', role='spine')
allows you to
constrain returned nodes to those having particular property values. Values for
each argument could be either a plain value or a special property matcher
object. If the value is a plain value, the corresponding result object should
have its property equal to the given plain value. Property matchers allow you to
express a more complex criterias, e.g. not equal, less than, one of given values
and so on:
The example below is for directly using Graph python. For demonstration purposes, you can replace graph.get_nodes with node in the Graph explorer. This specific example will not work on the Apstra GUI.
graph.get_nodes( type='system', role=is_in(['spine', 'leaf']), system_id=not_none(), )
In your graph schema you can define custom indexes for particular
node/relationship types and the methods get_nodes()
and
get_relationships()
pick the best index for each particular
combination of constraints passed to minimize search time.
Results of get_nodes()
/get_relationships()
are
special iterator objects. You can iterate over them and they will yield all
found graph objects. You can also use APIs that those iterators provide to
navigate those result sets. E.g. get_nodes()
returns you a
NodeIterator object which has methods out(
) and
in_()
. You can use those to get an iterator over all
outgoing or incoming relationship from each node in the original result set.
Then, you can use those to get nodes on the other end of those relationships and
continue from them. You can also pass property constraints to those methods the
same way you can do for get_nodes()
and
get_relationships()
.
graph.get_nodes('system', role='spine') \ .out('interface').node('interface', if_type='loopback')
The code in the example above finds all nodes with type system and role spine and then finds all their loopback interfaces.
Putting It All Together
Thequery below is an example of an internal rule that Apstra can use to derive telemetry expectations -- for example, link and interface status. The @rule will insert a callback to process_spine_leaf_link, in which case we write to telemetry expectations.
@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
Convenience Functions
To avoid creating complex where()
clauses when building a graph
query, use convenience functions, available from the Apstra GUI.
From the blueprint navigate to the Staged view or Active view, then click the GraphQL API Explorer button (top-right >_). The graph explorer opens in a new tab.
Type a graph query on the left. See function descriptions below.
From the Action drop-down list, select qe.
Click the Execute Query button (looks like a play button) to see results.
Functions
The Query Engine describes a number of helpful functions:
match(*path_queries)
This function returns a QueryBuilder
object containing each
result of a matched query. This is generally a useful shortcut for grouping
multiple match queries together.
These two queries are not a 'path' together (no intended relationship). Notice the comma to separate out arguments. This query will return all of the leaf devices and spine devices together.
match( node('system', name='leaf', role='leaf'), node('system', name='spine', role='spine'), )
node(self, type=None, name=None, id=None, **properties)
- Parameters
- type (str or None) - Type of node to search for
- name (str or None) - Sets the name of the property matcher in the results
- id (str or None) - Matches a specific node by node ID in the graph
- properties (dict or None) - Any additional keyword arguments or additional property matcher convenience functions to be used
- Returns - Query builder object for chaining queries
- Return type - QueryBuilder
While both a function, this is an alias for the PathQueryBuilder nodes -- see below.
iterate()
- Returns - generator
- Return type: generator
Iterate gives you a generator function that you can use to iterate on individual path queries as if it were a list. For example:
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 Nodes
node(self, type=None, name=None, id=None, **properties)
This function describes specific graph node, but is also a shortcut for
beginning a path query from a specific node. The result of a
`node()
call returns a path query object. When querying
a path, you usually want to specify a node `type`: for example
node('system')
would return a system node.
- Parameters
- type (str or None) - Type of node to search for
- name (str or None) - Sets the name of the property matcher in the results
- id (str or None) - Matches a specific node by node ID in the graph
- properties (dict or None) - Any additional keyword arguments or additional property matcher convenience functions to be used
- Returns - Query builder object for chaining queries
- Return type - QueryBuilder
If you want to use the node in your query results, you need to name it
--node('system', name='device')
. Furthermore, if you
want to match specific kwarg properties, you can directly specify the match
requirements -
node('system', name='device', role='leaf')
node('system', name='device', role='leaf')
out(type=None, id=None, name=None, **properties)
Traverses a relationship in the 'out' direction according to a graph schema. Acceptable parameters are the type of relationship (for example, interfaces), the specific name of a relationship, the id of a relationship, or other property matches that must match exactly given as keyword arguments.
- Parameters
- type (str or None) - Type of node relationship to search for
- id (str or None) - Matches a specific relationship by relationship ID in the graph
- name (str or None) - Matches a specific relationship by named relationship
For example:
node('system', name='system') \ .out('hosted_interfaces')
in_(type=None, id=None, name=None, **properties)
Traverses a relationship in the 'in' direction. Sets current node to relationship source node. Acceptable parameters are the type of relationship (for example, interfaces), the specific name of a relationship, the id of a relationship, or other property matches that must match exactly given as keyword arguments.
- Parameters
- type (str or None) - Type of node relationship to search for
- id (str or None) - Matches a specific relationship by relationship ID in the graph
- name (str or None) - Matches a specific relationship by named relationship
- properties (dict or None) - Matches relationships by any further kwargs or functions
node('interface', name='interface') \ .in_('hosted_interfaces')
where(predicate, names=None)
Allows you to specify a callback function against the graph results as a
filter or constraint. The predicate is a callback (usually lambda function)
run against the entire query result. where()
can be used
directly on an a path query result.
- Parameters
- predicate (callback) - Callback function to run against all nodes in graph
- names (str or None) - If names are given they are passed to callback function for match
node('system', name='system') \ .where(lambda system: system.role in ('leaf', 'spine'))
enure_different(*names)
Allows a user to ensure two different named nodes in the graph are not the same. This is helpful for relationships that may be bidirectional and could match on their own source nodes. Consider the query:
- Parameters
- names (tuple or list) - A list of names to ensure return different nodes or relationships from the graph
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')
The last line could be functionally equivalent to the
where()
function with a lambda callback function
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)
Property matchers
Property matches can be run on graph query objects directly - usually used
within a node()
function. Property matches allow for a few
functions.
eq(value)
Ensures the property value of the node matches exactly the results of the
eq(value)
function.
- Parameters
- value - Property to match for equality
node('system', name='system', role=eq('leaf'))
Which is similar to simply setting a value as a kwarg on a node object:
node('system', name='system', role='leaf')
node('system', name='system').where(lambda system: system.role == 'leaf')
Returns:
{ "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. Ensures the property value of the node does NOT match results of
ne(value)
function
- Parameters
- value - Value to ensure for inequality condition
node('system', name='system', role=ne('spine'))
Similar to:
node('system', name='system').where(lambda system: system != 'spine')
gt(value)
Greater-than. Ensures the property of the node is greater than the results of
gt(value)
function.
- Parameters
- value - Ensure property function is greater than this value
node('vn_instance', name='vlan', vlan_id=gt(200))
ge(value)
Greater-than or Equal To. Ensures the property of the node is greater than or
equal to results of ge()
.
- Parameters: value - Ensure property function is greater than or equal to this value
node('vn_instance', name='vlan', vlan_id=ge(200))
lt(value)
Less-than. Ensures the property of the node is less than the results of
lt(value)
.
- Parameters
- value - Ensure property function is less than this value
node('vn_instance', name='vlan', vlan_id=lt(200))
Similar to:
node('vn_instance', name='vlan').where(lambda vlan: vlan.vlan_id <= 200)
le(value)
Less-than or Equal to. Ensures the property is less than, or equal to the
results of le(value)
function.
- Parameters
- value - Ensures given value is less than or equal to property function
node('vn_instance', name='vlan', vlan_id=le(200))
Similar to:
node('vn_instance', name='vlan').where(lambda vlan: vlan.vlan_id < 200)
is_in(value)
Is in (list). Check if the property is in a given list or set containing
items is_in(value)
.
- Parameters
- value (list) - Ensure given property is in this list
node('system', name='system', role=is_in(['leaf', 'spine']))
Similar to:
node('system', name='system').where(lambda system: system.role in ['leaf', 'spine'])
not_in(value)
Is not in (list). Check if the property is NOT in a given list or set
containing items not_in(value)
.
- Parameters
- value (list) - List Value to ensure property matcher is not in
node('system', name='system', role=not_in(['leaf', 'spine']))
Similar to:
node('system', name='system').where(lambda system: system.role not in ['leaf', 'spine'])
is_none()
A query that expects is_none expects this particular attribute to be
specifically None
.
node('interface', name='interface', ipv4_addr=is_none()
Similar to:
node('interface', name='interface').where(lambda interface: interface.ipv4_addr is None)
not_none()
A matcher that expects this attribute to have a value.
node('interface', name='interface', ipv4_addr=not_none()
Similar to:
node('interface', name='interface').where(lambda interface: interface.ipv4_addr is not None)
Apstra Graph Datastore
The Apstra graph datastore is an in-memory graph database. The log file size is checked periodically, and when a blueprint change is committed. If the graph datastore reaches 100MB or more, a new graph datastore checkpoint file is generated. The database itself does not remove any graph datastore persistence logs or checkpoint files. Apstra provides clean-up tools for the main graph datastore.
Valid graph datastore persistence file groups contain four files:
log
, log-valid
,
checkpoint
, and checkpoint-valid
. Valid
files are the effective indicators for log and checkpoint files. The name of
each persistence file has three parts: basename, id, and extension.
# regex for sysdb persistence files. # e.g. # _Main-0000000059ba612e-00017938-checkpoint-valid # \--/ \-----------------------/ \--------------/ # basename id extension
- basename - derived from the main graph datastore partition name.
- id - a unix timestamp obtained from gettimeofday. Seconds and microseconds in the timestamp are separated by a "-". A persistence file group can be identified by id. The timestamp can also help to determine the generated time sequence of persistence file groups.
- extension -
log
,log-valid
,checkpoint
, orcheckpoint-valid
.