Exemplo n.º 1
0
 def _GetSegmentStats(self, message_name):
     switches = self._network_config.GetSwitches()
     message_types = self._network_config.all_messages
     path_finder = network_util.PathFinder(switches, message_types)
     message = self._network_config.GetAioMessageType(message_name)
     graph = network_util.MessageGraph(path_finder, message)
     return network_util.GetSegmentStats(graph, message.all_senders)
Exemplo n.º 2
0
def AddDependencies(task_mgr):
  """Add bootload dependencies given unicast routes in the network config."""

  config = network_config.NetworkConfig()
  path_finder = network_util.PathFinder(config.GetSwitches(),
                                        config.all_messages)
  destinations = ['aio_nodes.' + t.GetTarget() for t in task_mgr.GetTasks()]
  paths = path_finder.GetHops(None, 'aio_nodes.operator', destinations,
                              unicast=True)
  for path in paths:
    for ingress_index, _ in enumerate(path[:-1]):
      ingress_all = path_finder.GetAttachedNodes(path[ingress_index])
      ingress_tms570 = [n for n in ingress_all
                        if config.GetAioNode(n).tms570_node]
      for egress_index in range(ingress_index + 1, len(path)):
        egress_all = path_finder.GetAttachedNodes(path[egress_index])
        egress_tms570 = [n for n in egress_all
                         if config.GetAioNode(n).tms570_node]
        for egress in egress_tms570:
          for ingress in ingress_tms570:
            task_mgr.AddDependency(egress, ingress)

  # Configuration tasks depend on the corresponding application tasks.
  for target in [task.GetTarget() for task in task_mgr.GetTasks()]:
    task_mgr.AddInternalDependencies(target)
Exemplo n.º 3
0
    def testGetNodeBandwidthStatistics(self):
        switches = self._network_config.GetSwitches()
        message_types = self._network_config.all_messages
        path_finder = network_util.PathFinder(switches, message_types)
        node_stats = network_util.GetNodeBandwidthStatistics(
            path_finder, message_types)
        for aio_node in [
                'aio_nodes.controller_a', 'aio_nodes.controller_b',
                'aio_nodes.controller_c'
        ]:
            self.assertEqual(node_stats[aio_node].send,
                             {'ControllerCommand': 100})
            self.assertEqual(
                node_stats[aio_node].receive,
                {
                    'FlightComputerSensor':
                    50,  # 2 networks for all but the local Fc.
                    'MotorStatus': 800,  # 4 motors * 2 networks.
                })
            self.assertEqual(node_stats[aio_node].multicast_packet_rate['tx'],
                             100)
            self.assertEqual(node_stats[aio_node].multicast_packet_rate['rx'],
                             850)

        sensor_messages_sent = 0
        for aio_node in ['aio_nodes.fc_a', 'aio_nodes.fc_b', 'aio_nodes.fc_c']:
            self.assertEqual(node_stats[aio_node].send,
                             {'FlightComputerSensor': 10})
            self.assertEqual(node_stats[aio_node].receive, {})
            self.assertEqual(node_stats[aio_node].multicast_packet_rate['tx'],
                             10)
            self.assertEqual(node_stats[aio_node].multicast_packet_rate['rx'],
                             0)
            sensor_messages_sent += node_stats[aio_node].send[
                'FlightComputerSensor']

        for aio_node in [
                'aio_nodes.motor_pbo', 'aio_nodes.motor_sbi',
                'aio_nodes.motor_pbi', 'aio_nodes.motor_sbo'
        ]:
            self.assertEqual(node_stats[aio_node].send, {
                'MotorStatus': 100,
                'MotorStacking': 1000
            })
            # 2 networks * each other motor, no loopback packets.
            self.assertEqual(node_stats[aio_node].receive, {
                'ControllerCommand': 1000,
                'MotorStacking': 6000
            })
            self.assertEqual(node_stats[aio_node].multicast_packet_rate['tx'],
                             1100)
            self.assertEqual(node_stats[aio_node].multicast_packet_rate['rx'],
                             7000)

        # Multiply by two for the two networks, the by 2 for the two paths from the
        # wing to the ground.
        self.assertEqual(
            node_stats['aio_nodes.host'].receive['FlightComputerSensor'],
            sensor_messages_sent * 2 * 2)
