Source code for gns3_worker

"""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