Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Contributing to apstra-api-python
Thank you for contributing to apstra-api-python!

## Bugs?
Please report bugs as issues here on GitLab!

## Code submission
Submit your code as a new branch based off `main` and submit a Pull
Request (PR). All tests and approvals of a PR must be meet before merge.

### Developers
### Setup Development Environment
Create a virtual environment and install apstra-api-python:
```
$ cd apstra-api-python
$ python3.8 -m venv .venv
$ source .venv/bin/activate
$ pip install -r dev-requirements.txt
```

### Running Tests
- Run full test suite
```
$ tox
```
- Run unit tests:
```
$ tox -e py38
```
- Run linters:
```
$ tox -e flake8
```

### Format code
Black is used to format code pre-commit
```
black aos/
black tests/
```


39 changes: 6 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,44 +1,17 @@
# Apstra (AOS) RestAPI Python Library

Educational Python library with the goal to teach users how to
programmatically interact with apstra and integrate the Apstra
programmatically interact with Apstra and integrate the Apstra
Rest API.

## Documentation
If you are new to Apstra, this library or looking to get a better understanding of
the apstra Rest API please start with the
[documentation](https://apstra-api-python.readthedocs.io/en/latest/api-introduction/).
[documentation](https://apstra-api-python.readthedocs.io/en/latest/).

# Developers
## Testing
### Setup Development Environment
The documentation includes an introduction to the Apstra API along with a catalog
of working examples of common tasks within Apstra using this library.

Create a virtual environment and install aos-api-python:

```
$ cd apstra-api-python
$ python3.8 -m venv .venv
$ source .venv/bin/activate
$ pip install -r dev-requirements.txt
```

### Running Tests
- Run full test suite
```
$ tox
```
- Run unit tests:
```
$ tox -e py38
```
- Run linters:
```
$ tox -e flake8
```

### Format code
Black is used to format code
```
black aos/
black tests/
```
## Looking to contribute?
Please follow the [CONTRIBUTING](CONTRIBUTING.md) instructions
2 changes: 1 addition & 1 deletion aos/blueprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -603,7 +603,7 @@ def ql_query(self, bp_id: str, query: str, params: dict = None):
data = {"query": query}
resp = self.rest.json_resp_post(uri=ql_path, data=data, params=params)

return resp["items"]
return resp["data"]

# Resources
def get_all_bp_resource_groups(
Expand Down
150 changes: 122 additions & 28 deletions docs/example-scripts/blueprint/graph_queries.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,39 +29,57 @@ bp = aos.blueprint.get_id_by_name(label=bp_name)
```

## QE Queries
Return all fabric switches (nodes). Notice the use of "is_in" with role
to filter the query.
### Fabric Nodes
Return all fabric nodes (switches). Notice the use of `is_in` to filter
the query by role.
```python
switch_query = (
"match(node('system', name='switches', "
"role=is_in(['spine', 'leaf', 'superspine'])))"
)
resp = aos.blueprint.qe_query(bp.id, query=switch_query)
resp = aos.blueprint.qe_query(bp.id,
query=switch_query,
params={"type": "staging"})
```
In the query above `{"type": "staging"}` is included as a params
argument. This allows you to specify either the `staging` or `operation`
(active) blueprint graph.
This parameter is supported for both QE and QL queries. The default is
`staging`

`aos.blueprint.get_all_tor_nodes()` uses two queries to return all top of
rack (ToR) nodes and their properties. It calls two methods to do this.
### Get all ToR Nodes
A good example of QE queries in action is the
`aos.blueprint.get_all_tor_nodes()` method which uses two queries to
return all top of rack (ToR) nodes and their properties. It calls two
methods to collect the data it needs.

`aos.blueprint.get_bp_system_leaf_nodes()` returns all nodes of type system
with a role of 'leaf'.
```python
leaf_query = "match(node('system', name='leaf', role='leaf'))"
resp = aos.blueprint.qe_query(bp.id, query=leaf_query)
```
- `aos.blueprint.get_bp_system_leaf_nodes()` returns all nodes of type
`system` with a role of `leaf`.
```python
leaf_query = "match(node('system', name='leaf', role='leaf'))"
resp = aos.blueprint.qe_query(bp.id,
query=leaf_query,
params={"type": "staging"})
```

`aos.blueprint.get_bp_system_redundancy_group()` returns the
redundancy_group details a given system is a member of.
```python
system_id = 'foo'
rg_query = (
"match(node('redundancy_group', name='rg')"
".out('composed_of_systems')"
".node('system', role='leaf',"
f" id='{system_id}'))"
)
resp = aos.blueprint.qe_query(bp.id, query=rg_query)
```
- `aos.blueprint.get_bp_system_redundancy_group()` returns the
redundancy_group (ESI/MLAG) details for a given system. Notice `system_id`
is passed in as a variable for this query which is done for each system returned in
the above query to determine if the system belongs to a redundancy_group.
```python
system_id = 'foo'
rg_query = (
"match(node('redundancy_group', name='rg')"
".out('composed_of_systems')"
".node('system', role='leaf',"
f" id='{system_id}'))"
)
resp = aos.blueprint.qe_query(bp.id,
query=rg_query,
params={"type": "staging"})
```

### Links in fabric
Query the Blueprint for all fabric links between leafs and spines
```python
link_query = (
Expand All @@ -70,11 +88,18 @@ link_query = (
".node('interface', name='iface').out('link')"
".node('link', role='spine_leaf'))"
),
resp = aos.blueprint.qe_query(bp.id, query=link_query)
resp = aos.blueprint.qe_query(bp.id,
query=link_query,
params={"type": "staging"})
```

### Routing-zone fabric links
Query the Blueprint for all links in the fabric belonging to a specific
routing-zone (VRF). We are using routing-zone 'blue' in this example.
routing-zone (VRF). We are using routing-zone `blue` in this example.

You will also notice there are two queries combined here, the second query
starting with `node('system, role='leaf')`. This allows you to take the
output of the first query and pass it into a new query.
```python
link_query = (
"match(node('system', role='spine', deploy_mode='deploy')"
Expand All @@ -92,8 +117,77 @@ link_query = (
".in_('instantiated_by')"
".node('security_zone', vrf_name='blue')"
)
resp = aos.blueprint.qe_query(bp.id, query=link_query)
resp = aos.blueprint.qe_query(bp.id,
query=link_query,
params={"type": "staging"})
```

# QL Queries
:(
## QL Queries
### Leaf nodes interface details
Query the blueprint for all system leaf nodes and their current interface
details. This query also includes the connected links for each node.
Notice `(role: \"leaf\")` is used to filter by role.
```python
leaf_node_query = (
"{"
"system_nodes (role: \"leaf\"){"
"role "
"id "
"hostname "
"hosted_interfaces_targets {"
"ipv4_addr "
"if_name "
"link_targets {"
"link_type "
"id "
"speed "
"tags "
"}"
"}"
"}"
"}"
)

resp = aos.blueprint.ql_query(bp.id,
query=leaf_node_query,
params={"type": "staging"})
```

### Routing-zone interface details
Query the blueprint for all Routing-zones and their current interface
details. This query also includes the DHCP relay configured.
`instantiated_by_targets` is used to order the interfaces by type, first
by `loopback`, then by `subinterface`.
```python
sz_query = (
"{"
"security_zone_nodes {"
"id "
"label "
"sz_type "
"vrf_name "
"vni_id "
"vlan_id "
"instantiated_by_targets {"
"loopback : member_interfaces_targets(if_type:\"loopback\") {"
"if_name "
"ipv4_addr "
"ipv6_addr "
"}"
"subinterface : member_interfaces_targets(if_type:\"subinterface\") {"
"if_name "
"ipv4_addr "
"ipv6_addr "
"ipv6_enabled link : link_targets {role}"
"}"
"}"
"policy_policy_targets(policy_type:\"dhcp_relay\") {"
"dhcp_servers"
"}"
"}"
"}"
)
resp = aos.blueprint.ql_query(bp.id,
query=sz_query,
params={"type": "operations"})
```