Exemplo n.º 4
0
 def testCheckForUnintendedRecipients(self):
     config = self._network_config
     message_types = config.all_messages
     path_finder = network_util.PathFinder(config.GetSwitches(),
                                           message_types)
     for message in message_types:
         graph = network_util.MessageGraph(path_finder, message)
         network_util.CheckForUnintendedRecipients(graph)
Exemplo n.º 5
0
 def testCheckForLoopRoutes(self):
     config = self._network_config
     message_types = config.all_messages
     path_finder = network_util.PathFinder(config.GetSwitches(),
                                           message_types)
     for message in message_types:
         graph = network_util.MessageGraph(path_finder, message)
         visitor = network_util.MessageGraphVisitor()
         graph.VisitSenders(visitor, message.all_senders)
Exemplo n.º 6
0
def GetFullBandwidthStatistics(config):
    """Gather the bandwidth statistics for each link, message, and source.

  Args:
    config: A NetworkConfig.
  Returns:
    stats: A dictionary of link statistics, indexed by a
        (sender, message, source, sink) tuple denoting sender and message type.
  """
    Key = collections.namedtuple(  # pylint: disable=invalid-name
        'Key', ['sender', 'message', 'source', 'sink'])

    output = dict()
    message_types = config.all_messages
    switches = config.GetSwitches()
    locations = node_locations.GetNodeLocations(config)
    path_finder = network_util.PathFinder(switches, message_types)

    ignore_senders = FLAGS.ignore_node
    for location, senders in locations.iteritems():
        if location in FLAGS.ignore_location:
            ignore_senders += [s.snake_name for s in senders]

    for message in message_types:
        if message.frequency_hz <= 0:
            continue

        senders = [
            s for s in message.all_senders
            if s.snake_name not in ignore_senders
        ]
        graph = network_util.MessageGraph(path_finder, message)
        message_stats = network_util.GetSegmentStats(graph, senders)
        message_size = _GetMessageSize(message)

        for segment, senders in message_stats.iteritems():
            for sender, stats in senders.iteritems():
                packets_per_sec = stats['packets_per_sec']
                bytes_per_sec = packets_per_sec * message_size
                packets_peak = stats['peak']
                bytes_peak = packets_peak * message_size

                key = Key(sender=sender.snake_name,
                          message=message.name,
                          source=segment[0],
                          sink=segment[1])
                output[key] = Stat(bytes_per_sec=bytes_per_sec,
                                   packets_per_sec=packets_per_sec,
                                   bytes_peak=bytes_peak,
                                   packets_peak=packets_peak,
                                   utilization=bytes_per_sec * 8 /
                                   _GetBandwidth(switches, segment))

    return output
Exemplo n.º 7
0
    def Initialize(self):
        config = network_config.NetworkConfig(settings.NETWORK_YAML)
        message_types = config.all_messages
        path_finder = network_util.PathFinder(config.GetSwitches(),
                                              message_types)
        self._node_stats = {}
        for tag, stats in network_util.GetNodeBandwidthStatistics(
                path_finder, message_types).iteritems():
            self._node_stats[self._ParseNetworkConfigTag(tag)[1]] = stats

        self._link_stats = network_bandwidth.GetLinkBandwidthStatistics(config)
        self._freq_by_message_type = {}
        self._status_message_by_aio_node = {}
        for m in message_types:
            self._freq_by_message_type[m.name] = m.frequency_hz
            if m.name in ['CoreSwitchSlowStatus', 'SlowStatus']:
                for sender in m.all_senders:
                    self._status_message_by_aio_node[
                        sender.camel_name] = m.name
Exemplo n.º 8
0
def _WriteForwardingMapSource(autogen_root, output_dir, script_name, config):
    """Writes the implementation file for the forwarding map C module.

  Args:
    autogen_root: The MAKANI_HOME-equivalent top directory.
    output_dir: The directory in which to output the files.
    script_name: This script's filename.
    config: A NetworkConfig.
  """

    file_basename = os.path.join(output_dir, 'routes')
    rel_path = os.path.relpath(file_basename, autogen_root)
    parts = [
        textwrap.dedent("""
      // Generated by {0}; do not edit.

      #include "{1}"
      """[1:]).format(script_name, rel_path + '.h')
    ]

    parts.append(
        textwrap.dedent("""\
      #include <assert.h>
      #include <stddef.h>
      #include <stdint.h>

      #include "avionics/network/eop_message_type.h"
      #include "avionics/network/message_type.h"
      #include "avionics/network/winch_message_type.h"
      """))

    for messages in config.messages_by_type.values():
        path_finder = network_util.PathFinder(config.GetSwitches(), messages)
        forward = network_util.MakeForwardingMaps(messages, path_finder)
        parts.extend(_GetForwardingMap(forward, messages, config))

    with open(file_basename + '.c', 'w') as f:
        f.write('\n'.join(parts))
