Source code for start

"""
Start Function
==============


This module is run upon login from a student or teacher account.
It allows the user to select the scenario they are taking part in and provides them with instructions.

"""

import os
import importlib
import argparse
import sys

import random

from mininet.log import setLogLevel

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from typing import List, Type
    from scenarios import Scenario


def _choice_from_list(l, name='item'):
    """
    Helper Function.
    Displays a list of options to the user and validates their choice as an integer within the bounds of the array.

    :param l: Items to be chosen from
    :type l: list(str)
    :param name: String to be shown to the user to describe the item they're choosing
    :return: The choice as an index of l
    """
    # Loop until a valid choice is made
    while True:
        # Print list of containers in l "[0] Container_name"
        for idx, container in enumerate(l):
            print("[%i] %s" % (idx, container))
        # Get user's choice
        try:
            choice = raw_input("Select a %s: " % name)
            choice = int(choice)
        except ValueError:
            # The conversion of choice to int has failed
            print("*** Please enter a valid number.")
            continue
        if choice < 0 or choice >= len(l):
            # The choice was outside the range
            print("*** Please enter a number in the range 0-%d." % (len(l) - 1))
            continue
        # The choice was valid, return it
        return choice


[docs]def get_args(): """ Checks command line arguments to see if we're a student, teacher, or developer. Example: >>> sys.argv[1] = 'teacher' >>> get_args()['teacher'] True >>> get_args()['developer'] False :rtype: Dict :return: Returns a dictionary with the current directory, our teacher status, and our student status. """ isTeacher = False isDeveloer = False # Use argparse library to parse arguments and print usage/help parser = argparse.ArgumentParser() parser.add_argument('account', help='The user account to run as (student/teacher).', choices=['student', 'teacher'], nargs='?') # Sets optional args = parser.parse_args() if args.account: # If were running as teacher, store it if args.account == 'teacher': isTeacher = True # If no arguments were passed then we're not a student or a teacher # give the choice to be any else: while True: choice = raw_input('\n'.join(["[0] student", "[1] teacher", "[2] developer", "Select a user type:"])) try: choice = int(choice) except ValueError: # The conversion of choice to int has failed print("*** Please enter a valid number.") continue if choice < 0 or choice > 2: # The choice was outside the range print("*** Please enter a number in the range 0-2.") continue # The choice was valid, move on break if choice == 1: isTeacher = True elif choice == 2: isDeveloer = True return { 'directory': os.path.dirname(os.path.realpath(__file__)), # Get current directory 'teacher': isTeacher, 'developer': isDeveloer }
[docs]def containers(args): """ Allows running the 'example' function in each container for testing purposes :param args: Arguments from the 'get_args' function in this module. TODO Add filter for containers that contain an 'example' function, currently choosing a container without one would cause an uncaught error. [Priority=low] """ # Get every container in the container folder containerdir = args['directory'] + "/container" choices = list(x for x in os.listdir(containerdir) if not os.path.isfile(os.path.join(containerdir, x))) # Get the user's chosen container index = _choice_from_list(choices, 'container') # Import and run the container example importlib.import_module("container.%s.example" % choices[index])
[docs]def get_scenarios(directory): # type: (str) -> List[Type[Scenario]] """ Gets a list of enabled scenarios in the given directory and returns them. Scenarios are listed by their 'weight', a value defined in their class used for ordering. Example: >>> print(get_scenarios('/vagrant/scenarios/')[0].name) Host Scanning :param directory: Directory to look for scenarios in, usually '$ProjectRoot/scenarios/' :return: List[Scenario] """ # Only get scenarios that: # end in .py # don't begin with '__', these for are internal usage such as __init__ choices = list(x[:-3] for x in os.listdir(directory) if x[-3:] == ".py" and x[:2] != "__") # Import the scenarios and store them choices = [importlib.import_module("scenarios.%s" % c) for c in choices] # Only include the scenarios that have an 'Import' class choices = [c.Import for c in choices if hasattr(c, 'Import')] # Check the choices for enabled flag, if not enabled remove them choices = [scenario for scenario in choices if hasattr(scenario, 'enabled') and scenario.enabled] # Order choices by their given weights choices.sort(key=lambda x: x.weight if hasattr(x, 'weight') else sys.maxint) return choices
[docs]def test_scenarios(args): """ Runs every enables scenario with the seed 1-5 for testing purposes :param args: Arguments from the 'get_args' function in this module. TODO Could generate random seeds for this? [Priority=low] """ choices = get_scenarios(args['directory'] + "/scenarios") setLogLevel("error") for seed in range(5): for scenario in choices: print('*** %s SEED:%s' % (scenario.name, str(seed))) scenario(teacher=True, seed=str(seed)).run()
[docs]def run_scenario(args): """ Allows a student to select a single scenario to take part in. :param args: Arguments from the 'get_args' function in this module. """ # Get scenario list choices = get_scenarios(args['directory'] + "/scenarios") # Get the user's chosen scenario index = _choice_from_list([scenario.name for scenario in choices], 'scenario') chosen_scenario = choices[index] # Take in student ID seed = raw_input("Enter your ID: ") # Create a scenario with ID as seed scenario = chosen_scenario(teacher=False, developer=args['developer'], seed=seed) # Execute scenario scenario.run() # Reseed the random number generator so the password stays the FTP same between tasks random.seed(seed) # Host task on FTP scenario.run_ftp()
[docs]def batch_scenario(args): """ Allows a teacher to select a scenario with several student IDs at a time. :param args: Arguments from the 'get_args' function in this module. """ # Get scenario list choices = get_scenarios(args['directory'] + "/scenarios") # Get the user's chosen scenario index = _choice_from_list([scenario.name for scenario in choices], 'scenario') chosen_scenario = choices[index] # Take in student IDs seed = raw_input("Enter Student IDs seperated by spaces: ").split(" ") # Create and execute scenarios for each ID scenario = None for s in seed: scenario = chosen_scenario(teacher=True, developer=args['developer'], seed=s) scenario.run() # Reseed to a set value random.seed("") # Host answers for all generated scenarios on ftp scenario.run_ftp()
[docs]def cleanup(): """ Clean all docker systems to reduce VM size """ os.system("docker system prune -a --volumes")
[docs]def main(): """Run if we're the main file""" args = get_args() # Create list of options to be presented to the user # List contains text to be shown, and function to be executed if chosen options = [] # Give testing options to devs if args['developer']: options.append(("Test All Scenarios", test_scenarios)) options.append(("Container Examples", containers)) # Give single task options to students if not args['teacher']: options.append(("Scenarios", run_scenario)) # Give batch task options to teachers else: options.append(("Scenarios", batch_scenario)) options.append(("Clean space", cleanup)) # Print choices for i in range(0, len(options)): print("[%s] %s" % (i, options[i][0])) # Get choice and execute options[input("Select a category:")][1](args)
if __name__ == "__main__": main()