def __init__(self, bridge, test_vlans, trunk_iface, event_queue_append): self._ovs_helper = OvsHelper() self._logger = get_logger('device_discovery') self._bridge = bridge self._test_vlans = test_vlans self._polling_timer = None self._fdb_snapshot = None self._trunk_iface = trunk_iface self._trunk_ofport = None self._event_queue_append = event_queue_append
def __init__(self, target, tunnel_ip, ovs_bridge): self._logger = get_logger('daqclient') self._logger.info('Using target %s', target) self._channel = grpc.insecure_channel(target) self._stub = None self._mac_sessions = {} self._lock = threading.Lock() self._tunnel_ip = tunnel_ip self._endpoint_handler = OvsHelper() self._ovs_bridge = ovs_bridge # Assigned VLAN is always set to 0 for non-FOT DAQ Client self._assigned_vlan = 0
class NetworkHelper(): """Network setup helper for device coupler""" def __init__(self, trunk_iface, bridge, test_vlans): self._ovs_helper = OvsHelper() self._logger = get_logger('networkhelper') self._trunk_iface = trunk_iface self._bridge = bridge self._test_vlans = list(range(test_vlans[0], test_vlans[1] + 1)) def setup(self): """Setup n/w""" self._logger.info('Setting up device coupler network.') self._setup_ovs_bridge() def cleanup(self): """Clean up n/w""" self._delete_ovs_bridge() self._logger.info('Cleaned up device coupler network.') def _setup_ovs_bridge(self): self._ovs_helper.create_ovs_bridge(self._bridge) self._ovs_helper.add_iface_to_bridge(self._bridge, self._trunk_iface) self._ovs_helper.set_trunk_vlan(self._trunk_iface, self._test_vlans) def _delete_ovs_bridge(self): self._ovs_helper.delete_ovs_bridge(self._bridge) def get_ovs_bridge(self): return self._bridge
def setup(self): """Setup device coupler""" self._ovs_helper = OvsHelper() self._network_helper = NetworkHelper(self._trunk_iface, self._OVS_BRIDGE, self._test_vlans) self._network_helper.setup() self._event_queue = Queue() self._workers_executor = ThreadPoolExecutor( max_workers=self._WORKER_COUNT) self._device_discovery = DeviceDiscovery(self._OVS_BRIDGE, self._test_vlans, self._trunk_iface, self.add_event_to_queue) self._source_ip = self._ovs_helper.get_interface_ip() target_str = '%s:%s' % (self._daq_grpc_ip, self._daq_grpc_port) self._daq_client = DAQClient(target_str, self._source_ip, self._OVS_BRIDGE)
def __init__(self, trunk_iface, bridge, test_vlans): self._ovs_helper = OvsHelper() self._logger = get_logger('networkhelper') self._trunk_iface = trunk_iface self._bridge = bridge self._test_vlans = list(range(test_vlans[0], test_vlans[1] + 1))
class DeviceCoupler(): """Main for device coupler""" _WORKER_TIMEOUT = 10 _WORKER_COUNT = 3 _OVS_BRIDGE = "dev_br0" def __init__(self, config): self._logger = get_logger('device_coupler') self._test_vlans = (config.run_trigger.vlan_start, config.run_trigger.vlan_end) self._trunk_iface = config.switch_setup.data_intf self._daq_grpc_ip = config.run_trigger.runner_service_ip self._daq_grpc_port = config.device_reporting.server_port self._network_helper = None self._device_discovery = None self._daq_client = None self._event_queue = None self._workers_executor = None self._source_ip = None self._running = None def setup(self): """Setup device coupler""" self._ovs_helper = OvsHelper() self._network_helper = NetworkHelper(self._trunk_iface, self._OVS_BRIDGE, self._test_vlans) self._network_helper.setup() self._event_queue = Queue() self._workers_executor = ThreadPoolExecutor( max_workers=self._WORKER_COUNT) self._device_discovery = DeviceDiscovery(self._OVS_BRIDGE, self._test_vlans, self._trunk_iface, self.add_event_to_queue) self._source_ip = self._ovs_helper.get_interface_ip() target_str = '%s:%s' % (self._daq_grpc_ip, self._daq_grpc_port) self._daq_client = DAQClient(target_str, self._source_ip, self._OVS_BRIDGE) def start(self): """Start device coupler""" self._running = True self._device_discovery.start() self._daq_client.start() self._logger.info('Starting %s workers', self._WORKER_COUNT) for index in range(self._WORKER_COUNT): self._workers_executor.submit(self._process_event_queue) def cleanup(self): """Clean up device coupler""" self._running = False self._workers_executor.shutdown() self._daq_client.stop() self._device_discovery.cleanup() self._network_helper.cleanup() self._logger.info('Cleanup complete') def add_event_to_queue(self, event): """Add event to queue for processing""" self._event_queue.put(event) def _process_event_queue(self): while self._running: try: event = self._event_queue.get(timeout=self._WORKER_TIMEOUT) self._logger.info(event) if event.event_type == DiscoveryEventType.DISCOVERY: port = self._get_device_port(event.vlan) self._daq_client.process_device_discovery( event.mac, event.vlan, port) else: self._daq_client.process_device_expiry(event.mac) except Empty: # Worker timeout. Do nothing pass def _get_device_port(self, vlan): """Mapping for VLAN to ports uses first VLAN of accepted test VLAN range.""" # TODO: Change once device_coupler can reliably access switch port number return vlan - self._test_vlans[0] + 1
def test_ovs_bridge_vlan(self): """Test adding interfaces to OVS bridge with VLANs""" ovs = OvsHelper() bridge = 'test_br' ovs.delete_ovs_bridge(bridge) ovs.create_ovs_bridge(bridge) for index in range(1, 5): self._create_netns_with_veth_pair(index) iface = 'dev%s' % index tag = 200 + index % 2 ovs.add_iface_to_bridge(bridge, iface) ovs.set_native_vlan(iface, tag) retcode, _, _ = ovs._run_shell_no_raise( 'sudo ip netns exec vnet1 ping -c 3 10.1.1.3') self.assertEqual(retcode, 0) retcode, _, _ = ovs._run_shell_no_raise( 'sudo ip netns exec vnet1 ping -c 3 10.1.1.4') self.assertEqual(retcode, 1) for index in range(1, 5): vnet = 'vnet%s' % index self._delete_netns(vnet) ovs.delete_ovs_bridge(bridge)
def _delete_netns(self, vnet): ovs = OvsHelper() ovs._run_shell('sudo ip netns del %s' % vnet)
def _create_netns_with_veth_pair(self, index): ovs = OvsHelper() iface1 = 'dev%s' % index iface2 = 'netns0' vnet = 'vnet%s' % index ovs.create_veth_pair(iface1, iface2) ovs._run_shell('sudo ip netns add %s' % vnet) ovs._run_shell('sudo ip link set %s netns %s' % (iface2, vnet)) ovs._run_shell('sudo ip -n %s addr add 10.1.1.%s/24 dev %s' % (vnet, index, iface2)) ovs._run_shell('sudo ip -n %s link set %s up' % (vnet, iface2))
class DeviceDiscovery(): """Device discovery helper for device coupler""" _POLLING_INTERVAL = 3 def __init__(self, bridge, test_vlans, trunk_iface, event_queue_append): self._ovs_helper = OvsHelper() self._logger = get_logger('device_discovery') self._bridge = bridge self._test_vlans = test_vlans self._polling_timer = None self._fdb_snapshot = None self._trunk_iface = trunk_iface self._trunk_ofport = None self._event_queue_append = event_queue_append def start(self): """Setup device discovery""" self._trunk_ofport = self._ovs_helper.get_interface_ofport( self._trunk_iface) self._fdb_snapshot = set() self._polling_timer = HeartbeatScheduler(self._POLLING_INTERVAL) self._polling_timer.add_callback(self.poll_forwarding_table) self._polling_timer.start() def cleanup(self): """Clean up device discovery""" if self._polling_timer: self._polling_timer.stop() self._logger.info('Clean up complete.') def _process_entry(self, event): # Only process events for test vlans in config and on trunk port vlan_in_range = event.vlan >= self._test_vlans[ 0] and event.vlan <= self._test_vlans[1] if event.port == self._trunk_ofport and vlan_in_range: self._logger.info('Processing event: %s', event) self._event_queue_append(event) def poll_forwarding_table(self): """Poll forwarding table and determine devices learnt/expired""" fdb_table = set(self._ovs_helper.get_forwarding_table(self._bridge)) self._logger.info('Polling fdb on %s:\n %s', self._bridge, fdb_table) expired = self._fdb_snapshot - fdb_table discovered = fdb_table - self._fdb_snapshot for entry in expired: event = self._build_device_discovery_event(entry, expire=True) self._process_entry(event) for entry in discovered: event = self._build_device_discovery_event(entry) self._process_entry(event) self._fdb_snapshot = fdb_table def _build_device_discovery_event(self, entry, expire=False): port, vlan, mac = entry event = DeviceDiscoveryEvent() event.port = int(port) event.vlan = int(vlan) event.mac = mac event.event_type = DiscoveryEventType.EXPIRY if expire else DiscoveryEventType.DISCOVERY return event
class DAQClient(): """gRPC client to send device result""" def __init__(self, target, tunnel_ip, ovs_bridge): self._logger = get_logger('daqclient') self._logger.info('Using target %s', target) self._channel = grpc.insecure_channel(target) self._stub = None self._mac_sessions = {} self._lock = threading.Lock() self._tunnel_ip = tunnel_ip self._endpoint_handler = OvsHelper() self._ovs_bridge = ovs_bridge # Assigned VLAN is always set to 0 for non-FOT DAQ Client self._assigned_vlan = 0 def start(self): """Start the client handler""" grpc.channel_ready_future( self._channel).result(timeout=CONNECT_TIMEOUT_SEC) self._stub = SessionServerStub(self._channel) def stop(self): """Stop client handler""" for mac in self._mac_sessions.keys(): self._disconnect(mac) def _connect(self, mac, vlan, port): self._logger.info('Connecting %s with VLAN %s', mac, vlan) session_params = SessionParams() session_params.device_mac = mac session_params.device_vlan = vlan session_params.device_port = port session_params.assigned_vlan = self._assigned_vlan session_params.endpoint.ip = self._tunnel_ip or DEFAULT_SERVER_ADDRESS session = self._stub.StartSession(session_params) thread = threading.Thread( target=lambda: self._run_test_session(mac, session)) thread.start() self._logger.info('Connection of %s with VLAN %s succeeded', mac, vlan) return session def _disconnect(self, mac): with self._lock: session = self._mac_sessions.get(mac, {}).get('session') if session: session.cancel() mac_session = self._mac_sessions.pop(mac) index = mac_session['index'] interface = "vxlan%s" % index self._endpoint_handler.remove_vxlan_endpoint( interface, self._ovs_bridge) self._logger.info('Session terminated for %s', mac) else: self._logger.warning( 'Attempt to disconnect unconnected device %s', mac) def _is_session_running(self, mac, progress): result = self._process_session_progress(mac, progress) return result not in ('PASSED', 'FAILED', 'ERROR') def _process_session_progress(self, mac, progress): endpoint = progress.endpoint result_code = progress.result.code assert not (endpoint.ip and result_code), 'both endpoint.ip and result.code defined' if result_code: result_name = SessionResult.ResultCode.Name(result_code) self._logger.info('Device report %s as %s', mac, result_name) return result_name if endpoint.ip: self._logger.info('Device report %s endpoint ip: %s)', mac, endpoint.ip) # TODO: Change the way indexes work. Check for VXLAN port being sent index = endpoint.vni device = self._mac_sessions[mac] device['index'] = index interface = "vxlan%s" % index self._endpoint_handler.remove_vxlan_endpoint( interface, self._ovs_bridge) self._endpoint_handler.create_vxlan_endpoint( interface, endpoint.ip, index) self._endpoint_handler.add_iface_to_bridge( self._ovs_bridge, interface, tag=device['device_vlan']) return None def _run_test_session(self, mac, session): try: for progress in session: if not self._is_session_running(mac, progress): break self._logger.info('Progress complete for %s', mac) except Exception as e: self._logger.error('Progress exception: %s', e) self._logger.error('Traceback: %s', traceback.format_exc()) self._disconnect(mac) def _initiate_test_session(self, mac, device_vlan, port): if mac in self._mac_sessions: vlan_in_session = ( device_vlan == self._mac_sessions[mac]['device_vlan']) port_in_session = (port == self._mac_sessions[mac]['device_port']) if vlan_in_session and port_in_session: self._logger.info( 'Test session for %s already exists. Ignoring.', mac) return self._logger.info( 'MAC learned on VLAN %s. Terminating current session.', device_vlan) self._disconnect(mac) self._logger.info('Initiating test session for %s on VLAN %s', mac, device_vlan) if device_vlan: self._mac_sessions[mac] = {} self._mac_sessions[mac]['device_vlan'] = device_vlan self._mac_sessions[mac]['device_port'] = port self._mac_sessions[mac]['session'] = self._connect( mac, device_vlan, port) self._logger.info('Initiated test session %s', self._mac_sessions[mac]) def process_device_discovery(self, mac, device_vlan, port): """Process discovery of device to be tested""" # TODO: End existing test session and start new one if discovered on another vlan with self._lock: self._initiate_test_session(mac, device_vlan, port) def process_device_expiry(self, mac): """Process expiry of device""" self._logger.info('Terminating session for %s', mac) self._disconnect(mac)