Skip to content

Commit

Permalink
Merge pull request #175 from /issues/173-cv-container
Browse files Browse the repository at this point in the history
Fix issue with cv_container and relative topology
  • Loading branch information
titom73 authored Apr 7, 2020
2 parents 7195793 + 9cab4fb commit c228aa0
Show file tree
Hide file tree
Showing 6 changed files with 219 additions and 69 deletions.
71 changes: 57 additions & 14 deletions ansible_collections/arista/cvp/plugins/modules/cv_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
author: EMEA AS Team (@aristanetworks)
short_description: Manage Provisioning topology.
description:
- CloudVison Portal Configlet configuration requires a dictionary of containers with their parent,
- CloudVision Portal Configlet configuration requires a dictionary of containers with their parent,
to create and delete containers on CVP side.
- Returns number of created and/or deleted containers
options:
Expand Down Expand Up @@ -130,7 +130,7 @@ def create_builtin_containers(facts):

def get_root_container(containers_fact, debug=True):
"""
Extract name of the root conainer provided by cv_facts.
Extract name of the root container provided by cv_facts.
Parameters
----------
Expand Down Expand Up @@ -161,15 +161,15 @@ def tree_to_list(json_data, myList):
Example:
--------
>>> containers = {"Tenant": {"children": [{"Fabric": {"children": [{"Leaves": {"children": ["MLAG01", "MLAG02"]}}, "Spines"]}}]}}
>>> print tree_to_list(containers=containers, myList=lsit())
>>> print tree_to_list(containers=containers, myList=list())
[u'Tenant', u'Fabric', u'Leaves', u'MLAG01', u'MLAG02', u'Spines']
Parameters
----------
json_data : [type]
[description]
myList : list
Ordered list of element to create on CVP / recusrive function
Ordered list of element to create on CVP / recursive function
Returns
-------
Expand Down Expand Up @@ -247,17 +247,24 @@ def tree_build_from_dict(containers=None, root='Tenant'):
previously_created = list()
# Create root node to mimic CVP behavior
MODULE_LOGGER.debug('containers list is %s', str(containers))
MODULE_LOGGER.debug('root container is set to: %s', str(root))
tree.create_node(root, root)
# Iterate for first level of containers directly attached under root.
for container_name, container_info in containers.items():
if container_info['parent_container'] in [root]:
previously_created.append(container_name)
tree.create_node(container_name, container_name, parent=container_info['parent_container'])
MODULE_LOGGER.debug(
'create root tree entry with: %s', str(container_name))
# Loop since expected tree is not equal to number of entries in container topology
while len(tree.all_nodes()) < len(containers) + 1:
while (len(tree.all_nodes()) < len(containers)):
MODULE_LOGGER.debug(
' Tree has size: %s - Containers has size: %s', str(len(tree.all_nodes())), str(len(containers)))
for container_name, container_info in containers.items():
if tree.contains(container_info['parent_container']) and container_info['parent_container'] not in [root]:
try:
MODULE_LOGGER.debug(
'create new node with: %s', str(container_name))
tree.create_node(container_name, container_name, parent=container_info['parent_container'])
except: # noqa E722
continue
Expand Down Expand Up @@ -320,6 +327,7 @@ def tree_build_from_list(containers, root='Tenant'):
if cvp_container['parentName'] is None:
continue
if cvp_container['parentName'] in [root]:
MODULE_LOGGER.debug('found container attached to %s: %s', str(root), str(cvp_container))
previously_created.append(cvp_container['name'])
tree.create_node(cvp_container['name'], cvp_container['name'], parent=cvp_container['parentName'])
# Loop since expected tree is not equal to number of entries in container topology
Expand Down Expand Up @@ -475,7 +483,7 @@ def create_new_containers(module, intended, facts):
# Build ordered list of containers to create: from Tenant to leaves.
container_intended_tree = tree_build_from_dict(containers=intended, root=topology_root)
container_intended_ordered_list = tree_to_list(json_data=container_intended_tree, myList=list())
# Parse ordered list of container and chek if they are configured on CVP.
# Parse ordered list of container and check if they are configured on CVP.
# If not, then call container creation process.
for container_name in container_intended_ordered_list:
found = False
Expand All @@ -494,7 +502,7 @@ def create_new_containers(module, intended, facts):
# If a container has been created, increment creation counter
if response[0]:
count_container_creation += 1
# Build module message to retur for creation.
# Build module message to return for creation.
if count_container_creation > 0:
return [True, {'containers_created': "" + str(count_container_creation) + ""}]
return [False, {'containers_created': "0"}]
Expand Down Expand Up @@ -635,7 +643,7 @@ def container_info(container_name, module):
container_name : string
Name of the container to look for on CVP side.
module : AnsibleModule
Ansible module to get access to cvp cient.
Ansible module to get access to cvp client.
Returns
-------
dict: Dict of container info from CVP or exit with failure if no info for
Expand All @@ -659,7 +667,7 @@ def device_info(device_name, module):
device_name : string
Name of the container to look for on CVP side.
module : AnsibleModule
Ansible module to get access to cvp cient.
Ansible module to get access to cvp client.
Returns
-------
dict: Dict of device info from CVP or exit with failure if no info for
Expand Down Expand Up @@ -755,7 +763,7 @@ def container_factinfo(container_name, facts):
configlet_name : string
Name of the container to look for on CVP side.
module : AnsibleModule
Ansible module to get access to cvp cient.
Ansible module to get access to cvp client.
Returns
-------
Expand All @@ -777,7 +785,7 @@ def configlet_factinfo(configlet_name, facts):
configlet_name : string
Name of the container to look for on CVP side.
module : AnsibleModule
Ansible module to get access to cvp cient.
Ansible module to get access to cvp client.
Returns
-------
Expand Down Expand Up @@ -864,6 +872,31 @@ def attached_configlet_to_container(module, intended, facts):
return result


def locate_relative_root_container(containers_topology):
"""
Function to locate root container of partial topology
In case user provides a partial topology, it is required to locate root of
this topology and not CVP root container. it is useful in case of a partial
deletion and not complete tree.
Parameters
----------
containers_topology : dict
User's defined intended topology
Returns
-------
string
Name of the relative root container. None if not found.
"""
MODULE_LOGGER.debug('relative intended topology is: %s', str(containers_topology))
for container_name, container in containers_topology.items():
if container['parent_container'] not in containers_topology.keys():
return container_name
return None


def delete_topology(module, intended, facts):
"""
Delete CVP Topology when state is set to absent.
Expand All @@ -883,13 +916,23 @@ def delete_topology(module, intended, facts):

