aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarco Capitani <m.capitani@nextworks.it>2018-05-09 12:06:57 +0200
committerMarco Capitani <m.capitani@nextworks.it>2018-05-09 12:21:20 +0200
commit19d5a189b1db5ff8bd4a2f8eddad9d2e3f056cd5 (patch)
tree7006242f7fd9120b81eec0bffbe9a1aa93098cec
parente7e81f30d8a6054e142cf7c77459532247a644d2 (diff)
downloadrumba-19d5a189b1db5ff8bd4a2f8eddad9d2e3f056cd5.tar.gz
rumba-19d5a189b1db5ff8bd4a2f8eddad9d2e3f056cd5.zip
storyboard: fix Event calling get_e_id always
This fixes the behaviour of the Event class which would always call get_e_id and fail if the action is a pure function or not the method of a SBEntity. Now it will simply output a warning if the event is written (because it won't be readable as-is) On top of that, all DIFs are now scriptable, and some small fixes and cleanups.
-rw-r--r--rumba/elements/topology.py4
-rw-r--r--rumba/storyboard.py173
2 files changed, 124 insertions, 53 deletions
diff --git a/rumba/elements/topology.py b/rumba/elements/topology.py
index 3054782..c47d541 100644
--- a/rumba/elements/topology.py
+++ b/rumba/elements/topology.py
@@ -35,6 +35,10 @@ class DIF(object):
"""
Base class for DIFs.
"""
+
+ def get_e_id(self):
+ return "DIF." + self.name
+
def __init__(self, name, members=None):
"""
:param name: Name of the DIF.
diff --git a/rumba/storyboard.py b/rumba/storyboard.py
index 922b200..6b56a92 100644
--- a/rumba/storyboard.py
+++ b/rumba/storyboard.py
@@ -235,8 +235,8 @@ class ClientProcess(_SBEntity):
kill_cmd = self.shutdown.replace('<pid>', 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_id, self.node.name)
+ logger.warning('Could not kill client %s on node %s.',
+ self.ap_id, self.node.name)
else:
logger.debug(
'Client %s on node %s has terminated.',
@@ -421,8 +421,8 @@ class Server(_SBEntity):
try:
self.pids[node] = node.execute_command(cmd_2, as_root=self.as_root)
except ssh_support.SSHException:
- logger.warn('Could not start server %s on node %s.',
- self.id, node.name)
+ logger.warning('Could not start server %s on node %s.',
+ self.id, node.name)
def stop(self):
"""Stops this server"""
@@ -434,8 +434,8 @@ class Server(_SBEntity):
try:
node.execute_command("kill %s" % pid)
except ssh_support.SSHException:
- logger.warn('Could not kill server %s on node %s.',
- self.id, node.name)
+ logger.warning('Could not kill server %s on node %s.',
+ self.id, node.name)
# Base class for ARCFIRE storyboards
@@ -485,6 +485,7 @@ class StoryBoard(_SBEntity):
self.commands_list = {}
self.node_map = {}
self.shims = {}
+ self.difs = {}
self._build_nodes_lists()
# The following must be last, because it needs the info from
# _build_nodes_list
@@ -502,6 +503,7 @@ class StoryBoard(_SBEntity):
for node in self.experiment.nodes:
self.node_map[node.name] = node
for dif in self.experiment.dif_ordering:
+ self.difs[dif.name] = dif
if isinstance(dif, model.ShimEthDIF):
self.shims[dif.name] = dif
@@ -585,7 +587,7 @@ class StoryBoard(_SBEntity):
kwargs=None,
c_time=None,
trigger=None,
- ev_id=None,):
+ ev_id=None):
"""
Calls a Python function with the specified arguments as soon as
the specified triggers are satisfied.
@@ -1034,7 +1036,7 @@ class StoryBoard(_SBEntity):
for dif in node.difs:
if not isinstance(dif, model.ShimEthDIF):
continue
- action = functools.partial(node.schedule_link_state, dif, state)
+ action = functools.partial(node.set_link_state, dif, state)
self._script.add_event(Event(action, ev_time=t))
def schedule_node_up(self, t, node):
@@ -1166,8 +1168,13 @@ class StoryBoard(_SBEntity):
node.has_tcpdump = True
# Create random string
- pcap_file = node.name + '_' + dif.name + '_' + \
- str(uuid.uuid4())[0:4] + ".pcap"
+ pcap_file = (
+ node.name +
+ '_' +
+ dif.name +
+ '_' +
+ str(uuid.uuid4())[0:4] + ".pcap"
+ )
tcpd_client = Client(ap="tcpdump", options="-i %s -w %s" \
% (ipcp.ifname, pcap_file))\
@@ -1216,17 +1223,17 @@ class Event(object):
self.trigger = trigger.id if trigger is not None else None
self.exception = None
self.done = False
- self._repr = "%(prefix)s &%(label)s | %(entity)s %(method)s" % {
- 'prefix': self._prefix_repr(),
- 'label': self.id,
- 'entity': '$' + self.action.func.__self__.get_e_id(),
- 'method': self._action_repr()
- }
+ self._repr = None
def _action_repr(self):
if isinstance(self.action, functools.partial):
name = self.action.func.__name__
- args = ' '.join(self._action_arg_repr(x) for x in self.action.args)
+ arg_list = [self._action_arg_repr(x) for x in self.action.args]
+ arg_list += [
+ k + '=' + self._action_arg_repr(v)
+ for k, v in self.action.keywords.items()
+ ]
+ args = ' '.join(arg_list)
else:
name = self.action.__name__
args = ''
@@ -1307,7 +1314,39 @@ class Event(object):
def __str__(self):
return self.id
+ def __recover_e_name(self):
+ if isinstance(self.action, functools.partial):
+ func = self.action.func
+ else:
+ func = self.action
+
+ # Recover entity name if possible
+ entity = getattr(func, '__self__', None)
+ if entity is None: # Not a method, skip
+ e_name = None
+ else:
+ try:
+ e_name = entity.get_e_id() # SB entity
+ except AttributeError:
+ # last ditch effort
+ e_name = getattr(entity, 'name', None)
+ if e_name is not None:
+ # Prepend token used in .rsb
+ e_name = '$' + e_name
+ return e_name
+
def __repr__(self):
+ if self._repr is None:
+ e_name = self.__recover_e_name()
+ if e_name is None:
+ logger.warning("Event %s has no valid entity name, "
+ "cannot serialize correctly." % (self.id,))
+ self._repr = "%(prefix)s &%(label)s | %(entity)s %(method)s" % {
+ 'prefix': self._prefix_repr(),
+ 'label': self.id,
+ 'entity': e_name,
+ 'method': self._action_repr()
+ }
return self._repr
@@ -1354,7 +1393,8 @@ class _Script(object):
'Server': self._servers,
'Client': self._clients,
'ClientProcess': self._processes,
- 'ShimEthDIF': self._shims
+ 'ShimEthDIF': self._shims,
+ 'DIF': self._storyboard.difs
}
def add_process(self, process):
@@ -1428,45 +1468,72 @@ class _Script(object):
raise ValueError('No method called "%s" for entity %s.'
% (method_n, entity.get_e_id()))
args_l = action_l[1:]
- args = self._parse_action_arguments(args_l)
- return functools.partial(method, *args)
- # TODO maybe some argument checking?
+ args, kwargs = self._parse_action_arguments(args_l)
+ return functools.partial(method, *args, **kwargs)
+ # TODO maybe some introspection for argument checking?
- def _parse_action_arguments(self, args_l):
- args = []
- quotes_part = None
- while len(args_l) > 0:
+ @staticmethod
+ def _pop_str_arg(args_l):
+ part = None
+ must_close = False
+ while not must_close:
arg_s = args_l.pop(0)
- if quotes_part is not None:
+ if part is not None: # We're inside a string
if arg_s.endswith("'"):
- arg_s = arg_s[:-1] # Strip final '
- quotes_part += ' ' + arg_s
- args.append(quotes_part)
- quotes_part = None
- elif arg_s.startswith("'"):
- arg_s = arg_s[1:] # Strip initial '
+ arg_s = arg_s[:-1] # Strip final ' char
+ must_close = True
+ part += ' ' + arg_s
+ elif arg_s.startswith("'"): # Starting a string
+ arg_s = arg_s[1:] # Strip initial ' char
if arg_s.endswith("'"):
- arg_s = arg_s[:-1] # Strip final '
- args.append(arg_s)
- else:
- quotes_part = arg_s
- else: # Not a string
- if arg_s.startswith('$'):
- args.append(self._resolve_entity(arg_s[1:]))
- continue
- try: # No string, no entity, it must be a number
- args.append(int(arg_s))
- continue
- except ValueError:
- pass # Try to parse it as float
- try:
- args.append(float(arg_s))
- except ValueError:
- raise ValueError('Syntax error: %s is not a valid value. '
- 'If it is supposed to be a string, '
- 'enclose it in single quotes.'
- % (arg_s,))
- return args
+ arg_s = arg_s[:-1] # Strip final ' char
+ must_close = True
+ part = arg_s
+ else: # Should not happen
+ raise ValueError('Non string arg %s parsed as string'
+ % (arg_s,))
+ return part
+
+ def _pop_non_str_arg(self, args_l):
+ arg_s = args_l.pop(0)
+ if arg_s.startswith('$'):
+ return self._resolve_entity(arg_s[1:])
+ if '=' in arg_s: # kw arg
+ split = arg_s.split('=')
+ k = split[0]
+ v = ''.join(split[1:]) # e.g. v = equals containing str
+ args_l.insert(0, v) # treat the value as one common arg
+ parsed_v = self._pop_one_arg(args_l)
+ return k, parsed_v
+ try: # No string, no entity, no kw, it must be a number
+ return int(arg_s)
+ except ValueError:
+ pass # No int, maybe float?
+ try:
+ return float(arg_s)
+ except ValueError: # Out of options
+ raise ValueError('Syntax error: %s is not a valid value. '
+ 'If it is supposed to be a string, '
+ 'enclose it in single quotes.'
+ % (arg_s,))
+
+ def _pop_one_arg(self, args_l):
+ if args_l[0].startswith("'"):
+ return self._pop_str_arg(args_l)
+ else:
+ return self._pop_non_str_arg(args_l)
+
+ def _parse_action_arguments(self, args_l):
+ args = []
+ kwargs = {}
+ while len(args_l) > 0:
+ next_arg = self._pop_one_arg(args_l)
+ if isinstance(next_arg, tuple):
+ k, v = next_arg
+ kwargs[k] = v
+ else:
+ args.append(next_arg)
+ return args, kwargs
def _parse_prefix(self, prefix):
prefix = prefix.strip().split('&')