"""gns3_worker.py
Creates project and nodes on GNS3 server and then populates their configuration."""
import asyncio
from io import BytesIO
import tarfile
import requests
import aiohttp
import docker
[docs]
def invoker(servername_in: str, gns3_url_in: str, sw_vals_in: list,
allconf_in: list, prj_id_in: str, connx_in: list):
"""Add nodes to the new GNS3 project and push a copy of the configuration files
to their substrate docker containers. Use asyncio/aoihttp to let post requests
with long completion time run in the background usign cooperative multitasking
Parameters
----------
servername_in : str
The name of the aiohttp.ClientSession object used for the connections
gns3_url_in : str
The URL to be posted to the GNS3 server
sw_vals_in : list
List of needed data about the switches to be emulated
allconf_in : list
List-of-lists holding all of the switch's configurations
connx_in : list
List of connections we need to make between the GNS3 nodes we're creating
"""
# Manage an event loop for all of the work done by gns3_node_create_async
print('')
print('Creating cEOS nodes in GNS3 project and pushing startup configs to each.')
sw_vals_new = asyncio.run(gns3_nodes_create_async
(servername_in, gns3_url_in, sw_vals_in, allconf_in,
prj_id_in))
# Only AFTER gns3_node_create_async is done, do we start populating connections
lastwords = asyncio.run(gns3_connx_create_async(servername_in, gns3_url_in,
sw_vals_new, connx_in, prj_id_in))
return lastwords
[docs]
async def gns3_nodes_create_async(servername_in: str, gns3_url_in: str, sw_vals_in:
list, allconf_in: list, prj_id_in: str):
"""Add nodes to the new GNS3 project and push a copy of the configuration files
to their substrate docker containers.
Parameters
----------
servername_in : str
The name of the aiohttp.ClientSession object used for the connections
gns3_url_in : str
The URL to be posted to the GNS3 server
sw_vals_in : list
List of needed data about the switches to be emulated
all_conf_in : list
List-of-lists holding all of the switch's configurations
"""
print('')
print('Creating the nodes in the GNS3 project.')
async with aiohttp.ClientSession() as session:
# Set x/y coordinates for the first node on the project
nodex = -825
nodey = -375
# Create docker client for RESTful API
d_clnt = docker.DockerClient(base_url='tcp://'+servername_in+':2375')
# Initialize single string version of switch config from allconf_in
my_string_to_go = ''
tasks = []
# Loop through the switches and create them in the GNS3 project
for sw_val_ctr, sw_val in enumerate(sw_vals_in):
looped_template_id = requests.post(gns3_url_in + 'templates/' + sw_val[7] + '/duplicate', timeout=30).json()['template_id']
# Put request to change the # of interfaces of the temporary template
requests.put(gns3_url_in + 'templates/' + looped_template_id,
json={'adapters': int(sw_val[6])+1}, timeout=30)
# Request to instantiate a new node using the temporary template
newnodeoutput = requests.post(gns3_url_in + 'projects/' + prj_id_in +
'/templates/' + looped_template_id, timeout=30,
json={'x': nodex, 'y': nodey})
# Capture the GNS3 node_id of the virtual-switch we just created
sw_val[8] = newnodeoutput.json()['node_id']
# Request to delete the temporary template
requests.delete(gns3_url_in + 'templates/' + looped_template_id, timeout=30)
# Change the name of the GNS3 node that we just created
requests.put(gns3_url_in + 'projects/' + prj_id_in + '/nodes/' + sw_val[8],
timeout=30, json={'name': sw_val[0]})
# Capture the docker_id of the virtual-switch we just created (container
# re-spawned when we changed its name)
sw_val[9] = requests.get(gns3_url_in + 'projects/' + prj_id_in + '/nodes/' +
sw_val[8], timeout=30).json()['properties']['container_id']
# Copy the modified sw_val objects contents back into sw_vals_in[sw_val_ctr]
sw_vals_in[sw_val_ctr] = sw_val
# Increment x/y coordinates for the *next* switch to be instantiated
nodex += 150
if nodex > 400:
nodex = -800
nodey = nodey + 200
# Tell GNS3 to start the node that represents the current switch
requests.post(gns3_url_in + 'projects/' + prj_id_in + '/nodes/' + sw_val[8]
+ '/start', timeout=30)
# Rebuild the switch-config from its current state as a list of individual
# lines to a single string with newline characters.
my_string_to_go = ''
for i in allconf_in[sw_val_ctr]:
my_string_to_go = my_string_to_go + i + "\n"
# Apply ASCII encoding to the config string
ascii_to_go = my_string_to_go.encode('ascii')
# Turn the ASCII-encoded string into a bytes-like object
bytes_to_go = BytesIO(ascii_to_go)
# file_like_to_go = StringIO(my_string_to_go)
# Turn the switch-config string into a tar archive for later
fh = BytesIO()
with tarfile.open(fileobj=fh, mode='w') as tarch:
info = tarfile.TarInfo('startup-config')
info.size = len(my_string_to_go)
tarch.addfile(info, bytes_to_go)
# Get a docker API connection for the current switch's container
cont1 = d_clnt.containers.get(sw_val[9])
# Retrieve our tar archive from the file-like object ('fh') that we stored it in
uggo = fh.getbuffer()
# Put the startup-config onto / on the virtual-switch
cont1.put_archive('/', data=uggo)
# Move the startup-config from / to /mnt/flash on the virtual switch
cont1.exec_run('mv /startup-config /mnt/flash/')
# Set URL for request to GNS3 to stop the node
url = gns3_url_in+"projects/" + prj_id_in+"/nodes/" + sw_val[8] + "/stop"
# Assign the HTTP post request for async execution
tasks.append(asyncio.ensure_future(gns3_post(session, url, 'post')))
await asyncio.sleep(0.2)
await asyncio.gather(*tasks)
switch_vals_out = sw_vals_in
return switch_vals_out
[docs]
async def gns3_connx_create_async(servername_in: str, gns3_url_in: str, sw_vals_new:
list, connx_in: list, prj_id_in: str):
"""Add nodes to the new GNS3 project and push a copy of the configuration files
to their substrate docker containers.
Parameters
----------
servername_in : str
The name of the aiohttp.ClientSession object used for the connections
gns3_url_in : str
The URL to be posted to the GNS3 server
sw_vals_new : list
List of needed data about the switches to be emulated. GNS3 node IDs were
added by the gns3_nodes_create function.
connx_in : list
List of node-to-node connections that we want to create in the GNS3 project
"""
async with aiohttp.ClientSession() as sesh2:
# Loop through connections_to_make and make the connections between switches
print("Instantiating the connections between switches in the GNS3 \
project (might take a minute):")
cnx_urls = []
cnx_json = []
for n, val in enumerate(connx_in):
a_node_id = []
b_node_id = []
for m, val2 in enumerate(sw_vals_new):
if val[0] == val2[5]:
a_node_id = val2[8]
if val[2] == val2[5]:
b_node_id = val2[8]
a_node_adapter_nbr = str(val[1].split('/')[0].split('ethernet')[1])
b_node_adapter_nbr = str(val[3].split('/')[0].split('ethernet')[1])
# Make a list of URLs for the requests to create all the links
cnx_urls.append('')
cnx_urls[n] = gns3_url_in + 'projects/' + prj_id_in + '/links'
cnx_json.append({})
cnx_json[n] = {'nodes': [{'adapter_number': int(a_node_adapter_nbr),
'node_id': a_node_id, 'port_number': 0},
{'adapter_number': int(b_node_adapter_nbr),
'node_id': b_node_id, 'port_number': 0}]}
# Assign the HTTP post request for async execution
tasks_2 = []
for n, url in enumerate(cnx_urls):
tasks_2.append(asyncio.ensure_future(gns3_post(sesh2, str(url),
'post', jsondata=cnx_json[n])))
await asyncio.sleep(0.2)
responses = await asyncio.gather(*tasks_2)
return responses
[docs]
async def gns3_post(session_in: str, url_in: str, method: str, **kwargs) -> str:
"""Send an async POST request to GNS3 server.
Parameters
----------
session_in : str
The name of the aiohttp.ClientSession object used for the connections
url_in : str
The URL to be posted to the GNS3 server (includes project ID and node ID)
method : str
The HTTP method (get, put, post) to be used in the request
jsondata : str
Optional. Any JSON to be included with the HTTP request
"""
if 'jsondata' in kwargs:
jsondata = kwargs['jsondata']
if method == 'post':
if 'jsondata' in kwargs:
async with session_in.post(url_in, json=jsondata) as response:
await asyncio.sleep(.2)
else:
async with session_in.post(url_in) as response:
await asyncio.sleep(.2)
if method == 'get':
if 'jsondata' in kwargs:
async with session_in.get(url_in, json=jsondata) as response:
await asyncio.sleep(.2)
else:
async with session_in.get(url_in) as response:
await asyncio.sleep(.2)
if method == 'put':
if jsondata:
async with session_in.put(url_in, json=kwargs['jsondata']) as response:
await asyncio.sleep(.2)
else:
async with session_in.put(url_in) as response:
await asyncio.sleep(.2)
return response