Exemplo n.º 9
0
def GetNodeLocations(network_config):
  """Identify node location given core switch location.

  Args:
    network_config: A NetworkConfig.

  Returns:
    A dict of sets of AIO node names, where the key name indicates the
    node location.

  Raises:
    ValueError: If a node appears in more than one location.
  """
  nodes = {
      'wing': set(),
      'ground_station': set(),
      'remote_command': set(),
      'test_fixture': set()
  }
  core_switch_locations = {
      'cs_a': 'wing',
      'cs_b': 'wing',
      'cs_dyno_a': 'test_fixture',
      'cs_dyno_b': 'test_fixture',
      'cs_gs_a': 'ground_station',
      'cs_gs_b': 'ground_station',
      'joystick_radio_ground': 'remote_command',
      'remote_command': 'remote_command',
  }

  path_finder = network_util.PathFinder(network_config.GetSwitches(),
                                        network_config.aio_messages)
  destinations = ['aio_nodes.operator']
  for aio_node in network_config.aio_nodes:
    source = network_util.AioNodeToTag(aio_node)
    try:
      switches = path_finder.GetFirstSwitch(None, source, destinations,
                                            core_switch_locations.keys())
    except KeyError:
      switches = []
    for switch in switches:
      nodes[core_switch_locations[switch]].add(aio_node)

  # Remove test fixtures.
  # TODO: Eliminate after we support multiple network configurations.
  test_fixtures = [network_config.GetAioNode(n) for n in [
      'simulated_joystick', 'simulator', 'torque_cell', 'visualizer']]
  for loc in nodes:
    if loc != 'test_fixture':
      nodes[loc] -= set(test_fixtures)
  nodes['test_fixture'] |= set(test_fixtures)

  # Check for potential duplicate listings.
  for loc_a in nodes:
    for loc_b in nodes:
      intersection = nodes[loc_a] & nodes[loc_b]
      if loc_a != loc_b and intersection:
        names = [node.snake_name for node in intersection]
        raise ValueError(
            'Nodes appear both in {loc_a} and {loc_b}: {nodes}'.format(
                loc_a=loc_a, loc_b=loc_b, nodes=names))
  return nodes
Exemplo n.º 10
0
            'Now try e.g. \'fdp ./reduced.dot -Tpng:gd > reduced.png && '
            'eog reduced.png\'')


def main(argv):
    try:
        argv = FLAGS(argv)
    except gflags.FlagsError, e:
        sys.stderr.write('\nError: %s\n\nUsage: %s ARGS\n%s\n' %
                         (e, argv[0], FLAGS))
        sys.exit(1)

    config = network_config.NetworkConfig(FLAGS.network_file)
    message_types = config.all_messages

    path_finder = network_util.PathFinder(config.GetSwitches(), message_types)

    if FLAGS.print_routes:
        forwarding_maps = network_util.MakeForwardingMaps(
            message_types, path_finder)

    if FLAGS.print_routes:
        _PrintMulticastRoutes(forwarding_maps)

    if FLAGS.write_dot_files:
        _WriteDotFiles(path_finder)


if __name__ == '__main__':
    main(sys.argv)
