diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..1482982 --- /dev/null +++ b/CONTRIBUTING.md @@ -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/ +``` + + diff --git a/README.md b/README.md index 2956f5f..287e8f1 100644 --- a/README.md +++ b/README.md @@ -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/ -``` \ No newline at end of file +## Looking to contribute? +Please follow the [CONTRIBUTING](CONTRIBUTING.md) instructions \ No newline at end of file diff --git a/aos/blueprint.py b/aos/blueprint.py index f6df9c0..8877543 100644 --- a/aos/blueprint.py +++ b/aos/blueprint.py @@ -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( diff --git a/docs/example-scripts/blueprint/graph_queries.md b/docs/example-scripts/blueprint/graph_queries.md index b522130..b6e11f6 100644 --- a/docs/example-scripts/blueprint/graph_queries.md +++ b/docs/example-scripts/blueprint/graph_queries.md @@ -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 = ( @@ -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')" @@ -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 -:( \ No newline at end of file +## 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"}) +``` \ No newline at end of file