# Get root container for current topology
topology_root = get_root_container(containers_fact=facts['containers'])

# First try to get relative topology root container.
topology_root_relative = locate_relative_root_container(containers_topology=intended)
# If not found for any reason, fallback to CVP root container.
if topology_root_relative is None:
topology_root_relative = get_root_container(
containers_fact=facts['containers'])
MODULE_LOGGER.info('relative topology root is: %s', str(topology_root))
# Build a tree of containers configured on CVP
container_cvp_tree = tree_build_from_list(containers=facts['containers'], root=topology_root)
MODULE_LOGGER.info('build tree topology from facts topology')
container_cvp_tree = tree_build_from_list(
containers=facts['containers'], root=topology_root)
container_cvp_ordered_list = tree_to_list(json_data=container_cvp_tree, myList=list())

# Build a tree of containers expected to be deleted from CVP
container_intended_tree = tree_build_from_dict(containers=intended, root=topology_root)
MODULE_LOGGER.info('build tree topology from intended topology')
container_intended_tree = tree_build_from_dict(
containers=intended, root=topology_root_relative)
container_intended_ordered_list = tree_to_list(json_data=container_intended_tree, myList=list())

MODULE_LOGGER.info('container_intended_ordered_list %s', container_intended_ordered_list)
Expand Down
22 changes: 14 additions & 8 deletions docs/cv_configlet.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
# Manage Configlet content

