Files
toallab-automation/collections/ansible_collections/davidban77/gns3/plugins/modules/gns3_project.py

435 lines
14 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python
ANSIBLE_METADATA = {
"metadata_version": "1.4",
"status": ["preview"],
"supported_by": "community",
}
DOCUMENTATION = """
---
module: gns3_project
short_description: Module to interact with GNS3 server projects
version_added: '2.8'
description:
- 'Module to interact with GNS3 server projects.
- It is using the L(gns3fy library,https://davidban77.github.io/gns3fy/)'
- It opens/closes projects and performs basic turnup/teradown operations on nodes.
- It creates/updates or deletes projects.
requirements: [ gns3fy ]
author:
- David Flores (@davidban77)
options:
url:
description:
- URL target of the GNS3 server
required: true
type: str
port:
description:
- TCP port to connect to server REST API
type: int
default: 3080
user:
description:
- User to connect to GNS3 server
type: str
password:
description:
- Password to connect to GNS3 server
type: str
state:
description:
- State of the project to be on the GNS3 server
- '- C(opened): Opens a project and turns up nodes'
- '- C(closed): Closes a project and turns down nodes'
- '- C(present): Creates/update a project on the server'
- '- C(absent): Deletes a project on the server'
type: str
choices: ['opened', 'closed', 'present', 'absent']
project_name:
description:
- Project name
type: str
project_id:
description:
- Project ID
type: str
nodes_state:
description:
- Starts/stops nodes on the project.
- Used when I(state) is C(opened)/C(closed)
type: str
choices: ['started', 'stopped']
nodes_strategy:
description:
- Start/stop strategy of the devices defined on the project.
- '- C(all): It starts/stops all nodes at once'
- '- C(one_by_one): It starts/stops nodes serialy using I(nodes_delay) time
between each action'
- Used when I(state) is C(opened)/C(closed)
type: str
choices: ['all', 'one_by_one']
default: 'all'
nodes_delay:
description:
- Delay time in seconds to wait between nodes start/stop
- Used when I(nodes_strategy) is C(one_by_one)
type: int
default: 10
poll_wait_time:
description:
- Delay in seconds to wait to poll nodes when they are started/stopped.
- Used when I(nodes_state) is C(started)/C(stopped)
type: int
default: 5
nodes_spec:
description:
- List of dictionaries specifying the nodes properties.
- '- Mandatory attributes: C(name), C(node_type) and C(template).'
- '- Optional attributes: C(compute_id). It defaults to C(local)'
type: list
links_spec:
description:
- 'List of lists specifying the links endpoints. Example: C(- ["alpine-1",
"eth0", "alpine-2", "eth0"])'
- 'Mandatory attributes: C(node_a), C(port_a), C(node_b) and C(port_b)'
type: list
"""
EXAMPLES = """
# Open a GNS3 project
- name: Start lab
gns3_project:
url: http://localhost
state: opened
project_name: lab_example
# Stop all nodes inside an open project
- name: Stop nodes
gns3_project:
url: http://localhost
state: opened
project_name: lab_example
nodes_state: stopped
nodes_strategy: all
poll_wait_time: 5
# Open a GNS3 project and start nodes one by one with a delay of 10sec between them
- name: Start nodes one by one
gns3_project:
url: http://localhost
state: opened
project_name: lab_example
nodes_state: started
nodes_strategy: one_by_one
nodes_delay: 10
# Close a GNS3 project
- name: Stop lab
gns3_project:
url: http://localhost
state: closed
project_id: 'UUID-SOMETHING-1234567'
# Create a GNS3 project
- name: Create a project given nodes and links specs
gns3_project:
url: http://localhost
state: present
project_name: new_lab
nodes_spec:
- name: alpine-1
node_type: docker
template: alpine
- name: alpine-2
node_type: docker
template: alpine
links_spec:
- ('alpine-1', 'eth0', 'alpine-2', 'eth1')
# Delete a GNS3 project
- name: Delete project
gns3_project:
url: http://localhost
state: absent
project_name: new_lab
"""
RETURN = """
name:
description: Project name
type: str
project_id:
description: Project UUID
type: str
status:
description: Project status. Possible values: opened, closed
type: str
path:
description: Path of the project on the server (works only with compute=local)
type: str
auto_close:
description: Project auto close when client cut off the notifications feed
type: bool
auto_open:
description: Project open when GNS3 start
type: bool
auto_start:
description: Project start when opened
type: bool
filename:
description: Project filename
type: str
"""
import time
import traceback
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
GNS3FY_IMP_ERR = None
try:
from gns3fy import Gns3Connector, Project
HAS_GNS3FY = True
except Exception:
HAS_GNS3FY = False
GNS3FY_IMP_ERR = traceback.format_exc()
def return_project_data(project):
"Returns the project main attributes"
return dict(
name=project.name,
project_id=project.project_id,
status=project.status,
path=project.path,
auto_close=project.auto_close,
auto_open=project.auto_open,
auto_start=project.auto_start,
filename=project.filename,
)
# def nodes_state_verification(module, project, result):
def nodes_state_verification(
expected_nodes_state, nodes_strategy, nodes_delay, poll_wait_time, project
):
"Verifies each node state and returns a changed attribute"
nodes_statuses = [node.status for node in project.nodes]
# Verify if nodes do not match expected state
if expected_nodes_state == "started" and any(
status == "stopped" for status in nodes_statuses
):
# Turnup the nodes based on strategy
if nodes_strategy == "all":
project.start_nodes(poll_wait_time=poll_wait_time)
elif nodes_strategy == "one_by_one":
for node in project.nodes:
if node.status != "started":
node.start()
time.sleep(nodes_delay)
return True
elif expected_nodes_state == "stopped" and any(
status == "started" for status in nodes_statuses
):
# Shutdown nodes based on strategy
if nodes_strategy == "all":
project.stop_nodes(poll_wait_time=poll_wait_time)
elif nodes_strategy == "one_by_one":
for node in project.nodes:
if node.status != "stopped":
node.stop()
time.sleep(nodes_delay)
return True
return False
def create_node(node_spec, project, module):
"Creates the node specified in nodes_spec"
# If exceptions occur then print them out in ansible format
try:
project.create_node(**node_spec)
except Exception as err:
module.fail_json(msg=str(err), exception=traceback.format_exc())
def create_link(link_spec, project, module):
"Creates the node specified in nodes_spec"
# If exceptions occur then print them out in ansible format
try:
project.create_link(*link_spec)
except ValueError as err:
if "At least one port is used" in str(err):
return False
else:
module.fail_json(msg=str(err), exception=traceback.format_exc())
except Exception as err:
module.fail_json(msg=str(err), exception=traceback.format_exc())
return True
def main():
module = AnsibleModule(
argument_spec=dict(
url=dict(type="str", required=True),
port=dict(type="int", default=3080),
user=dict(type="str", default=None),
password=dict(type="str", default=None, no_log=True),
state=dict(
type="str",
required=True,
choices=["opened", "closed", "present", "absent"],
),
project_name=dict(type="str", default=None),
project_id=dict(type="str", default=None),
nodes_state=dict(type="str", choices=["started", "stopped"]),
nodes_strategy=dict(
type="str", choices=["all", "one_by_one"], default="all"
),
nodes_delay=dict(type="int", default=10),
poll_wait_time=dict(type="int", default=5),
nodes_spec=dict(type="list"),
links_spec=dict(type="list"),
),
supports_check_mode=True,
required_one_of=[["project_name", "project_id"]],
required_if=[["nodes_strategy", "one_by_one", ["nodes_delay"]]],
)
result = dict(changed=False)
if not HAS_GNS3FY:
module.fail_json(msg=missing_required_lib("gns3fy"), exception=GNS3FY_IMP_ERR)
if module.check_mode:
module.exit_json(**result)
server_url = module.params["url"]
server_port = module.params["port"]
server_user = module.params["user"]
server_password = module.params["password"]
state = module.params["state"]
project_name = module.params["project_name"]
project_id = module.params["project_id"]
nodes_state = module.params["nodes_state"]
nodes_strategy = module.params["nodes_strategy"]
nodes_delay = module.params["nodes_delay"]
poll_wait_time = module.params["poll_wait_time"]
nodes_spec = module.params["nodes_spec"]
links_spec = module.params["links_spec"]
try:
# Create server session
server = Gns3Connector(
url=f"{server_url}:{server_port}", user=server_user, cred=server_password
)
# Define the project
if project_name is not None:
project = Project(name=project_name, connector=server)
elif project_id is not None:
project = Project(project_id=project_id, connector=server)
except Exception as err:
module.fail_json(msg=str(err), **result)
#  Retrieve project information
try:
project.get()
pr_exists = True
except Exception as err:
pr_exists = False
reason = str(err)
if state == "opened":
if pr_exists:
if project.status != "opened":
# Open project
project.open()
# Now verify nodes
if nodes_state is not None:
# Change flag based on the nodes state
result["changed"] = nodes_state_verification(
expected_nodes_state=nodes_state,
nodes_strategy=nodes_strategy,
nodes_delay=nodes_delay,
poll_wait_time=poll_wait_time,
project=project,
)
else:
# Means that nodes are not taken into account for idempotency
result["changed"] = True
# Even if the project is open if nodes_state has been set, check it
else:
if nodes_state is not None:
result["changed"] = nodes_state_verification(
expected_nodes_state=nodes_state,
nodes_strategy=nodes_strategy,
nodes_delay=nodes_delay,
poll_wait_time=poll_wait_time,
project=project,
)
else:
module.fail_json(msg=reason, **result)
elif state == "closed":
if pr_exists:
if project.status != "closed":
# Close project
project.close()
result["changed"] = True
else:
module.fail_json(msg=reason, **result)
elif state == "present":
if pr_exists:
if nodes_spec is not None:
# Need to verify if nodes exist
_nodes_already_created = [node.name for node in project.nodes]
for node_spec in nodes_spec:
if node_spec["name"] not in _nodes_already_created:
# Open the project in case it was closed
project.open()
create_node(node_spec, project, module)
result["changed"] = True
if links_spec is not None:
for link_spec in links_spec:
project.open()
# Trigger another get to refresh nodes attributes
project.get()
# Link verification is already built in the library
created = create_link(link_spec, project, module)
if created:
result["changed"] = True
else:
# Create project
project.create()
# Nodes section
if nodes_spec is not None:
for node_spec in nodes_spec:
create_node(node_spec, project, module)
# Links section
if links_spec is not None:
for link_spec in links_spec:
create_link(link_spec, project, module)
result["changed"] = True
elif state == "absent":
if pr_exists:
# Stop nodes and close project to perform delete gracefully
if project.status != "opened":
# Project needs to be opened in order to be deleted...
project.open()
project.stop_nodes(poll_wait_time=0)
project.delete()
result["changed"] = True
else:
module.exit_json(**result)
# Return the project data
result["project"] = return_project_data(project)
module.exit_json(**result)
if __name__ == "__main__":
main()