public_interface.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 #
3 # License: BSD
4 # https://raw.github.com/robotics-in-concert/rocon_multimaster/license/LICENSE
5 #
6 
7 ##############################################################################
8 # Imports
9 ##############################################################################
10 
11 import copy
12 import re
13 # Delete this once we upgrade (hopefully anything after precise)
14 # Refer to https://github.com/robotics-in-concert/rocon_multimaster/issues/248
15 import threading
16 
17 import rospy
18 from gateway_msgs.msg import Rule
19 
20 from . import utils
21 
22 ##############################################################################
23 # Functions
24 ##############################################################################
25 
26 
27 def publicRuleExists(public_rule, public_rules):
28  '''
29  Checks that the public rule doesn't already exist in the list of public
30  rules (which can represent the public interface or the rules themselves).
31  We only need to compare the name/node as they uniquely identify the
32  name/regex
33 
34  @param public_rule : the rule to search for
35  @type Rule
36 
37  @param public_rules : list of Rule (public, watchlist or blacklist)
38  @type list : list of Rule objects
39 
40  @return True if the public rule exists, False otherwise
41  @rtype bool
42  '''
43  for rule in public_rules:
44  if rule.name == public_rule.name and \
45  rule.node == public_rule.node:
46  return True
47  return False
48 
49 ##############################################################################
50 # Public Interface
51 ##############################################################################
52 
53 
54 class PublicInterface(object):
55 
56  '''
57  The public interface is the set of rules
58  (pubs/subs/services/actions) that are exposed and made available for
59  freely sharing with a multimaster system.
60 
61  It consists of:
62  * list of currently available rules to be shared
63  * list of rules and filters that will be watched
64  and shared if they become available
65  '''
66 
67  def __init__(self, default_rule_blacklist, default_rules):
68  '''
69  Initialises the public interface
70 
71  @param default_rule_blacklist : connection type keyed dictionary of rules
72  @type str keyed dictionary of gateway_msgs.msg.Rule[]
73 
74  @param default_rules : connection type keyed dictionary of rules
75  @type str keyed dictionary of gateway_msgs.msg.Rule[]
76  '''
77  # List of rules to be monitored and (un)advertised as they
78  # become (un)available
79  self.watchlist = utils.create_empty_connection_type_dictionary()
80 
81  # Default rules that cannot be advertised - used in AdvertiseAll mode
82  self._default_blacklist = default_rule_blacklist
83 
84  # Default + custom blacklist - used in AdvertiseAll mode
86 
87  # list Rules currently being advertised (gateway_msgs.Rule)
88  # should we store utils.Connections instead?
89  self.public = utils.create_empty_connection_type_dictionary()
90 
91  self.advertise_all_enabled = False
92 
93  self.lock = threading.Lock()
94 
95  # Load up static rules.
96  for connection_type in utils.connection_types:
97  for rule in default_rules[connection_type]:
98  self.add_rule(rule)
99 
100  ##########################################################################
101  # Public Interfaces
102  ##########################################################################
103 
104  def add_rule(self, rule):
105  '''
106  Watch for a new public rule, as described for by the incoming message.
107 
108  @param rule : a rule msg from the advertise call
109  @type Rule
110 
111  @return the rule if added, or None if the rule exists already
112  @rtype Rule || None
113  '''
114  result = None
115  self.lock.acquire()
116  if not publicRuleExists(rule, self.watchlist[rule.type]):
117  self.watchlist[rule.type].append(rule)
118  result = rule
119  self.lock.release()
120  rospy.loginfo("Gateway : adding rule to public watchlist %s" % utils.format_rule(rule))
121  return result
122 
123  def remove_rule(self, rule):
124  '''
125  Attempt to remove a watchlist rule from the public interface. Be a
126  bit careful looking for a rule to remove, depending on the node name,
127  which can be set (exact rule/node name match) or None in which case all
128  nodes of that kind of advertisement will match.
129 
130  @param rule : a rule to unadvertise
131  @type Rule
132 
133  @return the list of rules removed
134  @rtype Rule[]
135  '''
136 
137  rospy.loginfo("Gateway : (req) unadvertise %s" % utils.format_rule(rule))
138 
139  if rule.node:
140  # This looks for *exact* matches.
141  try:
142  self.lock.acquire()
143  self.watchlist[rule.type].remove(rule)
144  self.lock.release()
145  return [rule]
146  except ValueError:
147  self.lock.release()
148  return []
149  else:
150  # This looks for any flip rules which match except for the node name
151  # also no need to check for type with the dic keys like they are
152  existing_rules = []
153  self.lock.acquire()
154  for existing_rule in self.watchlist[rule.type]:
155  if (existing_rule.name == rule.name):
156  existing_rules.append(existing_rule)
157  for rule in existing_rules:
158  self.watchlist[rule.type].remove(existing_rule) # not terribly optimal
159  self.lock.release()
160  return existing_rules
161 
162  def advertise_all(self, blacklist):
163  '''
164  Allow all rules apart from the ones in the provided blacklist +
165  default blacklist
166 
167  @param blacklist : list of Rule objects
168  @type list : list of Rule objects
169 
170  @return failure if already advertising all, success otherwise
171  @rtype bool
172  '''
173  rospy.loginfo("Gateway : received a request advertise everything!")
174  self.lock.acquire()
175 
176  # Check if advertise all already enabled
177  if self.advertise_all_enabled:
178  self.lock.release()
179  return False
180  self.advertise_all_enabled = True
181 
182  # generate watchlist
183  self.watchlist = utils.create_empty_connection_type_dictionary() # easy hack for getting a clean watchlist
184  for connection_type in utils.connection_types:
185  allow_all_rule = Rule()
186  allow_all_rule.name = '.*'
187  allow_all_rule.type = connection_type
188  allow_all_rule.node = '.*'
189  self.watchlist[connection_type].append(allow_all_rule)
190 
191  # generate blacklist (while making sure only unique rules get added)
192  self.blacklist = copy.deepcopy(self._default_blacklist)
193  for rule in blacklist:
194  if not publicRuleExists(rule, self.blacklist[rule.type]):
195  self.blacklist[rule.type].append(rule)
196 
197  self.lock.release()
198  return True
199 
200  def unadvertise_all(self):
201  '''
202  Disallow the allow all mode, if enabled. If allow all mode is not
203  enabled, remove everything from the public interface
204  '''
205  rospy.loginfo("Gateway : received a request to remove all advertisements!")
206  self.lock.acquire()
207 
208  # stop advertising all
209  self.advertise_all_enabled = False
210 
211  # easy hack for resetting the watchlist and blacklist
212  self.watchlist = utils.create_empty_connection_type_dictionary()
213  self.blacklist = self._default_blacklist
214 
215  self.lock.release()
216 
217  ##########################################################################
218  # List Accessors
219  ##########################################################################
220 
221  def getConnections(self):
222  '''
223  List of all rules with connection information that is being published.
224 
225  @return dictionary of utils.Connections keyed by type.
226  '''
227  return self.public
228 
229  def getInterface(self):
230  '''
231  List of all rules currently being advertised.
232 
233  @return list of all connections posted on hubs
234  @rtype list of gateway_msgs.msg.Rule
235  '''
236  l = []
237  self.lock.acquire()
238  for connection_type in utils.connection_types:
239  l.extend([connection.rule for connection in self.public[connection_type]])
240  self.lock.release()
241  return l
242 
243  def getWatchlist(self):
244  l = []
245  self.lock.acquire()
246  for connection_type in utils.connection_types:
247  l.extend(self.watchlist[connection_type])
248  self.lock.release()
249  return l
250 
251  def getBlacklist(self):
252  l = []
253  self.lock.acquire()
254  for connection_type in utils.connection_types:
255  l.extend(self.blacklist[connection_type])
256  self.lock.release()
257  return l
258 
259  ##########################################################################
260  # Filter
261  ##########################################################################
262 
263  def _matchAgainstRuleList(self, rules, rule):
264  '''
265  Match a given rule/rule against a given rule list
266 
267  @param rules : the rules against which to match
268  @type dict of list of Rule objects
269  @param rule : the given rule/rule to match
270  @type Rule
271  @return the list of rules matched, None if no rules found
272  @rtype list of Rules || None
273  '''
274  matched = False
275  for r in rules[rule.type]:
276  name_match_result = re.match(r.name, rule.name)
277  if name_match_result and name_match_result.group() == rule.name:
278  if r.node:
279  node_match_result = re.match(r.node, rule.node)
280  if node_match_result and node_match_result.group() == rule.node:
281  matched = True
282  else:
283  matched = True
284  if matched:
285  break
286  return matched
287 
288  def _allowRule(self, rule):
289  '''
290  Determines whether a given rule should be allowed given the
291  status of the current watchlist and blacklist
292 
293  @param rule : the given rule/rule to match
294  @type Rule
295  @return whether rule is allowed
296  @rtype bool
297  '''
298  self.lock.acquire()
299  matched_rules = self._matchAgainstRuleList(self.watchlist, rule)
300  #rospy.loginfo("PUBLIC IF : watchlist : {0} => MATCH ? {1}".format(self.watchlist, matched_rules))
301 
302  matched_blacklisted_rules = self._matchAgainstRuleList(self.blacklist, rule)
303  #rospy.loginfo("PUBLIC IF : blacklist : {0} => MATCH ? {1}".format(self.watchlist, matched_blacklisted_rules))
304 
305  self.lock.release()
306  success = False
307  if matched_rules and not matched_blacklisted_rules:
308  success = True
309  return success
310 
311  def _generatePublic(self, rule):
312  '''
313  Given a rule, determines if the rule is allowed. If it is
314  allowed, then returns the corresponding Rule object
315 
316  @param rules : the given rules to match
317  @type Rule
318  @return The generated Rule if allowed, None if no match
319  @rtype Rule || None
320  '''
321  if self._allowRule(rule):
322  return Rule(rule)
323  return None
324 
325  def update(self, connections, generate_advertisement_connection_details):
326  """
327  Checks a list of rules and determines which ones should be
328  added/removed to the public interface. Modifies the public interface
329  accordingly, and returns the list of rules to the gateway for
330  hub operations
331 
332  @param rules: the list of rules available locally
333  @type dict of lists of Rule objects
334 
335  @param generate_advertisement_connection_details : function from LocalMaster
336  that generates Connection.type_info and Connection.xmlrpc_uri
337  @type method (see LocalMaster.generate_advertisement_connection_details)
338 
339  @return: new public connections, as well as connections to be removed
340  @rtype: utils.Connection[], utils.Connection[]
341  """
342  # SLOW, EASY METHOD
343  permitted_connections = utils.create_empty_connection_type_dictionary()
344  new_public = utils.create_empty_connection_type_dictionary()
345  removed_public = utils.create_empty_connection_type_dictionary()
346  for connection_type in utils.connection_types:
347  for connection in connections[connection_type]:
348  #rospy.loginfo("PUBLIC IF : Checking: {0}...".format(connection))
349  if self._allowRule(connection.rule):
350  permitted_connections[connection_type].append(connection)
351  #rospy.loginfo("PUBLIC IF : Permitted: {0}".format(connection))
352  self.lock.acquire() # protect self.public
353  for connection_type in utils.connection_types:
354  for connection in permitted_connections[connection_type]:
355  if not connection.inConnectionList(self.public[connection_type]):
356  new_connection = generate_advertisement_connection_details(
357  connection.rule.type, connection.rule.name, connection.rule.node)
358  # can happen if connection disappeared in between getting the connection
359  # index (watcher thread) and checking for the topic_type (preceding line)
360  if new_connection is not None:
361  new_public[connection_type].append(new_connection)
362  self.public[connection_type].append(new_connection)
363  #rospy.loginfo("PUBLIC IF : New connection: {0}".format(new_connection))
364  removed_public[connection_type][:] = [
365  x for x in self.public[connection_type] if not x.inConnectionList(
366  permitted_connections[connection_type])]
367  self.public[connection_type][:] = [
368  x for x in self.public[connection_type] if not x.inConnectionList(removed_public[connection_type])]
369  self.lock.release()
370  #rospy.loginfo("PUBLIC IF : Removed connections: {0}".format(removed_public))
371  return new_public, removed_public
def publicRuleExists(public_rule, public_rules)
Functions.
def add_rule(self, rule)
Public Interfaces.
def _matchAgainstRuleList(self, rules, rule)
Filter.
def update(self, connections, generate_advertisement_connection_details)
def __init__(self, default_rule_blacklist, default_rules)


rocon_gateway
Author(s): Daniel Stonier , Jihoon Lee , Piyush Khandelwal
autogenerated on Mon Jun 10 2019 14:40:10