Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adding node linking feature #27

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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
7 changes: 7 additions & 0 deletions plugins/module_utils/cml_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,13 @@ def get_node_by_name(self, lab, name):
return node
return None

def get_link_by_nodes(self, lab, node1, node2):
for link in lab.links():
if ((link.node_a.label == node1.label and link.node_b.label == node2.label)
or (link.node_b.label == node1.label and link.node_a.label == node2.label)):
return link
return None

Comment on lines +70 to +76

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

def exit_json(self, **kwargs):

self.result.update(**kwargs)
Expand Down
219 changes: 219 additions & 0 deletions plugins/modules/cml_link_node.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-

# Copyright (c) 2017 Cisco and/or its affiliates.
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#

from __future__ import (absolute_import, division, print_function)

__metaclass__ = type

ANSIBLE_METADATA = {'metadata_version': '1.1', 'status': ['preview'], 'supported_by': 'community'}

DOCUMENTATION = r"""
---
module: cml_link_node
short_description: Create, update or delete a link between two nodes in a CML Lab
description:
- Create, update or delete a link between two nodes in a CML Lab
- Establishes a link between two nodes
- Node names need to be specified to create the link
- Node links can be updated to point to different nodes: node1->node2 is changed to node1->node3
- Node links can be deleted
- Note: when updating, all three nodes must be specified
author:
- Paul Pajerski (@ppajersk)
requirements:
- virl2_client
version_added: '0.1.0'
options:
action:
description: The desired action to take with the link
required: false
type: str
choices: ['create', 'update', 'delete']
default: create

lab:
description: The name of the CML lab (CML_LAB)
required: true
type: str

source_node:
description: The name of the first node
required: true
type: str

destination_node:
description: The name of the second node
required: true
type: str

update_node:
description: The name of the third node, this node is only used in update commands
where if provided alongside the update action, will update the link between source_node and destination_node
to link between source_node and update_node
required: false
type: str

x:
description: X coordinate on topology canvas
required: false
type: int

y:
description: Y coordinate on topology canvas
required: false
type: int

tags:
description: List of tags
required: false
type: list
elements: str

wait:
description: Wait for lab virtual machines to boot before continuing
required: false
type: bool
default: False
Comment on lines +74 to +94

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

obsolete for this module, should be deleted

"""

EXAMPLES = r"""
- name: Link two CML nodes
hosts: cml_hosts
connection: local
gather_facts: no
tasks:
- name: Link nodes
cisco.cml.cml_link_node:
host: "{{ cml_host }}"
user: "{{ cml_username }}"
password: "{{ cml_password }}"
lab: "{{ cml_lab }}"
source_node: "{{ source_node }}"
destination_node: "{{ destination_node }}"
action: create

- name: Update a link between two CML nodes
hosts: cml_hosts
connection: local
gather_facts: no
tasks:
- name: Link nodes
cisco.cml.cml_link_node:
host: "{{ cml_host }}"
user: "{{ cml_username }}"
password: "{{ cml_password }}"
lab: "{{ cml_lab }}"
source_node: "{{ source_node }}"
destination_node: "{{ destination_node }}"
update_node: "{{ update_node }}"
action: update

- name: Delete a link between two CML nodes
hosts: cml_hosts
connection: local
gather_facts: no
tasks:
- name: Link nodes
cisco.cml.cml_link_node:
host: "{{ cml_host }}"
user: "{{ cml_username }}"
password: "{{ cml_password }}"
lab: "{{ cml_lab }}"
source_node: "{{ source_node }}"
destination_node: "{{ destination_node }}"
action: delete
"""

from ansible.module_utils.basic import AnsibleModule, env_fallback
from ansible_collections.cisco.cml.plugins.module_utils.cml_utils import cmlModule, cml_argument_spec

def run_module():
# define available arguments/parameters a user can pass to the module
argument_spec = cml_argument_spec()
argument_spec.update(
lab=dict(type='str', required=True, fallback=(env_fallback, ['CML_LAB'])),
action=dict(type='str', required=True),
source_node=dict(type='str', required=True),
destination_node=dict(type='str', required=True),
update_node=dict(type='str', required=False),
tags=dict(type='list', elements='str'),
x=dict(type='int'),
y=dict(type='int'),
wait=dict(type='bool', default=False),
Comment on lines +157 to +160

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

obsolete for this module, should be deleted

)

# the AnsibleModule object will be our abstraction working with Ansible
# this includes instantiation, a couple of common attr would be the
# args/params passed to the execution, as well as if the module
# supports check mode
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True,
)
cml = cmlModule(module)

labs = cml.client.find_labs_by_title(cml.params['lab'])
if len(labs) > 0:
lab = labs[0]
else:
cml.fail_json("Cannot find lab {0}".format(cml.params['lab']))

# get both nodes by name
source_node = cml.get_node_by_name(lab, cml.params['source_node'])
destination_node = cml.get_node_by_name(lab, cml.params['destination_node'])
update_node = cml.get_node_by_name(lab, cml.params['update_node'])

if source_node == None or destination_node == None:
cml.fail_json("One or more nodes cannot be found. Nodes need to be created before a link can be established")
cml.exit_json(**cml.result)
return

link = cml.get_link_by_nodes(lab, source_node, destination_node)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

virl2-client node supports this natively:

link = source_node.get_link_to(destination_node)

as per:
https://github.com/CiscoDevNet/virl2-client/blob/22693cd1be5f321f3848fe591436524abdb804fe/virl2_client/models/node.py#L635

        def get_link_to(self, other_node: Node) -> Link | None:
        """
        Return one link between this node and another.

        :param other_node: The other node.
        :returns: A link between this node and the other node, if one exists.
        """
        for link in self.links():
            if other_node in link.nodes:
                return link
        return None


if cml.params['action'] == 'create':
if link == None: # if the link does not exist
link = lab.connect_two_nodes(source_node, destination_node)
cml.result['changed'] = True
else:
cml.fail_json("Link between nodes already exists")
elif cml.params['action'] == 'update':
if link is not None:
if update_node is not None: # only need to check if update_node is none here
lab.remove_link(link) # remove current link
link = lab.connect_two_nodes(source_node, update_node) # create new link
cml.result['changed'] = True
else:
cml.fail_json("update_node cannot be found or does not exist")
else:
cml.fail_json("Link between nodes does not exist")
elif cml.params['action'] == 'delete':
if link is not None:
lab.remove_link(link) # remove current link
cml.result['changed'] = True
else:
cml.fail_json("Link between nodes does not exist")
cml.exit_json(**cml.result)

def main():
run_module()

if __name__ == '__main__':
main()