Exemplo n.º 11
0
def _WriteSwitchInfo(autogen_root, output_dir, script_name, config):
    """Write switch_info.c.

  Args:
    autogen_root: The MAKANI_HOME-equivalent top directory.
    output_dir: The directory in which to output the files.
    script_name: This script's filename.
    config: A NetworkConfig.

  Raises:
    IpAddressException: On any IP address error.
    SwitchMismatchException: If the TMS570 port of a switch is invalid.
    TrunkException: If there is a problem with the trunk configuration.
  """
    file_without_extension = os.path.join(output_dir, 'switch_info')
    rel_path = os.path.relpath(file_without_extension, autogen_root)

    parts = [
        textwrap.dedent("""
      // Generated by {name}; do not edit.

      #include "{header}"

      #include <assert.h>
      #include <stdbool.h>
      #include <stdint.h>
      #include <string.h>

      """.format(name=script_name, header=rel_path + '.h'))
    ]

    ip_addr = {node.snake_name: node.ip_octet for node in config.aio_nodes}
    switches = config.GetSwitches()
    switches_sorted = sorted(switches.iterkeys())
    message_types = config.all_messages

    def _GetTms570Node(switch):
        if 'config' not in switch or 'tms570' not in switch['config']:
            return None
        port = switch['ports'][switch['config']['tms570']]
        if not port.startswith('aio_nodes.'):
            raise SwitchMismatchException(
                'No AIO node connected to TMS570 port.')
        return port[len('aio_nodes.'):]

    # The VLAN numbering convention here uses the lowest of the last octet of the
    # IP addresses of the TMS570s on the two connected switches, concatenated
    # with the port number on the associated switch.  Since the core switches have
    # 27 ports we need 5 bits to represent the port number.  As a consequence
    # TMS570s with last octets greater than 127 will present a problem for this
    # scheme.  Additionally the IP with last octet 0 is reserved for other VLANs.
    def _GetSegId(node, port, remote_node, remote_port):
        ip = ip_addr[node]
        remote_ip = ip_addr[remote_node]
        if remote_ip < ip:
            ip = remote_ip
            port = remote_port
        if ip <= 0 or ip > 127:
            raise IpAddressException('Invalid IP octet 0 >= IP or IP > 127.')
        return (ip << 5) | int(port)

    def _GenerateMask(port_list):
        return sum(1 << i for i in port_list)

    path_finder = network_util.PathFinder(switches, None, network_c=True)
    net_c_forward = network_util.MakeNetworkCForwardingMap(path_finder)

    for switch_name in switches_sorted:
        switch = switches[switch_name]
        # We use simple configs for virtual or managed switches.
        if 'config' not in switch or 'tms570' not in switch['config']:
            continue
        config = switch['config']
        node = _GetTms570Node(switch)
        seg_ids = [0] * config['chip']['num_ports']
        for port, remote in switch['ports'].iteritems():
            if remote.startswith('switches.'):
                _, remote_switch_name, remote_port = remote.split('.')
                remote_switch = switches[remote_switch_name]
                remote_node = _GetTms570Node(remote_switch)
                if not remote_node or remote_node not in ip_addr:
                    continue
                seg_ids[int(port)] = _GetSegId(node, port, remote_node,
                                               remote_port)
        info = {}
        info['node_camel'] = c_helpers.SnakeToCamel(node)
        info['switch_type'] = 'kSwitchType' + c_helpers.SnakeToCamel(
            config['chip']['type'])
        info['segment_vlans'] = 'segmentVlanIds' + info['node_camel']
        info['num_ports'] = config['chip']['num_ports']
        info['num_fiber_ports'] = config['chip']['num_fiber_ports']
        parts.append(
            textwrap.fill('static const uint16_t {}[{}] = {{{}}};'.format(
                info['segment_vlans'], info['num_ports'],
                ', '.join(str(i) for i in seg_ids)),
                          80,
                          subsequent_indent='  '))

        # Trunk configuration.
        trunk_ports = set()
        select_ports = set()
        trunk_unicast_learning_ports = set()
        info['num_multicast_overrides'] = 0
        info['multicast_overrides'] = 'NULL'
        if 'trunk' in config:
            trunk_ports = config['trunk']['ports']
            if 'unicast_learning' in config['trunk']:
                trunk_unicast_learning_ports = config['trunk'][
                    'unicast_learning']
            if 'override_message_routes' in config['trunk']:
                mcast_overrides = []
                override = config['trunk']['override_message_routes']
                for override_type, override_ports in override.iteritems():
                    mask = _GenerateMask(override_ports)
                    for m in message_types:
                        if m.name == override_type:
                            if m.eop_message:
                                mac = network_config.EopMessageTypeToEthernetAddress(
                                    eop_message_type.__dict__['kEopMessageType'
                                                              + override_type])
                            elif m.winch_message:
                                mac = network_config.WinchMessageTypeToEthernetAddress(
                                    winch_message_type.__dict__[
                                        'kWinchMessageType' + override_type])
                            else:
                                mac = network_config.AioMessageTypeToEthernetAddress(
                                    message_type.__dict__['kMessageType' +
                                                          override_type])
                            break
                    else:
                        raise TrunkException(
                            'Override contains invalid message type %s.' %
                            override_type)
                    mcast_overrides.append(('{%s}' % ', '.join(
                        '0x%02X' % x
                        for x in [mac.a, mac.b, mac.c, mac.d, mac.e, mac.f]),
                                            mask))
                info['multicast_overrides'] = 'multicastOverrides' + info[
                    'node_camel']
                info['num_multicast_overrides'] = len(mcast_overrides)
                parts.append(
                    'static const TrunkMulticastOverride {}[{}] = {{\n{}\n}};'.
                    format(
                        info['multicast_overrides'],
                        info['num_multicast_overrides'],
                        ',\n'.join('  {%s, 0x%08X}' % o
                                   for o in mcast_overrides)))
            if 'select_default_ports' in config['trunk']:
                select_ports = config['trunk']['select_default_ports']
            else:
                select_ports = trunk_ports

        info['forward_mask_a'] = _GenerateMask(config['network_a'])
        info['forward_mask_b'] = _GenerateMask(config['network_b'])
        info['forward_mask_c'] = net_c_forward[switch_name]
        info['egress_mask_c'] = _GenerateMask(config.get('network_c', []))
        if 'isolate' in config:
            if trunk_ports:
                raise TrunkException(
                    'Isolate and trunk definitions are currently not '
                    'supported together.')
            else:
                isolate_ports = config['isolate']
        else:
            isolate_ports = trunk_ports
        info['isolate_mask'] = _GenerateMask(isolate_ports)
        info['trunk_mask'] = _GenerateMask(trunk_ports)
        info['unicast_learning_mask'] = _GenerateMask(
            trunk_unicast_learning_ports)
        info['select_default_mask'] = _GenerateMask(select_ports)
        info['unicast_mask'] = _GenerateMask(config['unicast'])
        info['host_port'] = config['tms570']
        info['mirror_port'] = config['mirror']

        parts.append(
            'static const SwitchInfo switchInfo{node_camel} = {{\n'
            '  {switch_type}, {num_ports}, {num_fiber_ports},\n'
            '  {host_port}, {mirror_port},\n'
            '  0x{forward_mask_a:X}, 0x{forward_mask_b:X},\n'
            '  0x{forward_mask_c:X}, 0x{egress_mask_c:X},\n'
            '  0x{isolate_mask:X}, 0x{unicast_mask:X}, {segment_vlans},\n'
            '  {{0x{trunk_mask:X}, 0x{select_default_mask:X},'
            ' 0x{unicast_learning_mask:X},\n'
            '   {num_multicast_overrides}, {multicast_overrides}}}\n'
            '}};\n'.format(**info))

    parts.append(
        textwrap.dedent("""
      const SwitchInfo *GetSwitchInfo(AioNode node) {
        switch (node) {"""[1:]))

    unused_nodes = set(c_helpers.EnumHelper('AioNode', aio_node).Names())

    for switch_name in switches_sorted:
        switch = switches[switch_name]
        if 'config' not in switch or 'tms570' not in switch['config']:
            continue
        node = _GetTms570Node(switch)
        node_camel = c_helpers.SnakeToCamel(node)
        node_name = 'kAioNode' + node_camel
        unused_nodes.remove(node_name)
        parts.append('    case %s:' % node_name)
        if 'config' not in switch:
            parts.append('      assert(false);')
            parts.append('      return NULL;')
        else:
            parts.append('      return &switchInfo%s;' % node_camel)

    parts.append('    // Fall-through intentional.')
    for node in sorted(unused_nodes):
        parts.append('    case %s:' % node)
    parts.append(
        textwrap.dedent("""
          case kAioNodeForceSigned:
          case kNumAioNodes:
          default:
            assert(false);
            return NULL;
        }
      }
  """[1:]))

    with open(file_without_extension + '.c', 'w') as f:
        f.write('\n'.join(parts))