From 6a32967e2de49eaee359f29a482e19b3238f5016 Mon Sep 17 00:00:00 2001 From: Marco Capitani Date: Thu, 29 Mar 2018 14:45:58 +0200 Subject: storyboard: add/format docstrings Also: + small docstring fixes in model + docstring fixes in log + log refactoring + other small refactoring + some small documentation fixes --- rumba/storyboard.py | 602 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 444 insertions(+), 158 deletions(-) (limited to 'rumba/storyboard.py') diff --git a/rumba/storyboard.py b/rumba/storyboard.py index 2de21ff..de2bc62 100644 --- a/rumba/storyboard.py +++ b/rumba/storyboard.py @@ -62,7 +62,7 @@ except ImportError: # But we might not care -class SBEntity(object): +class _SBEntity(object): def __init__(self, e_id): self.id = e_id @@ -71,17 +71,37 @@ class SBEntity(object): return type(self).__name__ + '.' + self.id -class Client(SBEntity): +class Client(_SBEntity): + """ + Class representing a client application running in the experiment. + + A list of "client nodes" can be specified: if so, when generating a + random script, this Client will be run only on those nodes. + """ current_id = -1 @classmethod - def get_id(cls): + def _get_id(cls): cls.current_id += 1 return cls.current_id def __init__(self, ap, nodes=None, options=None, shutdown="kill ", c_id=None): + """ + :param ap: the application binary/command to be run + :type ap: `str` + :param nodes: the list of nodes on which the client should be run + :type nodes: `:py:class:`rumba.model.Node` or `list` thereof + :param options: the options to be passed to the binary or command + :type options: `str` + :param shutdown: the command to be run in order to stop the client. + The token "" will be changed into the process' + pid. + :type shutdown: `str` + :param c_id: the ID used to reference to this instance + :type c_id: `str` + """ self.ap = ap e_id = c_id if c_id is not None else self.ap.replace(' ', '_') super(Client, self).__init__(e_id) @@ -94,13 +114,35 @@ class Client(SBEntity): self.shutdown = shutdown def add_node(self, node): + """ + Add a node to this instance's list. + + :param node: the node to add + :type node: `rumba.model.Node` + """ if not isinstance(node, model.Node): raise Exception("A Node is required.") self.nodes.append(node) def process(self, duration, node=None, proc_id=None): + """ + Generates a `.ClientProcess` of this application. + + :param duration: the duration of the process. It will be + passed to the process call in stead of the + token. + :type duration: `float` + :param node: the node on which the process should be run. + If `None`, a random node from this client's list + will be picked. + :type node: `rumba.model.Node` + :param proc_id: the ID used to reference to the generated process + :type proc_id: `str` + :return: the generated process + :rtype: `.ClientProcess` + """ if proc_id is None: - proc_id = "%s_%s" % (self.id, self.get_id()) + proc_id = "%s_%s" % (self.id, self._get_id()) if node is None: if len(self.nodes) == 0: raise ValueError('No nodes for client %s' @@ -108,7 +150,7 @@ class Client(SBEntity): node = random.choice(self.nodes) return ClientProcess( proc_id, - self.ap, + self.id, self.startup, duration, node, @@ -116,18 +158,29 @@ class Client(SBEntity): ) -# Base class for client processes -# -# @ap: Application Process binary -# @duration: The time (in seconds) this process should run -# @start_time: The time at which this process is started. -# @options: Options to pass to the binary -# -class ClientProcess(SBEntity): - def __init__(self, proc_id, ap, startup, duration, +class ClientProcess(_SBEntity): + """Class representing a running client application process on a node""" + def __init__(self, proc_id, ap_id, startup, duration, node, shutdown="kill "): + """ + + :param proc_id: the ID used to identify this instance + :type proc_id: `str` + :param ap_id: the ID of the client app that generated this process + :type ap_id: `str` + :param startup: the full command used to start this process + :type startup: `str` + :param duration: the intended duration of this process. It will also + replace the "" token in the + `startup` parameter + :type duration: `int` od `float` + :param node: the node on which this process runs + :type node: `rumba.model.Node` + :param shutdown: the command used to stop this process + :type shutdown: `str` + """ super(ClientProcess, self).__init__(proc_id) - self.ap = ap + self.ap_id = ap_id self.startup = startup self.duration = duration if duration is not None else -1 self.start_time = None @@ -137,13 +190,14 @@ class ClientProcess(SBEntity): self.shutdown = shutdown def run(self): + """Starts this process""" if self.node is None: - raise Exception('No node specified for client %s' % (self.ap,)) + raise Exception('No node specified for client %s' % (self.ap_id,)) self.start_time = time.time() logger.debug( 'Starting client app %s on node %s with duration %s.', - self.ap, self.node.name, self.duration + self.ap_id, self.node.name, self.duration ) start_cmd = "./startup.sh %s %s" % ( @@ -155,52 +209,32 @@ class ClientProcess(SBEntity): self.pid = self.node.execute_command(start_cmd) except ssh_support.SSHException: logger.warning('Could not start client %s on node %s.', - self.ap, self.node.name) + self.ap_id, self.node.name) logger.debug('Client app %s on node %s got pid %s.', - self.ap, self.node.name, self.pid) + self.ap_id, self.node.name, self.pid) def stop(self): + """Stops this process""" if self.shutdown != "": logger.debug( 'Killing client %s on node %s.', - self.ap, self.node.name + self.ap_id, self.node.name ) try: kill_cmd = self.shutdown.replace('', str(self.pid)) self.node.execute_command(kill_cmd) except ssh_support.SSHException: logger.warn('Could not kill client %s on node %s.', - self.ap, self.node.name) + self.ap_id, self.node.name) else: logger.debug( 'Client %s on node %s has terminated.', - self.ap, self.node.name + self.ap_id, self.node.name ) - def check_and_kill(self): - """Check if the process should keep running, stop it if not, - and return true if and only if it is still running.""" - now = time.time() - if not self.running: - return False - if now - self.start_time >= self.duration: - self.stop() - self.running = False - return False - return True - -# Base class for server programs -# -# @ap: Application Process binary -# @arrival_rate: Average requests/s to be received by this server -# @mean_duration: Average duration of a client connection (in seconds) -# @options: Options to pass to the binary -# @max_clients: Maximum number of clients to serve -# @clients: Client binaries that will use this server -# @nodes: Specific nodes to start this server on -# -class Server(SBEntity): +class Server(_SBEntity): + """Class representing a server app running in the experiment""" current_id = -1 @@ -212,6 +246,33 @@ class Server(SBEntity): def __init__(self, ap, arrival_rate, mean_duration, options=None, max_clients=float('inf'), clients=None, nodes=None, min_duration=2, s_id=None): + """ + + :param ap: the application binary or command which should be run + :type ap: `str` + :param arrival_rate: + :type arrival_rate: `float` + :param mean_duration: the required average lifetime of a client + of this server + :type mean_duration: `float` + :param options: the options to be passed to the binary/command + starting this server + :type options: `str` + :param max_clients: the maximum number of simultaneous clients + which can be served by this application + :type max_clients: `int` + :param clients: the clients applications which will request to + be served by this application + :type clients: `list` of `.Client` + :param nodes: the list of nodes this server application + should be run on + :type nodes: `list` of `rumba.model.Node` + :param min_duration: the minimum lifetime of a client of this + server + :type min_duration: `float` + :param s_id: the ID used to identify this instance + :type s_id: `str` + """ self.ap = ap e_id = s_id if s_id is not None else self.ap.replace(' ', '_') super(Server, self).__init__(e_id) @@ -230,30 +291,61 @@ class Server(SBEntity): self.min_duration = min_duration def add_client(self, client): + """ + Adds a client to this server's list + + :param client: the client to add + :type client: `.Client` + """ self.clients.append(client) def del_client(self, client): + """ + Removes a client from this server's list + + :param client: the client to remove + :type client: `.Client` + """ self.clients.remove(client) def add_node(self, node): + """ + Adds a node to this server's list + + :param node: the node to add + :type node: `rumba.model.Node` + """ self.nodes.append(node) def del_node(self, node): + """ + Removes a node from this server's list + + :param node: the node to remove + :type node: `rumba.model.Node` + """ self.nodes.remove(node) - def get_new_clients(self, interval): + def _get_new_clients(self, interval): """ Returns a list of clients of size appropriate to the server's rate. - The list's size should be a sample from Poisson(arrival_rate) over + The list's size should be a sample ~ Poisson(arrival_rate) over interval seconds. Hence, the average size should be interval * arrival_rate. + + :param interval: the time increment for which new clients should be + generated + :type interval: `float` + + :return: the list of new clients to be started + :rtype: `list` of `(duration, node, proc_id, client)` tuples """ number = _poisson(self.arrival_rate * interval) number = int(min(number, self.max_clients)) return [self._make_process_arguments() for _ in range(number)] - def get_duration(self): + def _get_duration(self): return _exponential(self.actual_parameter) + self.min_duration def _make_process_arguments(self, duration=None, node=None, @@ -261,18 +353,36 @@ class Server(SBEntity): if len(self.clients) == 0: raise Exception("Server %s has empty client list." % (self,)) if duration is None: - duration = self.get_duration() + duration = self._get_duration() if client is None: client = random.choice(self.clients) if node is None: node = random.choice(client.nodes) if proc_id is None: - proc_id = "%s_%s" % (client.ap, client.get_id()) + proc_id = "%s_%s" % (client.ap, client._get_id()) return duration, node, proc_id, client def make_client_process(self, duration=None, node=None, proc_id=None, client=None): - """Returns a client of this server""" + """ + Returns a process of a client application of this server. + + Any parameter left as `None` will be randomly generated + according to the parameters assigned to this server and its + clients. + + :param duration: the lifetime of the process + :type duration: `float` + :param node: the node on which the process should be run + :type node: `rumba.model.Node` + :param proc_id: the ID identifying the returned process + :type proc_id: `str` + :param client: the client of which the returned process + should be an instance + :type client: `.Client` + :return: the process + :rtype: `.ClientProcess` + """ (d, n, p, c) = self._make_process_arguments(duration, node, proc_id, client) return c.process( @@ -282,6 +392,7 @@ class Server(SBEntity): ) def run(self): + """Starts this server""" for node in self.nodes: run_cmd = self.ap + ( (" " + self.options) if self.options is not None else "" @@ -298,6 +409,7 @@ class Server(SBEntity): self.id, node.name) def stop(self): + """Stops this server""" for node, pid in self.pids.items(): logger.debug( 'Killing server %s on node %s.', @@ -317,7 +429,8 @@ class Server(SBEntity): # @servers: App servers available in the network. # Type == Server or Type == List[Tuple[Server, Node]] # -class StoryBoard(SBEntity): +class StoryBoard(_SBEntity): + """Class representing the storyboard of an experiment""" SCRIPT_RESOLUTION = 0.1 EXECUTION_RESOLUTION = 2.5 # in seconds (may be a float) @@ -325,7 +438,19 @@ class StoryBoard(SBEntity): def get_e_id(self): return 'storyboard' - def __init__(self, duration, experiment=None, servers=None, script=None): + def __init__(self, duration, experiment=None, servers=None): + """ + + :param duration: the required duration of the storyboard (s) + :type duration: `float` + :param experiment: the experiment data this storyboard should use + :type experiment: `rumba.model.Experiment` + :param servers: the list of servers this storyboard will use to generate + random events through the + :meth:`.StoryBoard.generate_script` method + :type servers: list of `.Server` or (`.Server`, `rumba.model.Node`) + tuples + """ self.id = 'storyboard' self.experiment = experiment self.duration = duration @@ -347,7 +472,7 @@ class StoryBoard(SBEntity): self._build_nodes_lists() # The following must be last, because it needs the info from # _build_nodes_list - self._script = script if script is not None else _Script(self) + self._script = _Script(self) def _build_nodes_lists(self): """Populates server_nodes and client_nodes lists""" @@ -386,7 +511,8 @@ class StoryBoard(SBEntity): """ Set the storyboard's underlying experiment instance - @param experiment: the experiment instance + :param experiment: the experiment instance + :type experiment: `rumba.model.Experiment` """ if not isinstance(experiment, model.Experiment): raise TypeError('Experiment instance required.') @@ -394,7 +520,13 @@ class StoryBoard(SBEntity): self._build_nodes_lists() def add_server(self, server): - """Register a server node to the sb.""" + """ + Register a server application to the sb for + random event generation. + + :param server: the server application + :type server: `.Server` + """ if self.experiment is None: raise ValueError("Cannot add a server before " "setting the experiment.") @@ -404,11 +536,13 @@ class StoryBoard(SBEntity): def add_server_on_node(self, server, node): """ - Utility method to simultaneously add a server to a sb + Simultaneously add a server to this storyboard and a node to the server. - @param server: the server to be added to the storyboard - @param node: the node upon which the server should run + :param server: the server to be added to the storyboard + :type server: `.Server` + :param node: the node upon which the server should run + :type node: `rumba.model.Node` """ if self.experiment is None: raise ValueError("Cannot add a server before " @@ -420,6 +554,12 @@ class StoryBoard(SBEntity): self._validate_and_add_server(server, node) def del_server(self, server): + """ + Deregister a server application from this storyboard. + + :param server: the server to remove + :type server: `.Server` + """ del self.server_apps[server.id] self._build_nodes_lists() @@ -431,21 +571,25 @@ class StoryBoard(SBEntity): trigger=None, ev_id=None,): """ - Calls a function with the specified triggers and arguments. + Calls a Python function with the specified arguments as soon as + the specified triggers are satisfied. + :param call: the function to run - :type call: function (methods included) + :type call: `callable` + :param args: arguments to pass to the function + :type args: `list` + :param kwargs: keyword arguments to be passed + :type kwargs: `dict` :param c_time: the function will not be called before `c_time` seconds have passed - :type c_time: :py:class:`float` + :type c_time: `float` :param trigger: the function must not be called before the event `trigger` has completed - :type trigger: :py:class:`.Event` or :py:class:`str` + :type trigger: `.Event` or `str` :param ev_id: the ID to assign to the generated event - :type ev_id: :py:class:`str` - :param args: arguments to pass to the function - :param kwargs: keyword arguments to be passed + :type ev_id: `str` :return: the event representing the calling of the function - :rtype: :py:class:`.Event` + :rtype: `.Event` """ if args is None: args = [] @@ -458,12 +602,19 @@ class StoryBoard(SBEntity): def schedule_command(self, t, node, command): """ - Schedules the given command to be run at t seconds from the start. - The commands are run in no particular order, so take care - - @param t: (float) seconds to wait before running the command - @param node: (Node or str) the node on which the command should be run - @param command: (str or list[str]) the command(s) to be run, + Schedules the given command to be run no sooner than t seconds + from the start of the storyboard execution. + + Commands triggering at times very close to each other might be + run in any order. Use the :meth:`.StoryBoard.schedule_action` method + to force a command to run after another event. + + :param t: seconds to wait before running the command + :type t: `float` + :param node: the node on which the command should be run + :type node: `rumba.model.Node` or `str` + :param command: the command(s) to be run + :type command: `str` or `list` of `str` """ if self.experiment is None: raise ValueError("An experiment is needed to schedule commands.") @@ -483,8 +634,10 @@ class StoryBoard(SBEntity): """ Runs a command (or several) on a given node, immediately. - @param node: (Node or str) the node on which the command should be run - @param command: (str or list[str]) the command(s) to be run + :param node: the node on which the command should be run + :type node: `rumba.model.Node` or `str` + :param command: the command(s) to be run + :type command: `str` or `list` of `str` """ if self.experiment is None: raise ValueError("Experiment needed to run commands.") @@ -499,11 +652,13 @@ class StoryBoard(SBEntity): def add_event(self, event): """ - Add an event to this script, provided either as an Event - instance or as a string as read from a .rsb script. + Adds an event to this script. + + The event acan be passed either as an `.Event` + instance or as a string as read as from a .rsb script. :param event: the event to add - :type event: (Event or str) + :type event: `.Event` or `str` """ self._script.add_event(event) @@ -512,7 +667,7 @@ class StoryBoard(SBEntity): Remove an event from this storyboard :param event: the event (or id thereof) to remove - :type event: (Event or str) + :type event: `.Event` or `str` """ self._script.del_event(event) @@ -528,19 +683,25 @@ class StoryBoard(SBEntity): Except for the server, if a parameter is not specified, it will be randomly generated according to the server - parameters (mean duration, client apps and their nodes) - - @param server: the server of which one client should be run - @param duration: the duration of the client process - @param node: (Node or str) the node on which the client should be run - @param proc_id: the entity ID to use for the process - @param callback: callable or list thereof to be run + parameters (mean duration, registered client apps + and their nodes) + + :param server: the server of which one client should be run + :type server: `.Server` + :param duration: the duration of the client process + :type duration: `float` + :param node: the node on which the client should be run + :type node: `rumba.model.Node` or `str` + :param proc_id: the ID identifying the generated process + :type proc_id: `str` + :param callback: callable or list thereof to be run after client termination + :type callback: `callable` or `list` of `callable` """ if isinstance(server, str): server = self.server_apps[server] if duration is None: - duration = server.get_duration() + duration = server._get_duration() client = random.choice(server.clients) self.run_client(client, duration, node, proc_id, callback) @@ -556,12 +717,17 @@ class StoryBoard(SBEntity): If the node parameter is not given, it will be chosen at random among the client default nodes. - @param client: the client which should be run - @param duration: the duration of the client process - @param node: (Node or str) the node on which the client should be run - @param proc_id: the entity ID to use for the process - @param callback: callable or list thereof to be run + :param client: the client which should be run + :type client: `.Client` + :param duration: the duration of the client process + :type duration: `float` + :param node: the node on which the client should be run + :type node: `rumba.model.Node` or `str` + :param proc_id: the entity ID to use for the process + :type proc_id: `str` + :param callback: callable or list thereof to be run after client termination + :type callback: `callable` or `list` of `callable` """ if isinstance(client, str): client = self.client_apps[client] @@ -596,14 +762,18 @@ class StoryBoard(SBEntity): to the server parameters (client apps and their nodes). Note that this method, as opposed to - :meth:`rumba.storyboard.run_client_of`, will not generate an event - to stop the client after the duration is expired. In most cases, - :meth:`rumba.storyboard.run_client_of` is the way to go. - - @param server: the server of which one client should be run - @param duration: the duration of the client process - @param node: the node on which the client should be run - @param proc_id: the entity ID to use for the process + :meth:`.StoryBoard.run_client_of`, will not automatically generate + an event stopping the client after the duration is expired. + In most cases, :meth:`.StoryBoard.run_client_of` is the way to go. + + :param server: the server of which one client should be run + :type server: `.Server` + :param duration: the duration of the client process + :type duration: `float` + :param node: the node on which the client should be run + :type node: `rumba.model.Node` or `str` + :param proc_id: the ID identifying the generated process + :type proc_id: `str` """ if isinstance(server, str): server = self.server_apps[server] @@ -618,14 +788,18 @@ class StoryBoard(SBEntity): among the client default nodes. Note that this method, as opposed to - :meth:`rumba.storyboard.run_client`, will not generate an event - to stop the client after the duration is expired. In most cases, - :meth:`rumba.storyboard.run_client` is the way to go. - - @param client: the client which should be run - @param duration: the duration of the client process - @param node: the node on which the client should be run - @param proc_id: the entity ID to use for the process + :meth:`.StoryBoard.run_client`, will not automatically generate + an event stopping the client after the duration is expired. + In most cases, :meth:`.StoryBoard.run_client` is the way to go. + + :param client: the client which should be run + :type client: `.Client` + :param duration: the duration of the client process + :type duration: `float` + :param node: the node on which the client should be run + :type node: `rumba.model.Node` or `str` + :param proc_id: the entity ID to use for the process + :type proc_id: `str` """ if isinstance(client, str): client = self.client_apps[client] @@ -636,23 +810,31 @@ class StoryBoard(SBEntity): process.run() def kill_process(self, proc_id): + """ + Stops the `.ClientProcess` with the specified ID. + + :param proc_id: the ID of the process to kill + :type proc_id: `str` + """ process = self.process_dict.get(proc_id, None) if process is None: raise ValueError('No process named %s' % (proc_id,)) process.stop() del self.process_dict[proc_id] - def periodic_check(self, t): + def _periodic_check(self, t): self._script.check_for_ready_ev(t) self._script.run_ready() def generate_script(self, clean=True): """ Randomly generate a script for this experiment based on the - parameters provided to the instances of servers, nodes and clients. + `.Server` instances registered to this storyboard, + their nodes, thier clients and the nodes of their clients. - @param clean: if True, discard the current script before + :param clean: if `True`, discard the current script before generating a new one. + :type clean: `bool` """ if self.experiment is None: raise ValueError('Cannot generate script without an experiment') @@ -666,7 +848,7 @@ class StoryBoard(SBEntity): last_marker += 1 logger.debug('Passed the %s seconds mark', last_marker*marker) for server in self.server_apps.values(): - c_l = server.get_new_clients(self.SCRIPT_RESOLUTION) + c_l = server._get_new_clients(self.SCRIPT_RESOLUTION) for d, n, p, c in c_l: if d > self.duration - t: # would outlast the experiment continue @@ -692,6 +874,7 @@ class StoryBoard(SBEntity): return start_event def start(self): + """Start the storyboard execution.""" if self.experiment is None: raise ValueError("Cannot run sb with no experiment.") if self._script is None: @@ -717,13 +900,13 @@ class StoryBoard(SBEntity): server.run() res = self.SCRIPT_RESOLUTION # for brevity while self.cur_time < self.duration: - self.periodic_check(self.cur_time) + self._periodic_check(self.cur_time) next_breakpoint = math.ceil(self.cur_time / res) * res delta = next_breakpoint - self.cur_time if delta > 0: # just in case time.sleep(delta) self.cur_time = float(time.time() - self.start_time) - self.periodic_check(self.cur_time) + self._periodic_check(self.cur_time) # Do things that were scheduled # in the last seconds # of the StoryBoard @@ -734,6 +917,15 @@ class StoryBoard(SBEntity): server.stop() def fetch_logs(self, local_dir=None): + """ + Fetch all server application and client application logs from + the different nodes, and put them into `local_dir` + + :param local_dir: the local directory in which the logs should + be stored. If `None`, `/tmp/rumba/` + will be used + :type local_dir: `str` + """ if local_dir is None: local_dir = self.experiment.log_dir if not os.path.isdir(local_dir): @@ -750,11 +942,25 @@ class StoryBoard(SBEntity): logger.debug('Log list is:\n%s', logs_list) node.fetch_files(logs_list, dst_dir) - def set_link_state(self, t, dif, state): + def schedule_link_state(self, t, dif, state): + """ + Schedules a link's (`rumba.model.ShimEthDIF`) state to go + up or down at the specified time. + + :param t: the time in the storyboard at which the state + change should happen + :type t: `float` + :param dif: the DIF which should be reconfigured + :type dif: `rumba.model.ShimEthDIF` + :param state: the desired state + :type state: `str` -- either `up` or `down` + """ if self.experiment is None: raise ValueError("An experiment is needed to schedule commands.") if not isinstance(dif, model.ShimEthDIF): - raise Exception("Not a Shim Ethernet DIF.") + raise ValueError("Not a Shim Ethernet DIF.") + if state not in ['up', 'down']: + raise ValueError('Only possible states are "up" and "down"') if self._script is None: self._script = _Script(self) @@ -763,13 +969,46 @@ class StoryBoard(SBEntity): action = functools.partial(node.set_link_state, dif, state) self._script.add_event(Event(action, ev_time=t)) - def set_link_up(self, t, dif): - self.set_link_state(t, dif, 'up') + def schedule_link_up(self, t, dif): + """ + Schedules a link's (`rumba.model.ShimEthDIF`) state to go + up at the specified time. + + :param t: the time in the storyboard at which the state + change should happen + :type t: `float` + :param dif: the DIF which should be reconfigured + :type dif: `rumba.model.ShimEthDIF` + """ + self.schedule_link_state(t, dif, 'up') + + def schedule_link_down(self, t, dif): + """ + Schedules a link's (`rumba.model.ShimEthDIF`) state to go + down at the specified time. + + :param t: the time in the storyboard at which the state + change should happen + :type t: `float` + :param dif: the DIF which should be reconfigured + :type dif: `rumba.model.ShimEthDIF` + """ + self.schedule_link_state(t, dif, 'down') + + def schedule_node_state(self, t, node, state): + """ + Schedules a node's state to go up or down at the specified time. - def set_link_down(self, t, dif): - self.set_link_state(t, dif, 'down') + When a node is down all of its links are set to `down`. - def set_node_state(self, t, node, state): + :param t: the time in the storyboard at which the state + change should happen + :type t: `float` + :param node: the node which should be reconfigured + :type node: `rumba.model.Node` + :param state: the desired state + :type state: `str` -- either `up` or `down` + """ if self.experiment is None: raise ValueError("An experiment is needed to schedule commands.") @@ -779,29 +1018,54 @@ class StoryBoard(SBEntity): for dif in node.difs: if not isinstance(dif, model.ShimEthDIF): continue - action = functools.partial(node.set_link_state, dif, state) + action = functools.partial(node.schedule_link_state, dif, state) self._script.add_event(Event(action, ev_time=t)) - def set_node_up(self, t, node): - self.set_node_state(t, node, 'up') + def schedule_node_up(self, t, node): + """ + Schedules a node's state to go up at the specified time. - def set_node_down(self, t, node): - self.set_node_state(t, node, 'down') + :param t: the time in the storyboard at which the state + change should happen + :type t: `float` + :param node: the node which should be reconfigured + :type node: `rumba.model.Node` + """ + self.schedule_node_state(t, node, 'up') + + def schedule_node_down(self, t, node): + """ + Schedules a node's state to go down at the specified time. + + When a node is down all of its links are set to `down`. + + :param t: the time in the storyboard at which the state + change should happen + :type t: `float` + :param node: the node which should be reconfigured + :type node: `rumba.model.Node` + """ + self.schedule_node_state(t, node, 'down') def write_script(self, buffer): """ - Writes the script on a string buffer, at the current position + Writes the script on a (string-oriented) buffer, + at the current position - @param buffer: a string buffer. + :param buffer: a string buffer. + :type buffer: string-oriented `file-like` object """ self._script.write(buffer) def write_script_to_file(self, filename, clean=True): """ - Writes the script to a file, overwriting content. + Writes the script to a file. - @param filename: the name of the destination file - @param clean: if True, current file contents will be overwritten. + :param filename: the name of the destination file + :type filename: `str` + :param clean: if True, current file's contents will be overwritten. + If False, the script will be appended to the file. + :type clean: `bool` """ mode = 'w' if not clean: @@ -811,9 +1075,10 @@ class StoryBoard(SBEntity): def write_script_string(self): """ - Writes the script into a string and returns it. + Writes the script as a string and returns it. - @return: the script as a string. + :return: the script as a string. + :rtype: `str` """ s = StringIO() self.write_script(s) @@ -823,8 +1088,10 @@ class StoryBoard(SBEntity): """ Reads a script from a buffer, at the current position. - @param buffer: the buffer to read from. - @param clean: if True, discard the current script before reading. + :param buffer: the buffer to read from. + :type buffer: string-oriented `file-like` object + :param clean: if True, discard the current script before reading. + :type clean: `bool` """ if clean: self._script = _Script(self) @@ -834,8 +1101,10 @@ class StoryBoard(SBEntity): """ Reads a script from a file. - @param filename: the file to read from. - @param clean: if True, discard the current script before reading. + :param filename: the file to read from. + :type filename: `str` + :param clean: if True, discard the current script before reading. + :type clean: `bool` """ if clean: self._script = _Script(self) @@ -844,10 +1113,12 @@ class StoryBoard(SBEntity): def parse_script_string(self, string, clean=True): """ - Reads a script from a string. + Reads a string as a script. - @param string: the string to read from. - @param clean: if True, discard the current script before reading. + :param string: the string to read from. + :type string: `str` + :param clean: if True, discard the current script before reading. + :type clean: `bool` """ if clean: self._script = _Script(self) @@ -856,13 +1127,17 @@ class StoryBoard(SBEntity): def capture_traffic(self, start, end, node, dif): """ - Captures the traffic of an interface on a certain node. - - :param start: The time to start capturing. - :param end: The time to stop capturing. - :param node: The node to capture on. - :param dif: The Shim Ethernet DIF of the node, Rumba - automatically resolves the correct interface. + Captures the traffic of an interface on a node. + + :param start: the time to start capturing. + :type start: `float` + :param end: the time to stop capturing. + :type end: `float` + :param node: the node to capture on. + :type node: `rumba.model.Node` + :param dif: the node's Shim Ethernet DIF whose interface + will be used for the capture. + :type dif: `rumba.model.ShimEthDIF` """ for ipcp in dif.ipcps: if ipcp.node is not node: @@ -894,6 +1169,7 @@ class StoryBoard(SBEntity): class Event(object): + """Class representing an event in a `.StoryBoard`""" cur_id = -1 @@ -904,11 +1180,15 @@ class Event(object): def __init__(self, action, ev_id=None, ev_time=None, trigger=None): """ - @param ev_id: (str) id of the event - @param action: (any SBEntity method) action to undertake - when event is activated - @param ev_time: (float) seconds to wait before running the event - @param trigger: (Event) Event which must complete before this runs + :param ev_id: ID of the event + :type ev_id: `str` + :param action: action to undertake when event is activated + :type action: nullary `callable` + :param ev_time: seconds to wait before running the event + :type ev_time: `float` + :param trigger: Event which must complete before + this event runs + :type trigger: `.Event` """ self.id = ev_id if ev_id is not None else self.generate_id() if ev_time is None and trigger is None: @@ -957,6 +1237,11 @@ class Event(object): @property def failed(self): + """ + + :return: True if this event's execution failed + :rtype: `bool` + """ return self.exception is not None def pre_exec(self): # hook to be overridden @@ -971,9 +1256,7 @@ class Event(object): raise ValueError('Event %s has already ran' % self.id) def run(self): - """ - Run this event's action - """ + """Run this event's action""" self._start() try: self.action() @@ -983,9 +1266,12 @@ class Event(object): def check(self, cur_time): """ - Check if this event can be run. - @param cur_time: (float) current time - @return: True if the preconditions are satisfied, False otherwise. + Check if this event can be run, i.e. it's prerequisites are satisfied. + + :param cur_time: current elapsed time from storyboard's start. + :type cur_time: `float` + :return: True if the preconditions are satisfied, False otherwise. + :rtype: `bool` """ return \ (self.time is None or cur_time > self.time) \ -- cgit v1.2.3