Skip to content

Commit

Permalink
Merge pull request #13 from davidban77/develop
Browse files Browse the repository at this point in the history
Adding gns3_node
  • Loading branch information
davidban77 authored Sep 23, 2019
2 parents 645c11b + c736b5f commit 30e44cc
Show file tree
Hide file tree
Showing 6 changed files with 399 additions and 72 deletions.
21 changes: 17 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,42 @@
# Releases

## 1.4.0

**Enhacements:**

- Added `gns3_node` to interact with the node inside a project. Provides the following:
- `start/stop/suspend/reload`: Actions to be applied on the node. These are idempotent with the exception of `reload`.
- Special flags like `retry` for an action to be applied a second time just in case... And a `force_project_open` to interact with a device if the project is closed
- Refactored `gns3_project` to be more pythonic

## 1.3.0

**Enhancements:**

- Added `gns3_node_file` and `gns3_project_file` modules.
- Improved the Makefile
- Added alpine node to the tests

## 1.2.2.

**Fixes:**

- Upgrading to `gns3fy ^0.4.0`

## 1.2.1

Enhancement:
**Enhancements:**

- No more `node_type` needed when creating nodes in a project.

## 1.2.0

New features:
**Enhancements:**

- Modules:
- `gns3_nodes_inventory`: Returns inventory-style dictionary of the nodes.

Fixes:
**Fixes:**

- Modules:
- Error when using the `gns3_version` module when `gns3fy` is not installed
Expand All @@ -33,7 +46,7 @@ Fixes:

## 1.1.0

New features:
**Enhancements:**

- Roles:
- `create_lab`: Create a GNS3 Lab by creating a project and starting up the nodes
Expand Down
9 changes: 6 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
VERSION=1.2.2
VERSION=1.4.0

build:
rm -rf releases/
Expand All @@ -10,6 +10,9 @@ publish: build
test-create-lab:
cd test/playbooks; ansible-playbook main.yml -e execute=create

test-node-interaction:
cd test/playbooks; ansible-playbook node_interaction.yml

test-create-files:
cd test/playbooks; ansible-playbook create_files.yml

Expand All @@ -19,6 +22,6 @@ test-delete-files:
test-delete-lab:
cd test/playbooks; ansible-playbook main.yml -e execute=delete

test-create-env: test-create-lab test-create-files
test-create-all: test-create-lab test-create-files test-node-interaction

test-delete-env: test-delete-files test-delete-lab
test-delete-all: test-delete-files test-delete-lab
2 changes: 1 addition & 1 deletion galaxy.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace: "davidban77"
name: "gns3"
version: "1.3.0"
version: "1.4.0"
readme: "README.md"
description: "Module to interact with GNS3 server REST API based on gns3fy"
authors:
Expand Down
258 changes: 258 additions & 0 deletions plugins/modules/gns3_node.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
#!/usr/bin/env python

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

DOCUMENTATION = """
---
module: gns3_node
short_description: Module to operate a node in a GNS3 server project
version_added: '2.8'
description:
- Module to operate a node in a GNS3 server project.
- It starts/stops/suspend/reloads a node.
requirements: [ gns3fy ]
author:
- David Flores (@netpanda)
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
project_name:
description:
- Project name
type: str
project_id:
description:
- Project ID
type: str
node_name:
description:
- Node name
type: str
node_id:
description:
- Node ID
type: str
state:
description:
- State of the node, it can be:
- '- C(started): Starts a node'
- '- C(stopped): Stops a node'
- '- C(suspended): Suspends a node'
- '- C(reload): Special non-idempotent action that reloads a node'
type: str
choices: ['started', 'stopped', 'suspended', 'reload']
retry:
description:
- Retries an action based on the state, if true and state is set to reload
- it will reload the device and try to start it again if the status was not
- changed
type: bool
default: false
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
force_project_open:
description:
- It will open the project (if closed) to interact with the device.
- Otherwise it will throw out an error
type: bool
default: false
"""