## Descrpition
- [Manage Configlet content](#manage-configlet-content)
- [Description](#description)
- [Options](#options)
- [Usage](#usage)
- [Authentication](#authentication)
- [Inputs example](#inputs-example)
- [Result example](#result-example)

## Description

__Module name:__ `arista.cvp.cv_configlet`

Expand All @@ -14,12 +22,12 @@ Module comes with a set of options:
- `cvp_facts`: Current facts collecting on CVP by a previous task
- `configlet_filter`: Filter to apply configlet management. If configured, module will add/update/delete configlets matching entries. If not matching, module will ignore configlet configured on CVP. If option is not set, module will only work in `add` mode
- `state`: Provide an option to delete configlets from CloudVision. Default value is present and it is an optional field.
- `present`: Create / update configlets from CV.
- `absent`: remove configlets from CV.
- `present`: Create / update configlets from CV.
- `absent`: remove configlets from CV.

## Usage

__Authentication__
### Authentication

This module uses `HTTPAPI` connection plugin for authentication. These elements shall be declared using this plugin mechanism and are automatically shared with `arista.cvp.cv_*` modules.

Expand All @@ -37,7 +45,7 @@ ansible_network_os=eos
ansible_httpapi_port=443
```

__Inputs__
### Inputs example

Below is a basic playbook to collect facts:

Expand All @@ -59,7 +67,7 @@ Below is a basic playbook to collect facts:
register: cvp_configlet
```
__Result__
### Result example
Below is an example of expected output
Expand Down Expand Up @@ -88,5 +96,3 @@ msg:
-alias a971 show version+alias b61 show version
failed: false
```
94 changes: 86 additions & 8 deletions docs/cv_container.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Manage Provisionning topology

- [Manage Provisionning topology](#manage-provisionning-topology)
- [Descrpition](#descrpition)
- [Options](#options)
- [Usage](#usage)
- [Authentication](#authentication)
- [Build Topology](#build-topology)
- [Merge inputs example](#merge-inputs-example)
- [Merge result example](#merge-result-example)
- [Delete Topology](#delete-topology)
- [Delete inputs example](#delete-inputs-example)
- [Delete result example](#delete-result-example)

## Descrpition

__Module name:__ `arista.cvp.cv_container`
Expand All @@ -13,15 +25,15 @@ Module comes with a set of options:
- `topology`: Topology to build on Cloudvision.
- `cvp_facts`: Current facts collecting on CVP by a previous task
- `mode`: Define how module should work with container topology. Value can be:
- `merge`: Combine the new container topology with the existing topology.
- `override`: Discard the entire existing container topology and replace it with the new topology.
- `delete`: Discard the entire container topology provided in `topology` and keep other existing containers untouched.
- `merge`: Combine the new container topology with the existing topology.
- `override`: Discard the entire existing container topology and replace it with the new topology.
- `delete`: Discard the entire container topology provided in `topology` and keep other existing containers untouched.

If __`mode`__ is set to __`delete`__, container topology shall be described starting from `Tenant`. A partial tree is currenly not supported.
If __`mode`__ is set to __`delete`__, container topology can be a partial tree starting [`v1.0.6`](https://github.com/aristanetworks/ansible-cvp/releases/tag/v1.0.6). If using version `=< 1.0.5`, topology shall be described starting from `Tenant`.

## Usage

__Authentication__
### Authentication

This module uses `HTTPAPI` connection plugin for authentication. These elements shall be declared using this plugin mechanism and are automatically shared with `arista.cvp.cv_*` modules.

Expand All @@ -39,9 +51,11 @@ ansible_network_os=eos
ansible_httpapi_port=443
```

__Inputs__
### Build Topology

#### Merge inputs example

Below is a basic playbook to collect facts:
Below is a basic playbook to create a containers topology:

```yaml
vars:
Expand Down Expand Up @@ -75,7 +89,7 @@ tasks:
mode: merge
```
__Result__
#### Merge result example
Below is an example of expected output
Expand Down Expand Up @@ -201,3 +215,67 @@ Below is an example of expected output
"failed": false
}
```

### Delete Topology

To delete a topology you can either define a complete topology bound to `Tenants` or a partial tree.

#### Delete inputs example

```yaml
- name: Container Management in Cloudvision
hosts: cv_server
connection: local
gather_facts: false
collections:
- arista.avd
- arista.cvp
vars:
run_tasks: false
run_mode: delete
CVP_CONTAINERS:
POD01:
parent_container: Leafs

tasks:
- name: "collect CV facts on {{inventory_hostname}}"
arista.cvp.cv_facts:
register: CVP_FACTS

- name: 'running cv_container in {{run_mode}} on {{inventory_hostname}}'
arista.cvp.cv_container:
cvp_facts: "{{CVP_FACTS.ansible_facts}}"
topology: "{{CVP_CONTAINERS}}"
mode: '{{run_mode}}'
register: result

- debug:
msg: '{{result}}'
```
#### Delete result example
```json
{
"msg": {
"changed": true,
"cv_container": {
"changed": true,
"deletion_result": {
"containers_deleted": "1"
},
"taskIds": [],
"tasks": []
},
"data": {
"changed": true,
"deletion_result": {
"containers_deleted": "1"
},
"taskIds": [],
"tasks": []
},
"failed": false
}
}
```
Loading

0 comments on commit c228aa0

Please sign in to comment.