class FaucetTopoTestBase(FaucetTestBase): """ Extension to the base test for the integration test suite to help set up arbitrary topologies """ NUM_FAUCET_CONTROLLERS = 2 NUM_GAUGE_CONTROLLERS = 1 DESCENDING_DPIDS = False NETPREFIX = 24 IPV = 4 GROUP_TABLE = False NUM_DPS = 2 NUM_HOSTS = 4 LINKS_PER_HOST = 1 LACP_TIMEOUT = 10 dpids = None port_maps = None n_vlans = 0 configuration_options = None mininet_host_options = None host_vlans = None link_vlans = None host_information = None faucet_vips = None def _init_faucet_config(self): """Initialize & normalize faucet configuration file""" config_vars = {} for config_var in (self.config_ports, self.port_map): config_vars.update(config_var) faucet_config = self.CONFIG % config_vars self._write_yaml_conf(self.faucet_config_path, yaml.safe_load(faucet_config)) def _annotate_interfaces_conf(self, yaml_conf): """We don't need to annotate the interfaces""" return yaml_conf def _dp_ports(self): """Return ports on the first DP""" return list(self.topo.ports[self.topo.switches_by_id[0]].keys()) def get_gauge_watcher_config(self): """Return gauge watcher config""" return """ port_stats: dps: ['%s'] type: 'port_stats' interval: 5 db: 'stats_file' port_state: dps: ['%s'] type: 'port_state' interval: 5 db: 'state_file' flow_table: dps: ['%s'] type: 'flow_table' interval: 5 db: 'flow_dir' """ % (self.topo.switches_by_id[0], self.topo.switches_by_id[0], self.topo.switches_by_id[0]) def first_switch(self): """Return the first switch""" return self.net.get(self.topo.switches_by_id[0]) def port_labels(self, port_no): """Return regex for port label""" port_name = 'b%u' % port_no return {'port': port_name, 'port_description': r'.+'} def acls(self): """Defined configuration ACLs""" return {} def faucet_vip(self, i): """Faucet VLAN VIP""" return '10.%u.0.254/%u' % (i + 1, self.NETPREFIX) def faucet_mac(self, i): """Faucet VLAN MAC""" return '00:00:00:00:00:%u%u' % (i + 1, i + 1) def host_ip_address(self, host_index, vlan_index): """Create a string of the host IP address""" if isinstance(vlan_index, (list, tuple)): vlan_index = vlan_index[0] return '10.%u.0.%u/%u' % (vlan_index + 1, host_index + 1, self.NETPREFIX) def host_ping(self, src_host, dst_ip, intf=None): """Default method to ping from one host to an IP address""" self.one_ipv4_ping(src_host, dst_ip, require_host_learned=False, retries=5, timeout=1000, intf=intf) def set_host_ip(self, host, host_ip): """Default method for setting a hosts IP address""" host.setIP(str(host_ip.ip), prefixLen=self.NETPREFIX) def build_net(self, host_links=None, host_vlans=None, switch_links=None, link_vlans=None, mininet_host_options=None, n_vlans=1, dp_options=None, host_options=None, link_options=None, vlan_options=None, routers=None, router_options=None, include=None, include_optional=None): """ Args: host_links (dict): Host index key to list of dp indices host_vlans (dict): Host index key to vlan index/indices switch_links (list): List of link tuples of switch indices (u, v) link_vlans (dict): Link tuple of switch indices (u, v) mapping to vlans mininet_host_options (dict): Host index map to additional mininet host options n_vlans (int): Number of VLANs to generate dp_options (dict): Additional options for each DP, keyed by DP index host_options (dict): Additional options for each host, keyed by host index link_options (dict): Additional options for each link, keyed by indices tuple (u, v) vlan_options (dict): Additional options for each VLAN, keyed by vlan index routers (dict): Router index to list of VLANs in the router router_options (dict): Additional options for each router, keyed by router index include (list): Files to include using the the Faucet config 'include' key include_optional (list): File to include using the Faucet config 'include_optional' key """ self.topo = FaucetTopoGenerator(self.OVS_TYPE, self.ports_sock, self._test_name(), self.NUM_DPS, self.DESCENDING_DPIDS, host_links=host_links, host_vlans=host_vlans, switch_links=switch_links, link_vlans=link_vlans, hw_dpid=self.hw_dpid, hw_ports=self.switch_map, port_order=self.port_order, start_port=self.start_port, host_options=mininet_host_options) self.dpids = self.topo.get_dpids() self.dpid = self.dpids[0] # host_port_maps = {host_n: {switch_n: [ports, ...], ...}, ...} # link_port_maps = {(switch_n, switch_m): [ports, ...], ...} self.port_maps, self.host_port_maps, self.link_port_maps = self.topo.create_port_maps( ) self.port_map = self.port_maps[self.dpid] dpid_names = {} for i in self.topo.switches_by_id: dpid = self.topo.dpids_by_id[i] name = self.topo.switches_by_id[i] dpid_names[dpid] = name self.set_dpid_names(dpid_names) self.configuration_options = { 'acl_options': self.acls(), 'dp_options': dp_options, 'host_options': host_options, 'link_options': link_options, 'vlan_options': vlan_options, 'routers': routers, 'router_options': router_options, 'include': include, 'include_optional': include_optional, 'ignored_switches': self.IGNORED_SWITCHES } self.CONFIG = self.topo.get_config( n_vlans, **self.configuration_options, ) self.mininet_host_options = mininet_host_options self.n_vlans = n_vlans self.host_vlans = host_vlans self.link_vlans = link_vlans def start_net(self): """ Override start_net to create the faucet vips, the host information and set up the host routes for routed hosts """ super().start_net() # Create a dictionary of host information self.host_information = {} ips_for_vlans = {} for host_id, host_name in self.topo.hosts_by_id.items(): host = self.net.get(host_name) vlan = self.host_vlans[host_id] ip_interface = None if vlan is not None: if isinstance(vlan, list): vlan = tuple(vlan) ips_for_vlans.setdefault(vlan, 0) ip = self.host_ip_address(ips_for_vlans[vlan], vlan) ips_for_vlans[vlan] += 1 if self.mininet_host_options and host_id in self.mininet_host_options: mininet_ip = self.mininet_host_options[host_id].get( 'ip', None) if mininet_ip: ip = mininet_ip ip_interface = ipaddress.ip_interface(ip) self.set_host_ip(host, ip_interface) self.host_information[host_id] = { 'host': host, 'ip': ip_interface, 'mac': host.MAC(), 'vlan': vlan, 'bond': None, 'ports': self.host_port_maps[host_id] } # Store faucet vip interfaces self.faucet_vips = {} for vlan in range(self.n_vlans): self.faucet_vips[vlan] = ipaddress.ip_interface( self.faucet_vip(vlan)) # Setup the linux bonds for LACP connected hosts self.setup_lacp_bonds() # Add host routes to hosts for inter vlan routing self.setup_intervlan_host_routes() def setup_lacp_bonds(self): """Search through host options for lacp hosts and configure accordingly""" host_options = self.configuration_options['host_options'] if not host_options: return bond_index = 1 for host_id, options in host_options.items(): if 'lacp' in options: host = self.host_information[host_id]['host'] # LACP must be configured with host ports down for dp_i, ports in self.host_port_maps[host_id].items(): for port in ports: self.set_port_down(port, self.topo.dpids_by_id[dp_i]) orig_ip = host.IP() lacp_switches = [ self.net.get(self.topo.switches_by_id[i]) for i in self.host_port_maps[host_id] ] bond_members = [ pair[0].name for switch in lacp_switches for pair in host.connectionsTo(switch) ] bond_name = 'bond%u' % (bond_index) self.host_information[host_id]['bond'] = bond_name for bond_member in bond_members: # Deconfigure bond members self.quiet_commands( host, ('ip link set %s down' % bond_member, 'ip address flush dev %s' % bond_member)) # Configure bond interface self.quiet_commands( host, (('ip link add %s address 0e:00:00:00:00:99 ' 'type bond mode 802.3ad lacp_rate fast miimon 100 ' 'xmit_hash_policy layer2+3') % (bond_name), 'ip add add %s/%s dev %s' % (orig_ip, self.NETPREFIX, bond_name), 'ip link set %s up' % bond_name)) # Add bond members for bond_member in bond_members: self.quiet_commands(host, ('ip link set dev %s master %s' % (bond_member, bond_name), )) bond_index += 1 # Return the ports to UP for dp_i, ports in self.host_port_maps[host_id].items(): for port in ports: self.set_port_up(port, self.topo.dpids_by_id[dp_i]) def setup_intervlan_host_routes(self): """Configure host routes between hosts that belong on routed VLANs""" if self.configuration_options['routers']: for src in self.host_information: src_host = self.host_information[src]['host'] src_vlan = self.host_information[src]['vlan'] src_ip = self.host_information[src]['ip'] for dst in self.host_information: if src != dst: dst_host = self.host_information[dst]['host'] dst_vlan = self.host_information[dst]['vlan'] dst_ip = self.host_information[dst]['ip'] if src_vlan != dst_vlan and self.is_routed_vlans( src_vlan, dst_vlan): src_faucet_vip = self.faucet_vips[src_vlan] dst_faucet_vip = self.faucet_vips[dst_vlan] self.add_host_route(src_host, dst_ip, src_faucet_vip.ip) self.add_host_route(dst_host, src_ip, dst_faucet_vip.ip) def debug(self): """Print additional information when debugging""" try: super(FaucetTopoTestBase, self).debug() except Exception: pprint.pprint(self.host_information) raise def verify_no_cable_errors(self): """Check that prometheus does not detect any stack cabling errors on all DPs""" for i, name in self.topo.switches_by_id.items(): dpid = self.dpids[i] labels = {'dp_id': '0x%x' % int(dpid), 'dp_name': name} self.assertEqual( 0, self.scrape_prometheus_var(var='stack_cabling_errors_total', labels=labels, default=None, verify_consistent=True)) self.assertGreater( self.scrape_prometheus_var(var='stack_probes_received_total', labels=labels, verify_consistent=True), 0) def verify_stack_hosts(self, verify_bridge_local_rule=True, retries=3): """Verify hosts with stack LLDP messages""" lldp_cap_files = [] for host in self.hosts_name_ordered(): lldp_cap_file = os.path.join(self.tmpdir, '%s-lldp.cap' % host) lldp_cap_files.append(lldp_cap_file) host.cmd( timeout_cmd( 'tcpdump -U -n -c 1 -i %s -w %s ether proto 0x88CC and not ether src %s &' % (host.defaultIntf(), host.MAC(), lldp_cap_file), 60)) # should not flood LLDP from hosts self.verify_lldp_blocked(self.hosts_name_ordered()) # hosts should see no LLDP probes self.verify_empty_caps(lldp_cap_files) if verify_bridge_local_rule: # Verify 802.1x flood block triggered. for dpid in self.dpids: self.wait_nonzero_packet_count_flow( {'dl_dst': '01:80:c2:00:00:00/ff:ff:ff:ff:ff:f0'}, dpid=dpid, table_id=self._FLOOD_TABLE, ofa_match=False) self.retry_net_ping(retries=retries) def stack_port_status(self, dpid, dp_name, port_no): """Return the status of a stack port from prometheus""" labels = self.port_labels(port_no) labels.update({'dp_id': '0x%x' % int(dpid), 'dp_name': dp_name}) return self.scrape_prometheus_var('port_stack_state', labels=labels, default=None, dpid=dpid, verify_consistent=True) def wait_for_stack_port_status(self, dpid, dp_name, port_no, status, timeout=25): """Wait until prometheus detects a stack port has a certain status""" labels = self.port_labels(port_no) labels.update({'dp_id': '0x%x' % int(dpid), 'dp_name': dp_name}) if not self.wait_for_prometheus_var('port_stack_state', status, labels=labels, default=None, dpid=False, timeout=timeout): self.fail( 'did not get expected dpid %x port %u port_stack_state %u' % (int(dpid), port_no, status)) def one_stack_port_down(self, dpid, dp_name, port): """Set a stack port down and wait for prometheus to detect the change""" self.set_port_down(port, dpid, wait=False) self.wait_for_stack_port_status(dpid, dp_name, port, 4) def one_stack_port_up(self, dpid, dp_name, port): """Set a stack port up and wait for prometheus to detect the change""" self.set_port_up(port, dpid, wait=False) self.wait_for_stack_port_status(dpid, dp_name, port, 3) def verify_stack_up(self, prop=1.0, timeout=25): """Verify all stack ports are up""" for _ in range(timeout): links = 0 links_up = 0 for link, ports in self.link_port_maps.items(): if link[0] in self.IGNORED_SWITCHES or link[ 1] in self.IGNORED_SWITCHES: continue for port in ports: dpid = self.topo.dpids_by_id[link[0]] name = self.topo.switches_by_id[link[0]] status = self.stack_port_status(dpid, name, port) links += 1 if status == 3: # STACK_STATE_UP links_up += 1 prop_up = links_up / links if prop_up >= prop: return self.fail('not enough links up: %f / %f' % (links_up, links)) def verify_stack_down(self): """Verify all stack ports are down""" links = 0 links_down = 0 for link, ports in self.link_port_maps.items(): for port in ports: dpid = self.topo.dpids_by_id[link[0]] name = self.topo.switches_by_id[link[0]] status = self.stack_port_status(dpid, name, port) links += 1 if status != 3: links_down += 1 self.assertEqual(links, links_down, 'Not all links DOWN') def verify_one_stack_down(self, stack_offset_port, coldstart=False): """Test conditions when one stack port is down""" self.retry_net_ping() stack_link = None count = 0 for sport, link in self.topo.ports[ self.topo.switches_by_id[0]].items(): if self.topo.isSwitch(link[0]): if count == stack_offset_port: stack_link = (sport, link[1]) break count += 1 stack_port, remote_stack_port = stack_link # pytype: disable=attribute-error self.set_port_down(stack_port, wait=False) # self.dpids[1] is the intermediate switch. self.set_port_down(remote_stack_port, self.dpids[1], wait=False) # test case where one link is down when coldstarted. if coldstart: self.coldstart_conf() self.verify_stack_up(prop=0.75) self.verify_stack_hosts(verify_bridge_local_rule=False) # Broadcast works, and first switch doesn't see broadcast packet ins from stack. packet_in_before_broadcast = self.scrape_prometheus_var( 'of_vlan_packet_ins') self.verify_broadcast() packet_in_after_broadcast = self.scrape_prometheus_var( 'of_vlan_packet_ins') self.assertEqual(packet_in_before_broadcast, packet_in_after_broadcast) self.verify_no_cable_errors() def verify_no_arp_storm(self, ping_host, tcpdump_host): """Check that there is no excess ARP packets in the network""" switch_to_switch_links = 0 for link in self.topo.links(): src_node, dst_node = link if self.topo.isSwitch(src_node): if self.topo.isSwitch(dst_node): switch_to_switch_links += 1 num_arp_expected = switch_to_switch_links * 2 tcpdump_filter = 'arp and ether src %s' % ping_host.MAC() tcpdump_txt = self.tcpdump_helper( tcpdump_host, tcpdump_filter, [ lambda: ping_host.cmd('arp -d %s' % tcpdump_host.IP()), lambda: ping_host.cmd('ping -c1 %s' % tcpdump_host.IP()) ], packets=(num_arp_expected + 1)) num_arp_received = len( re.findall( 'who-has %s tell %s' % (tcpdump_host.IP(), ping_host.IP()), tcpdump_txt)) self.assertTrue(num_arp_received) self.assertLessEqual(num_arp_received, num_arp_expected) def verify_stack_has_no_loop(self): """Ping between first and last hosts (by name) then verify there is no broadcast storm""" for ping_host, tcpdump_host in ((self.hosts_name_ordered()[0], self.hosts_name_ordered()[-1]), (self.hosts_name_ordered()[-1], self.hosts_name_ordered()[0])): self.verify_no_arp_storm(ping_host, tcpdump_host) def verify_all_stack_hosts(self): """Test conditions for stack hosts""" for _ in range(2): self.verify_stack_up() self.verify_no_cable_errors() self.verify_stack_hosts() self.verify_traveling_dhcp_mac() self.verify_unicast_not_looped() self.verify_no_bcast_to_self() self.verify_stack_has_no_loop() self.flap_all_switch_ports() def verify_tunnel_established(self, src_host, dst_host, other_host, packets=3, dpid=None): """Verify ICMP packets tunnelled from src to dst.""" icmp_match = {'eth_type': IPV4_ETH, 'ip_proto': 1} self.wait_until_matching_flow(icmp_match, table_id=self._PORT_ACL_TABLE, ofa_match=False, dpid=dpid) tcpdump_text = self.tcpdump_helper( dst_host, 'icmp[icmptype] == 8', [ # need to set static ARP as only ICMP is tunnelled. lambda: src_host.cmd('arp -s %s %s' % (other_host.IP(), other_host.MAC())), lambda: src_host.cmd('ping -c%u -t1 %s' % (packets, other_host.IP())) ], packets=1, timeout=(packets + 1), ) self.wait_nonzero_packet_count_flow(icmp_match, table_id=self._PORT_ACL_TABLE, ofa_match=False, dpid=dpid) self.assertTrue( re.search('%s: ICMP echo request' % other_host.IP(), tcpdump_text), 'Tunnel was not established') def verify_one_broadcast(self, from_host, to_hosts): """Verify host connectivity via broadcast""" self.assertGreater(len(to_hosts), 1, 'Testing only one ext host is not useful') received_broadcasts = [] for to_host in to_hosts: if self.verify_broadcast(hosts=(from_host, to_host), broadcast_expected=None): received_broadcasts.append(to_host) received_names = {host.name: host for host in received_broadcasts} self.assertEqual( len(received_broadcasts), 1, 'Received not exactly one broadcast from %s: %s' % (from_host.name, received_names)) def map_int_ext_hosts(self): """ Obtains a list of the interal hosts, the external hosts and a dictionary of the internal and external hosts for each DP by DP name Returns int_hosts, ext_hosts, dp_hosts """ int_hosts = [] ext_hosts = [] dp_hosts = { self.topo.switches_by_id[dp_index]: ([], []) for dp_index in range(self.NUM_DPS) } for host_id, options in self.configuration_options[ 'host_options'].items(): host = self.host_information[host_id]['host'] if options.get('loop_protect_external', False): ext_hosts.append(host) int_or_ext = 1 else: int_hosts.append(host) int_or_ext = 0 for dp_i in self.host_port_maps[host_id].keys(): switch = self.topo.switches_by_id[dp_i] if isinstance(dp_hosts[switch][int_or_ext], list): dp_hosts[switch][int_or_ext].append(host) return set(int_hosts), set(ext_hosts), dp_hosts def verify_protected_connectivity(self): """ Checks: - All internal hosts can reach other internal hosts - All internal hosts can reach exactly one external host - All external hosts cannot flood to each other - All external hosts can reach the internal hosts """ self.verify_stack_up() int_hosts, ext_hosts, _ = self.map_int_ext_hosts() for int_host in int_hosts: # All internal hosts can reach other internal hosts. for other_int_host in int_hosts - {int_host}: self.verify_broadcast(hosts=(int_host, other_int_host), broadcast_expected=True) self.one_ipv4_ping(int_host, other_int_host.IP()) # All internal hosts can reach exactly one external host. self.verify_one_broadcast(int_host, ext_hosts) for ext_host in ext_hosts: # All external hosts can reach internal hosts. for int_host in int_hosts: self.verify_broadcast(hosts=(ext_host, int_host), broadcast_expected=True) self.one_ipv4_ping(ext_host, int_host.IP()) # All external hosts cannot flood to each other. for other_ext_host in ext_hosts - {ext_host}: self.verify_broadcast(hosts=(ext_host, other_ext_host), broadcast_expected=False) def set_externals_state(self, dp_name, externals_up): """Set the port up/down state of all external ports on a switch""" dp_conf = self._get_faucet_conf()['dps'][dp_name] for port_num, port_conf in dp_conf['interfaces'].items(): if port_conf.get('loop_protect_external'): if externals_up: self.set_port_up(port_num, dp_conf.get('dp_id')) else: self.set_port_down(port_num, dp_conf.get('dp_id')) def validate_with_externals_down(self, dp_name): """Check situation when all externals on a given dp are down""" self.set_externals_state(dp_name, False) self.verify_protected_connectivity() self.set_externals_state(dp_name, True) def validate_with_externals_down_fails(self, dp_name): """Faucet code is not currently correct, so expect to fail.""" # TODO: Fix faucet so the test inversion is no longer required. asserted = False try: self.validate_with_externals_down(dp_name) except AssertionError: asserted = True self.assertTrue(asserted, 'Did not fail as expected for %s' % dp_name) def verify_intervlan_routing(self): """Verify intervlan routing but for LAG host use bond interface""" for src in self.host_information: for dst in self.host_information: if dst > src: self.check_host_connectivity_by_id(src, dst) def check_host_connectivity_by_id(self, src_id, dst_id): """Ping from src to dst with host_id parameters if they should be able to""" src_host, src_ip, _, src_vlan, src_bond, _ = self.host_information[ src_id].values() dst_host, dst_ip, _, dst_vlan, dst_bond, _ = self.host_information[ dst_id].values() connectivity = src_vlan == dst_vlan or self.is_routed_vlans( src_vlan, dst_vlan) if self.is_routed_vlans(src_vlan, dst_vlan): src_vip = self.faucet_vips[src_vlan] dst_vip = self.faucet_vips[dst_vlan] self.host_ping(src_host, src_vip.ip, src_bond) # pytype: disable=attribute-error self.host_ping(dst_host, dst_vip.ip, dst_bond) # pytype: disable=attribute-error if connectivity: self.host_ping(src_host, dst_ip.ip, src_bond) # pytype: disable=attribute-error self.host_ping(dst_host, src_ip.ip, dst_bond) # pytype: disable=attribute-error def is_routed_vlans(self, vlan_a, vlan_b): """Return true if the two vlans share a router""" if self.configuration_options['routers']: for vlans in self.configuration_options['routers'].values(): if (vlan_a in vlans and vlan_b in vlans): return True return False def bcast_dst_blocked_helper(self, port, first_host, second_host, success_re, retries): """Helper for checking broadcast destination has been blocked""" tcpdump_filter = 'udp and ether src %s and ether dst %s' % ( first_host.MAC(), "ff:ff:ff:ff:ff:ff") target_addr = str(self.faucet_vips[0].network.broadcast_address) for _ in range(retries): tcpdump_txt = self.tcpdump_helper( second_host, tcpdump_filter, [ partial(first_host.cmd, ('date | socat - udp-datagram:%s:%d,broadcast' % (target_addr, port))) ], packets=1) if re.search(success_re, tcpdump_txt): return True time.sleep(1) return False def get_expected_synced_states(self, host_id): """Return the list of regex string for the expected sync state of a LACP LAG connection""" synced_state_list = [] oper_key = self.configuration_options['host_options'][host_id]['lacp'] lacp_ports = [ port for ports in self.host_information[host_id]['ports'].values() for port in ports ] for port in lacp_ports: synced_state_txt = r""" Slave Interface: \S+ MII Status: up Speed: \d+ Mbps Duplex: full Link Failure Count: \d+ Permanent HW addr: \S+ Slave queue ID: 0 Aggregator ID: \d+ Actor Churn State: monitoring Partner Churn State: monitoring Actor Churned Count: \d+ Partner Churned Count: \d+ details actor lacp pdu: system priority: 65535 system mac address: 0e:00:00:00:00:99 port key: \d+ port priority: 255 port number: \d+ port state: 63 details partner lacp pdu: system priority: 65535 system mac address: 0e:00:00:00:00:01 oper key: %d port priority: 255 port number: %d port state: 62 """.strip() % (oper_key, port) synced_state_list.append(synced_state_txt) return synced_state_list def prom_lacp_up_ports(self, dpid): """Get the number of up LAG ports according to Prometheus for a dpid""" lacp_up_ports = 0 for host_id, options in self.configuration_options[ 'host_options'].items(): # Find LACP hosts for key in options.keys(): if key == 'lacp': # Is LACP host for dp_i, ports in self.host_port_maps[host_id].items(): if dpid == self.topo.dpids_by_id[dp_i]: # Host has links to dpid for port in ports: # Obtain up LACP ports for that dpid port_labels = self.port_labels(port) lacp_state = self.scrape_prometheus_var( 'port_lacp_state', port_labels, default=0, dpid=dpid, verify_consistent=True) lacp_up_ports += 1 if lacp_state == 3 else 0 return lacp_up_ports def verify_num_lag_up_ports(self, expected_up_ports, dpid): """Checks to see if Prometheus has the expected number of up LAG ports on the specified DP""" for _ in range(self.LACP_TIMEOUT * 10): if self.prom_lacp_up_ports(dpid) == expected_up_ports: return time.sleep(1) self.assertEqual(self.prom_lacp_up_ports(dpid), expected_up_ports) def require_linux_bond_up(self, host_id): """Checks to see if the host has properly formed into a bonded state""" synced_state_list = self.get_expected_synced_states(host_id) host = self.host_information[host_id]['host'] bond_name = self.host_information[host_id]['bond'] for _ in range(self.LACP_TIMEOUT * 2): result = host.cmd('cat /proc/net/bonding/%s|sed "s/[ \t]*$//g"' % bond_name) result = '\n'.join([line.rstrip() for line in result.splitlines()]) with open(os.path.join(self.tmpdir, 'bonding-state.txt'), 'w') as state_file: state_file.write(result) matched_all = True for state_txt in synced_state_list: if not re.search(state_txt, result): matched_all = False break if matched_all: return time.sleep(1) synced_state_txt = r"""""" for state_txt in synced_state_list: synced_state_txt += state_txt + "\n\n" synced_state_txt.strip() self.assertFalse( re.search(synced_state_txt, result), msg='LACP did not synchronize: %s\n\nexpected:\n\n%s' % (result, synced_state_txt)) def verify_lag_connectivity(self, host_id): """Verify LAG connectivity""" lacp_ports = self.host_port_maps[host_id] # All ports down for dp_i, ports in lacp_ports.items(): dpid = self.topo.dpids_by_id[dp_i] for port in ports: self.set_port_down(port, dpid) self.verify_num_lag_up_ports(0, dpid) # Pick a port to set up up_dp = random.choice(list(lacp_ports.keys())) up_dpid = self.topo.dpids_by_id[up_dp] up_port = random.choice(lacp_ports[up_dp]) self.set_port_up(up_port, up_dpid) self.verify_num_lag_up_ports(1, up_dpid) # Ensure connectivity with one port self.verify_lag_host_connectivity() # Set the other ports to UP for dp_i, ports in lacp_ports.items(): dpid = self.topo.dpids_by_id[dp_i] for port in ports: self.set_port_up(port, dpid) self.verify_num_lag_up_ports(len(ports), dpid) # Ensure connectivity with all ports self.require_linux_bond_up(host_id) self.verify_lag_host_connectivity() # Tear down first port self.set_port_down(up_port, up_dpid) self.verify_num_lag_up_ports(len(lacp_ports[up_dp]) - 1, up_dpid) # Ensure connectivity with new ports only self.verify_lag_host_connectivity() def verify_lag_host_connectivity(self): """Verify LAG hosts can connect to any other host using the interface""" # Find all LACP hosts for lacp_id, host_options in self.configuration_options[ 'host_options'].items(): if 'lacp' in host_options: # Found LACP host for dst_id in self.host_information: if lacp_id == dst_id: continue # Test connectivity to any other host (might be another LAG host) self.check_host_connectivity_by_id(lacp_id, dst_id)
def build_net(self, host_links=None, host_vlans=None, switch_links=None, link_vlans=None, mininet_host_options=None, n_vlans=1, dp_options=None, host_options=None, link_options=None, vlan_options=None, routers=None, router_options=None, include=None, include_optional=None): """ Args: host_links (dict): Host index key to list of dp indices host_vlans (dict): Host index key to vlan index/indices switch_links (list): List of link tuples of switch indices (u, v) link_vlans (dict): Link tuple of switch indices (u, v) mapping to vlans mininet_host_options (dict): Host index map to additional mininet host options n_vlans (int): Number of VLANs to generate dp_options (dict): Additional options for each DP, keyed by DP index host_options (dict): Additional options for each host, keyed by host index link_options (dict): Additional options for each link, keyed by indices tuple (u, v) vlan_options (dict): Additional options for each VLAN, keyed by vlan index routers (dict): Router index to list of VLANs in the router router_options (dict): Additional options for each router, keyed by router index include (list): Files to include using the the Faucet config 'include' key include_optional (list): File to include using the Faucet config 'include_optional' key """ self.topo = FaucetTopoGenerator(self.OVS_TYPE, self.ports_sock, self._test_name(), self.NUM_DPS, self.DESCENDING_DPIDS, host_links=host_links, host_vlans=host_vlans, switch_links=switch_links, link_vlans=link_vlans, hw_dpid=self.hw_dpid, hw_ports=self.switch_map, port_order=self.port_order, start_port=self.start_port, host_options=mininet_host_options) self.dpids = self.topo.get_dpids() self.dpid = self.dpids[0] # host_port_maps = {host_n: {switch_n: [ports, ...], ...}, ...} # link_port_maps = {(switch_n, switch_m): [ports, ...], ...} self.port_maps, self.host_port_maps, self.link_port_maps = self.topo.create_port_maps( ) self.port_map = self.port_maps[self.dpid] dpid_names = {} for i in self.topo.switches_by_id: dpid = self.topo.dpids_by_id[i] name = self.topo.switches_by_id[i] dpid_names[dpid] = name self.set_dpid_names(dpid_names) self.configuration_options = { 'acl_options': self.acls(), 'dp_options': dp_options, 'host_options': host_options, 'link_options': link_options, 'vlan_options': vlan_options, 'routers': routers, 'router_options': router_options, 'include': include, 'include_optional': include_optional, 'ignored_switches': self.IGNORED_SWITCHES } self.CONFIG = self.topo.get_config( n_vlans, **self.configuration_options, ) self.mininet_host_options = mininet_host_options self.n_vlans = n_vlans self.host_vlans = host_vlans self.link_vlans = link_vlans