EXAMPLES = """
# Open a GNS3 project and start router01 node
- name: Start node
gns3_node:
url: http://localhost
project_name: lab_example
node_name: router01
node_state: started
force_project_open: true
# Stop a node and wait 10 seconds to poll for status
- name: Stop node
gns3_node:
url: http://localhost
project_name: lab_example
node_name: router01
state: stopped
# Suspend a node based on UUID
- name: Suspend node
gns3_node:
url: http://localhost
project_name: lab_example
node_id: 'ROUTER-UUID-SOMETHING-1234567'
state: suspended
# Reload a node and apply a retry to start if needed
- name: Stop lab
gns3_node:
url: http://localhost
project_id: 'PROJECT-UUID-SOMETHING-1234567'
node_name: router01
state: reload
retry: true
poll_wait_time: 30
"""

RETURN = """
name:
description: Project name
type: str
project_id:
description: Project UUID
type: str
node_id:
description: Node UUID
type: str
status:
description: Project status. Possible values: opened, closed
type: str
node_directory:
description: Path of the node on the server (works only with compute=local)
type: str
node_type:
description: Network node type
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_node_data(node):
"Returns the node main attributes"
return dict(
name=node.name,
project_id=node.project_id,
node_id=node.node_id,
status=node.status,
node_directory=node.node_directory,
node_type=node.node_type,
)


def state_verification(expected_state, node, retry=False, poll_wait_time=5):
"Verifies node state and returns a changed attribute"
if expected_state == "started" and node.status != "started":
node.start()
if node.status != "started" and retry:
node.start()
return True
elif expected_state == "stopped" and node.status != "stopped":
node.stop()
if node.status != "stopped" and retry:
node.stop()
return True
elif expected_state == "suspended" and node.status != "suspended":
node.suspend()
if node.status != "suspended" and retry:
node.suspend()
return True
elif expected_state == "reload":
node.reload()
time.sleep(poll_wait_time)
node.get()
if node.status != "started" and retry:
node.start()
return True
return False


def main():
module = AnsibleModule(
argument_spec=dict(
url=dict(type="str", required=True),
port=dict(type="int", default=3080),
state=dict(
type="str",
required=True,
choices=["started", "stopped", "suspended", "reload"],
),
project_name=dict(type="str", default=None),
project_id=dict(type="str", default=None),
node_name=dict(type="str", default=None),
node_id=dict(type="str", default=None),
retry=dict(type="bool", default=False),
poll_wait_time=dict(type="int", default=5),
force_project_open=dict(type="bool", default=False),
),
required_one_of=[["project_name", "project_id"], ["node_name", "node_id"]],
)
result = dict(changed=False)
if not HAS_GNS3FY:
module.fail_json(msg=missing_required_lib("gns3fy"), exception=GNS3FY_IMP_ERR)

server_url = module.params["url"]
server_port = module.params["port"]
state = module.params["state"]
project_name = module.params["project_name"]
project_id = module.params["project_id"]
node_name = module.params["node_name"]
node_id = module.params["node_id"]
retry = module.params["retry"]
poll_wait_time = module.params["poll_wait_time"]
force_project_open = module.params["force_project_open"]

try:
# Create server session
server = Gns3Connector(url=f"{server_url}:{server_port}")
# 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)
if project is None:
module.fail_json(msg="Could not retrieve project. Check name", **result)

project.get()
if project.status != "opened" and force_project_open:
project.open()

# Retrieve node
if node_name is not None:
node = project.get_node(name=node_name)
elif node_id is not None:
node = project.get_node(node_id=node_id)
if node is None:
module.fail_json(msg="Could not retrieve node. Check name", **result)
except Exception as err:
module.fail_json(msg=str(err), **result)

# Apply state change
result["changed"] = state_verification(
expected_state=state, node=node, retry=retry, poll_wait_time=poll_wait_time
)

# Return the node data
result["node"] = return_node_data(node)
module.exit_json(**result)


if __name__ == "__main__":
main()
Loading

0 comments on commit 30e44cc

Please sign in to comment.