4 # Copyright (c) 2020 Project CHIP Authors
5 # Copyright (c) 2018-2019 Google LLC.
8 # Licensed under the Apache License, Version 2.0 (the "License");
9 # you may not use this file except in compliance with the License.
10 # You may obtain a copy of the License at
12 # http://www.apache.org/licenses/LICENSE-2.0
14 # Unless required by applicable law or agreed to in writing, software
15 # distributed under the License is distributed on an "AS IS" BASIS,
16 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 # See the License for the specific language governing permissions and
18 # limitations under the License.
23 # Implements ChipInet class that tests Inet Layer multicast
24 # among multiple nodes.
31 from happy.ReturnMsg import ReturnMsg
32 from happy.Utils import *
33 from happy.utils.IP import IP
34 from happy.HappyNode import HappyNode
35 from happy.HappyNetwork import HappyNetwork
37 from ChipTest import ChipTest
41 options["configuration"] = None
42 options["interface"] = None
43 options["ipversion"] = None
44 options["transport"] = None
45 options["interval"] = None
46 options["quiet"] = False
54 class ChipInetMulticast(HappyNode, HappyNetwork, ChipTest):
57 Supports two networks: IPv4 and IPv6
58 Supports two transports: raw IP and UDP
64 def __init__(self, opts=options):
65 HappyNode.__init__(self)
66 HappyNetwork.__init__(self)
67 ChipTest.__init__(self)
69 # Dictionary that will eventually contain 'node' and 'tag' for
70 # the sender node process.
74 # Array that will eventually contain 'node' and 'tag'
75 # dictionaries for the receiver node processes.
79 self.configuration = opts["configuration"]
80 self.interface = opts["interface"]
81 self.ipversion = opts["ipversion"]
82 self.transport = opts["transport"]
83 self.interval = opts["interval"]
84 self.quiet = opts["quiet"]
85 self.tap = opts["tap"]
87 def __log_error_and_exit(self, error):
88 self.logger.error("[localhost] ChipInetMulticast: %s" % (error))
91 def __checkNodeExists(self, node, description):
92 if not self._nodeExists(node):
93 emsg = "The %s '%s' does not exist in the test topology." % (
95 self.__log_error_and_exit(emsg)
97 def __pre_check(self):
98 """Sanity check the instantiated options and configuration"""
99 # Sanity check the transport
100 if self.transport != "udp" and self.transport != "raw":
101 emsg = "Transport type must be one of 'raw' or 'udp'."
102 self.__log_error_and_exit(emsg)
104 # Sanity check the IP version
105 if self.ipversion != "4" and self.ipversion != "6":
106 emsg = "The IP version must be one of '4' or '6'."
107 self.__log_error_and_exit(emsg)
109 # Sanity check the configuration
110 if self.configuration == None:
111 emsg = "The test configuration is missing."
112 self.__log_error_and_exit(emsg)
114 # There must be exactly one sender
115 if self.configuration['sender'] == None or len(self.configuration['sender']) != 1:
116 emsg = "The test configuration must have exactly one sender, 'sender'."
117 self.__log_error_and_exit(emsg)
119 # There must be at least one receiver
120 if self.configuration["receivers"] == None or len(self.configuration["receivers"]) < 1:
121 emsg = "The test configuration must at least one receiver in 'receivers'."
122 self.__log_error_and_exit(emsg)
124 # Each specified sender and receiver node must exist in the
125 # loaded Happy configuration.
127 for node in self.configuration['sender'].keys():
128 self.__checkNodeExists(node, "sender")
130 for node in self.configuration['receivers'].keys():
131 self.__checkNodeExists(node, "receiver")
133 if not self.interval == None and not self.interval.isdigit():
134 emsg = "Please specify a valid send interval in milliseconds."
135 self.__log_error_and_exit(emsg)
137 def __gather_process_results(self, process, quiet):
138 """Gather and return the exit status and output as a tuple for the
139 specified process node and tag.
141 node = process['node']
144 status, output = self.get_test_output(node, tag, quiet)
146 return (status, output)
148 def __gather_sender_results(self, quiet):
149 """Gather and return the exit status and output as a tuple for the
152 status, output = self.__gather_process_results(self.sender, quiet)
154 return (status, output)
156 def __gather_receiver_results(self, receiver, quiet):
157 """Gather and return the exit status and output as a tuple for the
158 specified receiver process.
160 status, output = self.__gather_process_results(receiver, quiet)
162 return (status, output)
164 def __gather_receivers_results(self, quiet):
165 """Gather and return the exit status and output as a tuple for all
168 receiver_results = {}
169 receivers_results = []
171 for receiver in self.receivers:
172 receiver_results['status'], receiver_results['output'] = \
173 self.__gather_receiver_results(receiver, quiet)
174 receivers_results.append(receiver_results)
176 return (receivers_results)
178 def __gather_results(self):
179 """Gather and return the exit status and output as a dictionary for all
180 sender and receiver processes.
185 receivers_results = []
187 sender_results['status'], sender_results['output'] = \
188 self.__gather_sender_results(quiet)
190 receivers_results = self.__gather_receivers_results(quiet)
192 results['sender'] = sender_results
193 results['receivers'] = receivers_results
197 def __process_results(self, results):
200 output['sender'] = ""
201 output['receivers'] = []
203 # Iterate through the sender and receivers return status. If
204 # all had successful (0) status, then the cumulative return
205 # status is successful (True). If any had unsuccessful status,
206 # then the cumulative return status is unsuccessful (False).
208 # For the output, simply key it by sender and receivers.
212 status = (results['sender']['status'] == 0)
214 output['sender'] = results['sender']['output']
218 for receiver_results in results['receivers']:
219 status = (status and (receiver_results['status'] == 0))
220 output['receivers'].append(receiver_results['output'])
222 return (status, output)
224 def __start_node(self, node, attributes, extra, tag):
225 """Start the test process on the specified node with the provided
226 tag using the provided attributes and extra command line
229 cmd = self.getChipInetLayerMulticastPath()
231 # Generate and accumulate the multicast group-related command
234 for group in attributes['groups']:
235 cmd += " --group %u" % (group['id'])
236 cmd += " --group-expected-tx-packets %u" % (group['send'])
237 cmd += " --group-expected-rx-packets %u" % (group['receive'])
239 # Generate and accumulate the transport and IP version command
242 cmd += " --ipv" + self.ipversion
244 cmd += " --" + self.transport
246 # If present, generate and accumulate the LwIP hosted OS
247 # network tap device interface.
250 cmd += " --tap-device " + self.tap
252 # If present, generate and accumulate the LwIP hosted OS
253 # network local IPv4 address.
255 if self.ipversion == "4" and attributes.has_key('tap-ipv4-local-addr'):
256 cmd += " --local-addr " + attributes['tap-ipv4-local-addr']
258 # Generate and accumulate, if present, the bound network
259 # interface command line option.
261 if not self.interface == None:
262 cmd += " --interface " + self.interface
264 # Accumulate any extra command line options specified.
269 "[localhost] ChipInetMulticast: Will start process on node '%s' with tag '%s' and command '%s'" % (node, tag, cmd))
271 self.start_chip_process(
272 node, cmd, tag, sync_on_output=self.ready_to_service_events_str)
274 def __start_receiver(self, node, attributes, identifier):
275 """Start a receiver test process on the specified node with the
276 provided attributes and tag identifier.
279 tag = "INET-MCAST-RX-%u" % (identifier)
281 extra_cmd = " --listen"
283 self.__start_node(node, attributes, extra_cmd, tag)
285 receiver['node'] = node
286 receiver['tag'] = tag
288 self.receivers.append(receiver)
290 def __start_receivers(self):
291 """Start all receiver test processes."""
294 for receiver in self.configuration['receivers']:
295 self.__start_receiver(
296 receiver, self.configuration['receivers'][receiver], identifier)
299 def __start_sender(self):
300 """Start the sender test process."""
301 node = list(self.configuration['sender'])[0]
302 attributes = self.configuration['sender'][node]
303 tag = "INET-MCAST-TX-0"
307 if not self.interval == None:
308 extra_cmd += " --interval " + str(self.interval)
310 extra_cmd += " --no-loopback"
312 self.__start_node(node, attributes, extra_cmd, tag)
314 self.sender['node'] = node
315 self.sender['tag'] = tag
317 def __wait_for_sender(self):
318 """Block and wait for the sender test process."""
319 node = self.sender['node']
320 tag = self.sender['tag']
323 "[localhost] ChipInetMulticast: Will wait for sender on node %s with tag %s..." % (node, tag))
325 self.wait_for_test_to_end(node, tag)
327 def __stop_receiver(self, receiver):
328 node = receiver['node']
329 tag = receiver['tag']
332 "[localhost] ChipInetMulticast: Will stop receiver on node %s with tag %s..." % (node, tag))
334 self.stop_chip_process(node, tag)
336 def __stop_receivers(self):
337 """Stop all receiver test processes."""
338 for receiver in self.receivers:
339 self.__stop_receiver(receiver)
344 self.logger.debug("[localhost] ChipInetMulticast: Run.")
348 self.__start_receivers()
350 self.logger.debug("[%s] ChipInetMulticast: %u receivers should be running." % (
351 "localhost", len(self.receivers)))
353 for receiver in self.receivers:
354 emsg = "receiver %s should be running." % (receiver['tag'])
355 self.logger.debug("[%s] ChipInetMulticast: %s" %
356 (receiver['node'], emsg))
358 self.__start_sender()
360 self.__wait_for_sender()
362 self.__stop_receivers()
364 # Gather results from the sender and receivers
366 results = self.__gather_results()
368 # Process the results from the sender and receivers into a
369 # singular status value and a results dictionary containing
372 status, results = self.__process_results(results)
374 self.logger.debug("[localhost] ChipInetMulticast: Done.")
376 return ReturnMsg(status, results)