class OpenflowPortManager(Module): ''' Manage Ports from Openflow Protocol ''' service = True def __init__(self, server): Module.__init__(self, server) self.apiroutine = RoutineContainer(self.scheduler) self.apiroutine.main = self._manage_ports self.routines.append(self.apiroutine) self.managed_ports = {} self.createAPI(api(self.getports, self.apiroutine), api(self.getallports, self.apiroutine), api(self.getportbyno, self.apiroutine), api(self.waitportbyno, self.apiroutine), api(self.getportbyname, self.apiroutine), api(self.waitportbyname, self.apiroutine), api(self.resync, self.apiroutine) ) self._synchronized = False def _get_ports(self, connection, protocol, onup = False, update = True): ofdef = connection.openflowdef dpid = connection.openflow_datapathid vhost = connection.protocol.vhost add = [] try: if hasattr(ofdef, 'ofp_multipart_request'): # Openflow 1.3, use ofp_multipart_request to get ports for m in protocol.querymultipart(ofdef.ofp_multipart_request(type=ofdef.OFPMP_PORT_DESC), connection, self.apiroutine): yield m ports = self.managed_ports.setdefault((vhost, dpid), {}) for msg in self.apiroutine.openflow_reply: for p in msg.ports: add.append(p) ports[p.port_no] = p else: # Openflow 1.0, use features_request if onup: # Use the features_reply on connection setup reply = connection.openflow_featuresreply else: request = ofdef.ofp_msg() request.header.type = ofdef.OFPT_FEATURES_REQUEST for m in protocol.querywithreply(request): yield m reply = self.apiroutine.retvalue ports = self.managed_ports.setdefault((vhost, dpid), {}) for p in reply.ports: add.append(p) ports[p.port_no] = p if update: for m in self.apiroutine.waitForSend(OpenflowPortSynchronized(connection)): yield m for m in self.apiroutine.waitForSend(ModuleNotification(self.getServiceName(), 'update', datapathid = connection.openflow_datapathid, connection = connection, vhost = protocol.vhost, add = add, remove = [], reason = 'connected')): yield m except ConnectionResetException: pass except OpenflowProtocolException: pass def _get_existing_ports(self): for m in callAPI(self.apiroutine, 'openflowmanager', 'getallconnections', {'vhost':None}): yield m with closing(self.apiroutine.executeAll([self._get_ports(c, c.protocol, False, False) for c in self.apiroutine.retvalue if c.openflow_auxiliaryid == 0])) as g: for m in g: yield m self._synchronized = True for m in self.apiroutine.waitForSend(ModuleNotification(self.getServiceName(), 'synchronized')): yield m def _wait_for_sync(self): if not self._synchronized: yield (ModuleNotification.createMatcher(self.getServiceName(), 'synchronized'),) def _manage_ports(self): try: self.apiroutine.subroutine(self._get_existing_ports()) conn_update = ModuleNotification.createMatcher('openflowmanager', 'update') port_status = OpenflowAsyncMessageEvent.createMatcher(of13.OFPT_PORT_STATUS, None, 0) while True: yield (conn_update, port_status) if self.apiroutine.matcher is port_status: e = self.apiroutine.event m = e.message c = e.connection if (c.protocol.vhost, c.openflow_datapathid) in self.managed_ports: if m.reason == c.openflowdef.OFPPR_ADD: # A new port is added self.managed_ports[(c.protocol.vhost, c.openflow_datapathid)][m.desc.port_no] = m.desc self.scheduler.emergesend(ModuleNotification(self.getServiceName(), 'update', datapathid = c.openflow_datapathid, connection = c, vhost = c.protocol.vhost, add = [m.desc], remove = [], reason = 'add')) elif m.reason == c.openflowdef.OFPPR_DELETE: try: del self.managed_ports[(c.protocol.vhost, c.openflow_datapathid)][m.desc.port_no] self.scheduler.emergesend(ModuleNotification(self.getServiceName(), 'update', datapathid = c.openflow_datapathid, connection = c, vhost = c.protocol.vhost, add = [], remove = [m.desc], reason = 'delete')) except KeyError: pass elif m.reason == c.openflowdef.OFPPR_MODIFY: try: self.scheduler.emergesend(ModuleNotification(self.getServiceName(), 'modified', datapathid = c.openflow_datapathid, connection = c, vhost = c.protocol.vhost, old = self.managed_ports[(c.protocol.vhost, c.openflow_datapathid)][m.desc.port_no], new = m.desc, reason = 'modified')) except KeyError: self.scheduler.emergesend(ModuleNotification(self.getServiceName(), 'update', datapathid = c.openflow_datapathid, connection = c, vhost = c.protocol.vhost, add = [m.desc], remove = [], reason = 'add')) self.managed_ports[(c.protocol.vhost, c.openflow_datapathid)][m.desc.port_no] = m.desc else: e = self.apiroutine.event for c in e.remove: if c.openflow_auxiliaryid == 0 and (c.protocol.vhost, c.openflow_datapathid) in self.managed_ports: self.scheduler.emergesend(ModuleNotification(self.getServiceName(), 'update', datapathid = c.openflow_datapathid, connection = c, vhost = c.protocol.vhost, add = [], remove = list(self.managed_ports[(c.protocol.vhost, c.openflow_datapathid)].values()), reason = 'disconnected')) del self.managed_ports[(c.protocol.vhost, c.openflow_datapathid)] for c in e.add: if c.openflow_auxiliaryid == 0: self.apiroutine.subroutine(self._get_ports(c, c.protocol, True, True)) finally: self.scheduler.emergesend(ModuleNotification(self.getServiceName(), 'unsynchronized')) def getports(self, datapathid, vhost = ''): "Return all ports of a specifed datapath" for m in self._wait_for_sync(): yield m r = self.managed_ports.get((vhost, datapathid)) if r is None: self.apiroutine.retvalue = None else: self.apiroutine.retvalue = list(r.values()) def getallports(self, vhost = None): "Return all (datapathid, port, vhost) tuples, optionally filterd by vhost" for m in self._wait_for_sync(): yield m if vhost is None: self.apiroutine.retvalue = [(dpid, p, vh) for (vh, dpid),v in self.managed_ports.items() for p in v.values()] else: self.apiroutine.retvalue = [(dpid, p, vh) for (vh, dpid),v in self.managed_ports.items() if vh == vhost for p in v.values()] def getportbyno(self, datapathid, portno, vhost = ''): "Return port with specified portno" for m in self._wait_for_sync(): yield m self.apiroutine.retvalue = self._getportbyno(datapathid, portno, vhost) def _getportbyno(self, datapathid, portno, vhost = ''): ports = self.managed_ports.get((vhost, datapathid)) if ports is None: return None else: return ports.get(portno) def waitportbyno(self, datapathid, portno, timeout = 30, vhost = ''): for m in self._wait_for_sync(): yield m def waitinner(): ports = self.managed_ports.get((vhost, datapathid)) if ports is None: for m in callAPI(self.apiroutine, 'openflowmanager', 'waitconnection', {'datapathid': datapathid, 'vhost':vhost, 'timeout': timeout}): yield m c = self.apiroutine.retvalue ports = self.managed_ports.get((vhost, datapathid)) if ports is None: yield (OpenflowPortSynchronized.createMatcher(c),) ports = self.managed_ports.get((vhost, datapathid)) if ports is None: raise ConnectionResetException('Datapath %016x is not connected' % datapathid) if portno not in ports: yield (OpenflowAsyncMessageEvent.createMatcher(of13.OFPT_PORT_STATUS, datapathid, 0, _ismatch = lambda x: x.message.desc.port_no == portno),) self.apiroutine.retvalue = self.apiroutine.event.message.desc else: self.apiroutine.retvalue = ports[portno] for m in self.apiroutine.executeWithTimeout(timeout, waitinner()): yield m if self.apiroutine.timeout: raise OpenflowPortNotAppearException('Port %d does not appear on datapath %016x' % (portno, datapathid)) def getportbyname(self, datapathid, name, vhost = ''): "Return port with specified portno" for m in self._wait_for_sync(): yield m self.apiroutine.retvalue = self._getportbyname(datapathid, name, vhost) def _getportbyname(self, datapathid, name, vhost = ''): if not isinstance(name, bytes): name = _bytes(name) ports = self.managed_ports.get((vhost, datapathid)) if ports is None: return None else: for p in ports.values(): if p.name == name: return p return None def waitportbyname(self, datapathid, name, timeout = 30, vhost = ''): for m in self._wait_for_sync(): yield m if not isinstance(name, bytes): name = _bytes(name) def waitinner(): ports = self.managed_ports.get((vhost, datapathid)) if ports is None: for m in callAPI(self.apiroutine, 'openflowmanager', 'waitconnection', {'datapathid': datapathid, 'vhost':vhost, 'timeout': timeout}): yield m c = self.apiroutine.retvalue ports = self.managed_ports.get((vhost, datapathid)) if ports is None: yield (OpenflowPortSynchronized.createMatcher(c),) ports = self.managed_ports.get((vhost, datapathid)) if ports is None: raise ConnectionResetException('Datapath %016x is not connected' % datapathid) for p in ports.values(): if p.name == name: self.apiroutine.retvalue = p return yield (OpenflowAsyncMessageEvent.createMatcher(of13.OFPT_PORT_STATUS, datapathid, 0, _ismatch = lambda x: x.message.desc.name == name),) self.apiroutine.retvalue = self.apiroutine.event.message.desc for m in self.apiroutine.executeWithTimeout(timeout, waitinner()): yield m if self.apiroutine.timeout: raise OpenflowPortNotAppearException('Port %r does not appear on datapath %016x' % (name, datapathid)) def resync(self, datapathid, vhost = ''): ''' Resync with current ports ''' # Sometimes when the OpenFlow connection is very busy, PORT_STATUS message may be dropped. # We must deal with this and recover from it # Save current manged_ports if (vhost, datapathid) not in self.managed_ports: self.apiroutine.retvalue = None return else: last_ports = set(self.managed_ports[(vhost, datapathid)].keys()) add = set() remove = set() ports = {} for _ in range(0, 10): for m in callAPI(self.apiroutine, 'openflowmanager', 'getconnection', {'datapathid': datapathid, 'vhost':vhost}): yield m c = self.apiroutine.retvalue if c is None: # Disconnected, will automatically resync when reconnected self.apiroutine.retvalue = None return ofdef = c.openflowdef protocol = c.protocol try: if hasattr(ofdef, 'ofp_multipart_request'): # Openflow 1.3, use ofp_multipart_request to get ports for m in protocol.querymultipart(ofdef.ofp_multipart_request(type=ofdef.OFPMP_PORT_DESC), c, self.apiroutine): yield m for msg in self.apiroutine.openflow_reply: for p in msg.ports: ports[p.port_no] = p else: # Openflow 1.0, use features_request request = ofdef.ofp_msg() request.header.type = ofdef.OFPT_FEATURES_REQUEST for m in protocol.querywithreply(request): yield m reply = self.apiroutine.retvalue for p in reply.ports: ports[p.port_no] = p except ConnectionResetException: break except OpenflowProtocolException: break else: if (vhost, datapathid) not in self.managed_ports: self.apiroutine.retvalue = None return current_ports = set(self.managed_ports[(vhost, datapathid)]) # If a port is already removed remove.intersection_update(current_ports) # If a port is already added add.difference_update(current_ports) # If a port is not acquired, we do not add it acquired_keys = set(ports.keys()) add.difference_update(acquired_keys) # Add and remove previous added/removed ports current_ports.difference_update(remove) current_ports.update(add) # If there are changed ports, the changed ports may or may not appear in the acquired port list # We only deal with following situations: # 1. If both lack ports, we add them # 2. If both have additional ports, we remote them to_add = acquired_keys.difference(current_ports.union(last_ports)) to_remove = current_ports.intersection(last_ports).difference(acquired_keys) if not to_add and not to_remove and current_ports == last_ports: break else: add.update(to_add) remove.update(to_remove) current_ports.update(to_add) current_ports.difference_update(to_remove) last_ports = current_ports # Actual add and remove mports = self.managed_ports[(vhost, datapathid)] add_ports = [] remove_ports = [] for k in add: if k not in mports: add_ports.append(ports[k]) mports[k] = ports[k] for k in remove: try: oldport = mports.pop(k) except KeyError: pass else: remove_ports.append(oldport) for m in self.apiroutine.waitForSend(ModuleNotification(self.getServiceName(), 'update', datapathid = datapathid, connection = c, vhost = vhost, add = add_ports, remove = remove_ports, reason = 'resync')): yield m self.apiroutine.retvalue = None
class OVSDBPortManager(Module): ''' Manage Ports from OVSDB Protocol ''' service = True def __init__(self, server): Module.__init__(self, server) self.apiroutine = RoutineContainer(self.scheduler) self.apiroutine.main = self._manage_ports self.routines.append(self.apiroutine) self.managed_ports = {} self.managed_ids = {} self.monitor_routines = set() self.ports_uuids = {} self.wait_portnos = {} self.wait_names = {} self.wait_ids = {} self.bridge_datapathid = {} self.createAPI(api(self.getports, self.apiroutine), api(self.getallports, self.apiroutine), api(self.getportbyid, self.apiroutine), api(self.waitportbyid, self.apiroutine), api(self.getportbyname, self.apiroutine), api(self.waitportbyname, self.apiroutine), api(self.getportbyno, self.apiroutine), api(self.waitportbyno, self.apiroutine), api(self.resync, self.apiroutine)) self._synchronized = False def _get_interface_info(self, connection, protocol, buuid, interface_uuid, port_uuid): try: method, params = ovsdb.transact( 'Open_vSwitch', ovsdb.wait( 'Interface', [["_uuid", "==", ovsdb.uuid(interface_uuid)]], ["ofport"], [{ "ofport": ovsdb.oset() }], False, 5000), ovsdb.wait( 'Interface', [["_uuid", "==", ovsdb.uuid(interface_uuid)]], ["ofport"], [{ "ofport": -1 }], False, 0), ovsdb.wait( 'Interface', [["_uuid", "==", ovsdb.uuid(interface_uuid)]], ["ifindex"], [{ "ifindex": ovsdb.oset() }], False, 5000), ovsdb.select( 'Interface', [["_uuid", "==", ovsdb.uuid(interface_uuid)]], [ "_uuid", "name", "ifindex", "ofport", "type", "external_ids" ])) for m in protocol.querywithreply(method, params, connection, self.apiroutine): yield m r = self.apiroutine.jsonrpc_result[0] if 'error' in r: raise JsonRPCErrorResultException( 'Error while acquiring interface: ' + repr(r['error'])) r = self.apiroutine.jsonrpc_result[1] if 'error' in r: raise JsonRPCErrorResultException( 'Error while acquiring interface: ' + repr(r['error'])) r = self.apiroutine.jsonrpc_result[2] if 'error' in r: # Ignore this port because it is in an error state self.apiroutine.retvalue = [] return r = self.apiroutine.jsonrpc_result[3] if 'error' in r: raise JsonRPCErrorResultException( 'Error while acquiring interface: ' + repr(r['error'])) if not r['rows']: self.apiroutine.retvalue = [] return r0 = r['rows'][0] if r0['ofport'] < 0: # Ignore this port because it is in an error state self.apiroutine.retvalue = [] return r0['_uuid'] = r0['_uuid'][1] r0['ifindex'] = ovsdb.getoptional(r0['ifindex']) r0['external_ids'] = ovsdb.getdict(r0['external_ids']) if buuid not in self.bridge_datapathid: self.apiroutine.retvalue = [] return else: datapath_id = self.bridge_datapathid[buuid] if 'iface-id' in r0['external_ids']: eid = r0['external_ids']['iface-id'] r0['id'] = eid id_ports = self.managed_ids.setdefault((protocol.vhost, eid), []) id_ports.append((datapath_id, r0)) else: r0['id'] = None self.managed_ports.setdefault((protocol.vhost, datapath_id), []).append((port_uuid, r0)) notify = False if (protocol.vhost, datapath_id, r0['ofport']) in self.wait_portnos: notify = True del self.wait_portnos[(protocol.vhost, datapath_id, r0['ofport'])] if (protocol.vhost, datapath_id, r0['name']) in self.wait_names: notify = True del self.wait_names[(protocol.vhost, datapath_id, r0['name'])] if (protocol.vhost, r0['id']) in self.wait_ids: notify = True del self.wait_ids[(protocol.vhost, r0['id'])] if notify: for m in self.apiroutine.waitForSend( OVSDBPortUpNotification(connection, r0['name'], r0['ofport'], r0['id'], protocol.vhost, datapath_id, port=r0)): yield m self.apiroutine.retvalue = [r0] except JsonRPCProtocolException: self.apiroutine.retvalue = [] def _remove_interface_id(self, connection, protocol, datapath_id, port): eid = port['id'] eid_list = self.managed_ids.get((protocol.vhost, eid)) for i in range(0, len(eid_list)): if eid_list[i][1]['_uuid'] == port['_uuid']: del eid_list[i] break def _remove_interface(self, connection, protocol, datapath_id, interface_uuid, port_uuid): ports = self.managed_ports.get((protocol.vhost, datapath_id)) r = None if ports is not None: for i in range(0, len(ports)): if ports[i][1]['_uuid'] == interface_uuid: r = ports[i][1] if r['id']: self._remove_interface_id(connection, protocol, datapath_id, r) del ports[i] break if not ports: del self.managed_ports[(protocol.vhost, datapath_id)] return r def _remove_all_interface(self, connection, protocol, datapath_id, port_uuid, buuid): ports = self.managed_ports.get((protocol.vhost, datapath_id)) if ports is not None: removed_ports = [r for puuid, r in ports if puuid == port_uuid] not_removed_ports = [(puuid, r) for puuid, r in ports if puuid != port_uuid] ports[:len(not_removed_ports)] = not_removed_ports del ports[len(not_removed_ports):] for r in removed_ports: if r['id']: self._remove_interface_id(connection, protocol, datapath_id, r) if not ports: del self.managed_ports[(protocol.vhost, datapath_id)] return removed_ports if port_uuid in self.ports_uuids and self.ports_uuids[ port_uuid] == buuid: del self.ports_uuids[port_uuid] return [] def _update_interfaces(self, connection, protocol, updateinfo, update=True): """ There are several kinds of updates, they may appear together: 1. New bridge created (or from initial updateinfo). We should add all the interfaces to the list. 2. Bridge removed. Remove all the ports. 3. name and datapath_id may be changed. We will consider this as a new bridge created, and an old bridge removed. 4. Bridge ports modification, i.e. add/remove ports. a) Normally a port record is created/deleted together. A port record cannot exist without a bridge containing it. b) It is also possible that a port is removed from one bridge and added to another bridge, in this case the ports do not appear in the updateinfo 5. Port interfaces modification, i.e. add/remove interfaces. The bridge record may not appear in this situation. We must consider these situations carefully and process them in correct order. """ port_update = updateinfo.get('Port', {}) bridge_update = updateinfo.get('Bridge', {}) working_routines = [] def process_bridge(buuid, uo): try: nv = uo['new'] if 'datapath_id' in nv: if ovsdb.getoptional(nv['datapath_id']) is None: # This bridge is not initialized. Wait for the bridge to be initialized. for m in callAPI( self.apiroutine, 'ovsdbmanager', 'waitbridge', { 'connection': connection, 'name': nv['name'], 'timeout': 5 }): yield m datapath_id = self.apiroutine.retvalue else: datapath_id = int(nv['datapath_id'], 16) self.bridge_datapathid[buuid] = datapath_id elif buuid in self.bridge_datapathid: datapath_id = self.bridge_datapathid[buuid] else: # This should not happen, but just in case... for m in callAPI(self.apiroutine, 'ovsdbmanager', 'waitbridge', { 'connection': connection, 'name': nv['name'], 'timeout': 5 }): yield m datapath_id = self.apiroutine.retvalue self.bridge_datapathid[buuid] = datapath_id if 'ports' in nv: nset = set((p for _, p in ovsdb.getlist(nv['ports']))) else: nset = set() if 'old' in uo: ov = uo['old'] if 'ports' in ov: oset = set((p for _, p in ovsdb.getlist(ov['ports']))) else: # new ports are not really added; it is only sent because datapath_id is modified nset = set() oset = set() if 'datapath_id' in ov and ovsdb.getoptional( ov['datapath_id']) is not None: old_datapathid = int(ov['datapath_id'], 16) else: old_datapathid = datapath_id else: oset = set() old_datapathid = datapath_id # For every deleted port, remove the interfaces with this port _uuid remove = [] add_routine = [] for puuid in oset - nset: remove += self._remove_all_interface( connection, protocol, old_datapathid, puuid, buuid) # For every port not changed, check if the interfaces are modified; for puuid in oset.intersection(nset): if puuid in port_update: # The port is modified, there should be an 'old' set and 'new' set pu = port_update[puuid] if 'old' in pu: poset = set((p for _, p in ovsdb.getlist( pu['old']['interfaces']))) else: poset = set() if 'new' in pu: pnset = set((p for _, p in ovsdb.getlist( pu['new']['interfaces']))) else: pnset = set() # Remove old interfaces remove += [ r for r in (self._remove_interface( connection, protocol, datapath_id, iuuid, puuid) for iuuid in (poset - pnset)) if r is not None ] # Prepare to add new interfaces add_routine += [ self._get_interface_info(connection, protocol, buuid, iuuid, puuid) for iuuid in (pnset - poset) ] # For every port added, add the interfaces def add_port_interfaces(puuid): # If the uuid does not appear in update info, we have no choice but to query interfaces with select # we cannot use data from other bridges; the port may be moved from a bridge which is not tracked try: method, params = ovsdb.transact( 'Open_vSwitch', ovsdb.select('Port', [["_uuid", "==", ovsdb.uuid(puuid)]], ["interfaces"])) for m in protocol.querywithreply( method, params, connection, self.apiroutine): yield m r = self.apiroutine.jsonrpc_result[0] if 'error' in r: raise JsonRPCErrorResultException( 'Error when query interfaces from port ' + repr(puuid) + ': ' + r['error']) if r['rows']: interfaces = ovsdb.getlist( r['rows'][0]['interfaces']) with closing( self.apiroutine.executeAll([ self._get_interface_info( connection, protocol, buuid, iuuid, puuid) for _, iuuid in interfaces ])) as g: for m in g: yield m self.apiroutine.retvalue = list( itertools.chain( r[0] for r in self.apiroutine.retvalue)) else: self.apiroutine.retvalue = [] except JsonRPCProtocolException: self.apiroutine.retvalue = [] except ConnectionResetException: self.apiroutine.retvalue = [] for puuid in nset - oset: self.ports_uuids[puuid] = buuid if puuid in port_update and 'new' in port_update[puuid] \ and 'old' not in port_update[puuid]: # Add all the interfaces in 'new' interfaces = ovsdb.getlist( port_update[puuid]['new']['interfaces']) add_routine += [ self._get_interface_info(connection, protocol, buuid, iuuid, puuid) for _, iuuid in interfaces ] else: add_routine.append(add_port_interfaces(puuid)) # Execute the add_routine try: with closing(self.apiroutine.executeAll(add_routine)) as g: for m in g: yield m except: add = [] raise else: add = list( itertools.chain(r[0] for r in self.apiroutine.retvalue)) finally: if update: self.scheduler.emergesend( ModuleNotification(self.getServiceName(), 'update', datapathid=datapath_id, connection=connection, vhost=protocol.vhost, add=add, remove=remove, reason='bridgemodify' if 'old' in uo else 'bridgeup')) except JsonRPCProtocolException: pass except ConnectionResetException: pass except OVSDBBridgeNotAppearException: pass ignore_ports = set() for buuid, uo in bridge_update.items(): # Bridge removals are ignored because we process OVSDBBridgeSetup event instead if 'old' in uo: if 'ports' in uo['old']: oset = set( (puuid for _, puuid in ovsdb.getlist(uo['old']['ports']))) ignore_ports.update(oset) if 'new' not in uo: if buuid in self.bridge_datapathid: del self.bridge_datapathid[buuid] if 'new' in uo: # If bridge contains this port is updated, we process the port update totally in bridge, # so we ignore it later if 'ports' in uo['new']: nset = set( (puuid for _, puuid in ovsdb.getlist(uo['new']['ports']))) ignore_ports.update(nset) working_routines.append(process_bridge(buuid, uo)) def process_port(buuid, port_uuid, interfaces, remove_ids): if buuid not in self.bridge_datapathid: return datapath_id = self.bridge_datapathid[buuid] ports = self.managed_ports.get((protocol.vhost, datapath_id)) remove = [] if ports is not None: remove = [p for _, p in ports if p['_uuid'] in remove_ids] not_remove = [(_, p) for _, p in ports if p['_uuid'] not in remove_ids] ports[:len(not_remove)] = not_remove del ports[len(not_remove):] if interfaces: try: with closing( self.apiroutine.executeAll([ self._get_interface_info( connection, protocol, buuid, iuuid, port_uuid) for iuuid in interfaces ])) as g: for m in g: yield m add = list( itertools.chain( (r[0] for r in self.apiroutine.retvalue if r[0]))) except Exception: self._logger.warning("Cannot get new port information", exc_info=True) add = [] else: add = [] if update: for m in self.apiroutine.waitForSend( ModuleNotification(self.getServiceName(), 'update', datapathid=datapath_id, connection=connection, vhost=protocol.vhost, add=add, remove=remove, reason='bridgemodify')): yield m for puuid, po in port_update.items(): if puuid not in ignore_ports: bridge_id = self.ports_uuids.get(puuid) if bridge_id is not None: datapath_id = self.bridge_datapathid[bridge_id] if datapath_id is not None: # This port is modified if 'new' in po: nset = set((iuuid for _, iuuid in ovsdb.getlist( po['new']['interfaces']))) else: nset = set() if 'old' in po: oset = set((iuuid for _, iuuid in ovsdb.getlist( po['old']['interfaces']))) else: oset = set() working_routines.append( process_port(bridge_id, puuid, nset - oset, oset - nset)) if update: for r in working_routines: self.apiroutine.subroutine(r) else: try: with closing( self.apiroutine.executeAll(working_routines, None, ())) as g: for m in g: yield m finally: self.scheduler.emergesend( OVSDBConnectionPortsSynchronized(connection)) def _get_ports(self, connection, protocol): try: try: method, params = ovsdb.monitor( 'Open_vSwitch', 'ovsdb_port_manager_interfaces_monitor', { 'Bridge': [ ovsdb.monitor_request( ["name", "datapath_id", "ports"]) ], 'Port': [ovsdb.monitor_request(["interfaces"])] }) try: for m in protocol.querywithreply(method, params, connection, self.apiroutine): yield m except JsonRPCErrorResultException: # The monitor is already set, cancel it first method2, params2 = ovsdb.monitor_cancel( 'ovsdb_port_manager_interfaces_monitor') for m in protocol.querywithreply(method2, params2, connection, self.apiroutine, False): yield m for m in protocol.querywithreply(method, params, connection, self.apiroutine): yield m r = self.apiroutine.jsonrpc_result except: def _msg(): for m in self.apiroutine.waitForSend( OVSDBConnectionPortsSynchronized(connection)): yield m self.apiroutine.subroutine(_msg(), False) raise # This is the initial state, it should contains all the ids of ports and interfaces self.apiroutine.subroutine( self._update_interfaces(connection, protocol, r, False)) update_matcher = JsonRPCNotificationEvent.createMatcher( 'update', connection, connection.connmark, _ismatch=lambda x: x.params[ 0] == 'ovsdb_port_manager_interfaces_monitor') conn_state = protocol.statematcher(connection) while True: yield (update_matcher, conn_state) if self.apiroutine.matcher is conn_state: break else: self.apiroutine.subroutine( self._update_interfaces( connection, protocol, self.apiroutine.event.params[1], True)) except JsonRPCProtocolException: pass finally: if self.apiroutine.currentroutine in self.monitor_routines: self.monitor_routines.remove(self.apiroutine.currentroutine) def _get_existing_ports(self): for m in callAPI(self.apiroutine, 'ovsdbmanager', 'getallconnections', {'vhost': None}): yield m matchers = [] for c in self.apiroutine.retvalue: self.monitor_routines.add( self.apiroutine.subroutine(self._get_ports(c, c.protocol))) matchers.append(OVSDBConnectionPortsSynchronized.createMatcher(c)) for m in self.apiroutine.waitForAll(*matchers): yield m self._synchronized = True for m in self.apiroutine.waitForSend( ModuleNotification(self.getServiceName(), 'synchronized')): yield m def _wait_for_sync(self): if not self._synchronized: yield (ModuleNotification.createMatcher(self.getServiceName(), 'synchronized'), ) def _manage_ports(self): try: self.apiroutine.subroutine(self._get_existing_ports()) connsetup = OVSDBConnectionSetup.createMatcher() bridgedown = OVSDBBridgeSetup.createMatcher(OVSDBBridgeSetup.DOWN) while True: yield (connsetup, bridgedown) e = self.apiroutine.event if self.apiroutine.matcher is connsetup: self.monitor_routines.add( self.apiroutine.subroutine( self._get_ports(e.connection, e.connection.protocol))) else: # Remove ports of the bridge ports = self.managed_ports.get((e.vhost, e.datapathid)) if ports is not None: ports_original = ports ports = [p for _, p in ports] for p in ports: if p['id']: self._remove_interface_id( e.connection, e.connection.protocol, e.datapathid, p) newdpid = getattr(e, 'new_datapath_id', None) buuid = e.bridgeuuid if newdpid is not None: # This bridge changes its datapath id if buuid in self.bridge_datapathid and self.bridge_datapathid[ buuid] == e.datapathid: self.bridge_datapathid[buuid] = newdpid def re_add_interfaces(): with closing( self.apiroutine.executeAll([ self._get_interface_info( e.connection, e.connection.protocol, buuid, r['_uuid'], puuid) for puuid, r in ports_original ])) as g: for m in g: yield m add = list( itertools.chain( r[0] for r in self.apiroutine.retvalue)) for m in self.apiroutine.waitForSend( ModuleNotification( self.getServiceName(), 'update', datapathid=e.datapathid, connection=e.connection, vhost=e.vhost, add=add, remove=[], reason='bridgeup')): yield m self.apiroutine.subroutine(re_add_interfaces()) else: # The ports are removed for puuid, _ in ports_original: if puuid in self.ports_uuids[ puuid] and self.ports_uuids[ puuid] == buuid: del self.ports_uuids[puuid] del self.managed_ports[(e.vhost, e.datapathid)] self.scheduler.emergesend( ModuleNotification(self.getServiceName(), 'update', datapathid=e.datapathid, connection=e.connection, vhost=e.vhost, add=[], remove=ports, reason='bridgedown')) finally: for r in list(self.monitor_routines): r.close() self.scheduler.emergesend( ModuleNotification(self.getServiceName(), 'unsynchronized')) def getports(self, datapathid, vhost=''): "Return all ports of a specifed datapath" for m in self._wait_for_sync(): yield m self.apiroutine.retvalue = [ p for _, p in self.managed_ports.get((vhost, datapathid), []) ] def getallports(self, vhost=None): "Return all ``(datapathid, port, vhost)`` tuples, optionally filterd by vhost" for m in self._wait_for_sync(): yield m if vhost is None: self.apiroutine.retvalue = [ (dpid, p, vh) for (vh, dpid), v in self.managed_ports.items() for _, p in v ] else: self.apiroutine.retvalue = [ (dpid, p, vh) for (vh, dpid), v in self.managed_ports.items() if vh == vhost for _, p in v ] def getportbyno(self, datapathid, portno, vhost=''): "Return port with specified portno" portno &= 0xffff for m in self._wait_for_sync(): yield m self.apiroutine.retvalue = self._getportbyno(datapathid, portno, vhost) def _getportbyno(self, datapathid, portno, vhost=''): ports = self.managed_ports.get((vhost, datapathid)) if ports is None: return None else: for _, p in ports: if p['ofport'] == portno: return p return None def waitportbyno(self, datapathid, portno, timeout=30, vhost=''): "Wait for port with specified portno" portno &= 0xffff for m in self._wait_for_sync(): yield m def waitinner(): p = self._getportbyno(datapathid, portno, vhost) if p is not None: self.apiroutine.retvalue = p else: try: self.wait_portnos[(vhost, datapathid, portno)] = \ self.wait_portnos.get((vhost, datapathid, portno),0) + 1 yield (OVSDBPortUpNotification.createMatcher( None, None, portno, None, vhost, datapathid), ) except: v = self.wait_portnos.get((vhost, datapathid, portno)) if v is not None: if v <= 1: del self.wait_portnos[(vhost, datapathid, portno)] else: self.wait_portnos[(vhost, datapathid, portno)] = v - 1 raise else: self.apiroutine.retvalue = self.apiroutine.event.port for m in self.apiroutine.executeWithTimeout(timeout, waitinner()): yield m if self.apiroutine.timeout: raise OVSDBPortNotAppearException( 'Port ' + repr(portno) + ' does not appear before timeout') def getportbyname(self, datapathid, name, vhost=''): "Return port with specified name" if isinstance(name, bytes): name = name.decode('utf-8') for m in self._wait_for_sync(): yield m self.apiroutine.retvalue = self._getportbyname(datapathid, name, vhost) def _getportbyname(self, datapathid, name, vhost=''): ports = self.managed_ports.get((vhost, datapathid)) if ports is None: return None else: for _, p in ports: if p['name'] == name: return p return None def waitportbyname(self, datapathid, name, timeout=30, vhost=''): "Wait for port with specified name" for m in self._wait_for_sync(): yield m def waitinner(): p = self._getportbyname(datapathid, name, vhost) if p is not None: self.apiroutine.retvalue = p else: try: self.wait_names[(vhost, datapathid, name)] = \ self.wait_portnos.get((vhost, datapathid, name) ,0) + 1 yield (OVSDBPortUpNotification.createMatcher( None, name, None, None, vhost, datapathid), ) except: v = self.wait_names.get((vhost, datapathid, name)) if v is not None: if v <= 1: del self.wait_names[(vhost, datapathid, name)] else: self.wait_names[(vhost, datapathid, name)] = v - 1 raise else: self.apiroutine.retvalue = self.apiroutine.event.port for m in self.apiroutine.executeWithTimeout(timeout, waitinner()): yield m if self.apiroutine.timeout: raise OVSDBPortNotAppearException( 'Port ' + repr(name) + ' does not appear before timeout') def getportbyid(self, id, vhost=''): "Return port with the specified id. The return value is a pair: ``(datapath_id, port)``" for m in self._wait_for_sync(): yield m self.apiroutine = self._getportbyid(id, vhost) def _getportbyid(self, id, vhost=''): ports = self.managed_ids.get((vhost, id)) if ports: return ports[0] else: return None def waitportbyid(self, id, timeout=30, vhost=''): "Wait for port with the specified id. The return value is a pair ``(datapath_id, port)``" for m in self._wait_for_sync(): yield m def waitinner(): p = self._getportbyid(id, vhost) if p is None: try: self.wait_ids[(vhost, id)] = self.wait_ids.get( (vhost, id), 0) + 1 yield (OVSDBPortUpNotification.createMatcher( None, None, None, id, vhost), ) except: v = self.wait_ids.get((vhost, id)) if v is not None: if v <= 1: del self.wait_ids[(vhost, id)] else: self.wait_ids[(vhost, id)] = v - 1 raise else: self.apiroutine.retvalue = ( self.apiroutine.event.datapathid, self.apiroutine.event.port) else: self.apiroutine.retvalue = p for m in self.apiroutine.executeWithTimeout(timeout, waitinner()): yield m if self.apiroutine.timeout: raise OVSDBPortNotAppearException( 'Port ' + repr(id) + ' does not appear before timeout') def resync(self, datapathid, vhost=''): ''' Resync with current ports ''' # Sometimes when the OVSDB connection is very busy, monitor message may be dropped. # We must deal with this and recover from it # Save current manged_ports if (vhost, datapathid) not in self.managed_ports: self.apiroutine.retvalue = None return else: for m in callAPI(self.apiroutine, 'ovsdbmanager', 'getconnection', { 'datapathid': datapathid, 'vhost': vhost }): yield m c = self.apiroutine.retvalue if c is not None: # For now, we restart the connection... for m in c.reconnect(False): yield m for m in self.apiroutine.waitWithTimeout(0.1): yield m for m in callAPI(self.apiroutine, 'ovsdbmanager', 'waitconnection', { 'datapathid': datapathid, 'vhost': vhost }): yield m self.apiroutine.retvalue = None
class OpenflowPortManager(Module): ''' Manage Ports from Openflow Protocol ''' service = True def __init__(self, server): Module.__init__(self, server) self.apiroutine = RoutineContainer(self.scheduler) self.apiroutine.main = self._manage_ports self.routines.append(self.apiroutine) self.managed_ports = {} self.createAPI(api(self.getports, self.apiroutine), api(self.getallports, self.apiroutine), api(self.getportbyno, self.apiroutine), api(self.waitportbyno, self.apiroutine), api(self.getportbyname, self.apiroutine), api(self.waitportbyname, self.apiroutine), api(self.resync, self.apiroutine)) self._synchronized = False def _get_ports(self, connection, protocol, onup=False, update=True): ofdef = connection.openflowdef dpid = connection.openflow_datapathid vhost = connection.protocol.vhost add = [] try: if hasattr(ofdef, 'ofp_multipart_request'): # Openflow 1.3, use ofp_multipart_request to get ports for m in protocol.querymultipart( ofdef.ofp_multipart_request( type=ofdef.OFPMP_PORT_DESC), connection, self.apiroutine): yield m ports = self.managed_ports.setdefault((vhost, dpid), {}) for msg in self.apiroutine.openflow_reply: for p in msg.ports: add.append(p) ports[p.port_no] = p else: # Openflow 1.0, use features_request if onup: # Use the features_reply on connection setup reply = connection.openflow_featuresreply else: request = ofdef.ofp_msg() request.header.type = ofdef.OFPT_FEATURES_REQUEST for m in protocol.querywithreply(request): yield m reply = self.apiroutine.retvalue ports = self.managed_ports.setdefault((vhost, dpid), {}) for p in reply.ports: add.append(p) ports[p.port_no] = p if update: for m in self.apiroutine.waitForSend( OpenflowPortSynchronized(connection)): yield m self._logger.info( "Openflow port synchronized on connection %r", connection) for m in self.apiroutine.waitForSend( ModuleNotification( self.getServiceName(), 'update', datapathid=connection.openflow_datapathid, connection=connection, vhost=protocol.vhost, add=add, remove=[], reason='connected')): yield m except ConnectionResetException: pass except OpenflowProtocolException: pass def _get_existing_ports(self): for m in callAPI(self.apiroutine, 'openflowmanager', 'getallconnections', {'vhost': None}): yield m with closing( self.apiroutine.executeAll([ self._get_ports(c, c.protocol, False, False) for c in self.apiroutine.retvalue if c.openflow_auxiliaryid == 0 ])) as g: for m in g: yield m self._synchronized = True self._logger.info("Openflow ports synchronized") for m in self.apiroutine.waitForSend( ModuleNotification(self.getServiceName(), 'synchronized')): yield m def _wait_for_sync(self): if not self._synchronized: yield (ModuleNotification.createMatcher(self.getServiceName(), 'synchronized'), ) def _manage_ports(self): try: self.apiroutine.subroutine(self._get_existing_ports()) conn_update = ModuleNotification.createMatcher( 'openflowmanager', 'update') port_status = OpenflowAsyncMessageEvent.createMatcher( of13.OFPT_PORT_STATUS, None, 0) while True: yield (conn_update, port_status) if self.apiroutine.matcher is port_status: e = self.apiroutine.event m = e.message c = e.connection if (c.protocol.vhost, c.openflow_datapathid) in self.managed_ports: if m.reason == c.openflowdef.OFPPR_ADD: # A new port is added self.managed_ports[(c.protocol.vhost, c.openflow_datapathid )][m.desc.port_no] = m.desc self.scheduler.emergesend( ModuleNotification( self.getServiceName(), 'update', datapathid=c.openflow_datapathid, connection=c, vhost=c.protocol.vhost, add=[m.desc], remove=[], reason='add')) elif m.reason == c.openflowdef.OFPPR_DELETE: try: del self.managed_ports[( c.protocol.vhost, c.openflow_datapathid)][m.desc.port_no] self.scheduler.emergesend( ModuleNotification( self.getServiceName(), 'update', datapathid=c.openflow_datapathid, connection=c, vhost=c.protocol.vhost, add=[], remove=[m.desc], reason='delete')) except KeyError: pass elif m.reason == c.openflowdef.OFPPR_MODIFY: try: self.scheduler.emergesend( ModuleNotification( self.getServiceName(), 'modified', datapathid=c.openflow_datapathid, connection=c, vhost=c.protocol.vhost, old=self.managed_ports[( c.protocol.vhost, c.openflow_datapathid )][m.desc.port_no], new=m.desc, reason='modified')) except KeyError: self.scheduler.emergesend( ModuleNotification( self.getServiceName(), 'update', datapathid=c.openflow_datapathid, connection=c, vhost=c.protocol.vhost, add=[m.desc], remove=[], reason='add')) self.managed_ports[(c.protocol.vhost, c.openflow_datapathid )][m.desc.port_no] = m.desc else: e = self.apiroutine.event for c in e.remove: if c.openflow_auxiliaryid == 0 and ( c.protocol.vhost, c.openflow_datapathid) in self.managed_ports: self.scheduler.emergesend( ModuleNotification( self.getServiceName(), 'update', datapathid=c.openflow_datapathid, connection=c, vhost=c.protocol.vhost, add=[], remove=list(self.managed_ports[( c.protocol.vhost, c.openflow_datapathid)].values()), reason='disconnected')) del self.managed_ports[(c.protocol.vhost, c.openflow_datapathid)] for c in e.add: if c.openflow_auxiliaryid == 0: self.apiroutine.subroutine( self._get_ports(c, c.protocol, True, True)) finally: self.scheduler.emergesend( ModuleNotification(self.getServiceName(), 'unsynchronized')) def getports(self, datapathid, vhost=''): "Return all ports of a specifed datapath" for m in self._wait_for_sync(): yield m r = self.managed_ports.get((vhost, datapathid)) if r is None: self.apiroutine.retvalue = None else: self.apiroutine.retvalue = list(r.values()) def getallports(self, vhost=None): "Return all ``(datapathid, port, vhost)`` tuples, optionally filterd by vhost" for m in self._wait_for_sync(): yield m if vhost is None: self.apiroutine.retvalue = [ (dpid, p, vh) for (vh, dpid), v in self.managed_ports.items() for p in v.values() ] else: self.apiroutine.retvalue = [ (dpid, p, vh) for (vh, dpid), v in self.managed_ports.items() if vh == vhost for p in v.values() ] def getportbyno(self, datapathid, portno, vhost=''): "Return port with specified OpenFlow portno" for m in self._wait_for_sync(): yield m self.apiroutine.retvalue = self._getportbyno(datapathid, portno, vhost) def _getportbyno(self, datapathid, portno, vhost=''): ports = self.managed_ports.get((vhost, datapathid)) if ports is None: return None else: return ports.get(portno) def waitportbyno(self, datapathid, portno, timeout=30, vhost=''): """ Wait for the specified OpenFlow portno to appear, or until timeout. """ for m in self._wait_for_sync(): yield m def waitinner(): ports = self.managed_ports.get((vhost, datapathid)) if ports is None: for m in callAPI(self.apiroutine, 'openflowmanager', 'waitconnection', { 'datapathid': datapathid, 'vhost': vhost, 'timeout': timeout }): yield m c = self.apiroutine.retvalue ports = self.managed_ports.get((vhost, datapathid)) if ports is None: yield (OpenflowPortSynchronized.createMatcher(c), ) ports = self.managed_ports.get((vhost, datapathid)) if ports is None: raise ConnectionResetException( 'Datapath %016x is not connected' % datapathid) if portno not in ports: yield (OpenflowAsyncMessageEvent.createMatcher( of13.OFPT_PORT_STATUS, datapathid, 0, _ismatch=lambda x: x.message.desc.port_no == portno), ) self.apiroutine.retvalue = self.apiroutine.event.message.desc else: self.apiroutine.retvalue = ports[portno] for m in self.apiroutine.executeWithTimeout(timeout, waitinner()): yield m if self.apiroutine.timeout: raise OpenflowPortNotAppearException( 'Port %d does not appear on datapath %016x' % (portno, datapathid)) def getportbyname(self, datapathid, name, vhost=''): "Return port with specified port name" for m in self._wait_for_sync(): yield m self.apiroutine.retvalue = self._getportbyname(datapathid, name, vhost) def _getportbyname(self, datapathid, name, vhost=''): if not isinstance(name, bytes): name = _bytes(name) ports = self.managed_ports.get((vhost, datapathid)) if ports is None: return None else: for p in ports.values(): if p.name == name: return p return None def waitportbyname(self, datapathid, name, timeout=30, vhost=''): """ Wait for a port with the specified port name to appear, or until timeout """ for m in self._wait_for_sync(): yield m if not isinstance(name, bytes): name = _bytes(name) def waitinner(): ports = self.managed_ports.get((vhost, datapathid)) if ports is None: for m in callAPI(self.apiroutine, 'openflowmanager', 'waitconnection', { 'datapathid': datapathid, 'vhost': vhost, 'timeout': timeout }): yield m c = self.apiroutine.retvalue ports = self.managed_ports.get((vhost, datapathid)) if ports is None: yield (OpenflowPortSynchronized.createMatcher(c), ) ports = self.managed_ports.get((vhost, datapathid)) if ports is None: raise ConnectionResetException( 'Datapath %016x is not connected' % datapathid) for p in ports.values(): if p.name == name: self.apiroutine.retvalue = p return yield (OpenflowAsyncMessageEvent.createMatcher( of13.OFPT_PORT_STATUS, datapathid, 0, _ismatch=lambda x: x.message.desc.name == name), ) self.apiroutine.retvalue = self.apiroutine.event.message.desc for m in self.apiroutine.executeWithTimeout(timeout, waitinner()): yield m if self.apiroutine.timeout: raise OpenflowPortNotAppearException( 'Port %r does not appear on datapath %016x' % (name, datapathid)) def resync(self, datapathid, vhost=''): ''' Resync with current ports ''' # Sometimes when the OpenFlow connection is very busy, PORT_STATUS message may be dropped. # We must deal with this and recover from it # Save current manged_ports if (vhost, datapathid) not in self.managed_ports: self.apiroutine.retvalue = None return else: last_ports = set(self.managed_ports[(vhost, datapathid)].keys()) add = set() remove = set() ports = {} for _ in range(0, 10): for m in callAPI(self.apiroutine, 'openflowmanager', 'getconnection', { 'datapathid': datapathid, 'vhost': vhost }): yield m c = self.apiroutine.retvalue if c is None: # Disconnected, will automatically resync when reconnected self.apiroutine.retvalue = None return ofdef = c.openflowdef protocol = c.protocol try: if hasattr(ofdef, 'ofp_multipart_request'): # Openflow 1.3, use ofp_multipart_request to get ports for m in protocol.querymultipart( ofdef.ofp_multipart_request( type=ofdef.OFPMP_PORT_DESC), c, self.apiroutine): yield m for msg in self.apiroutine.openflow_reply: for p in msg.ports: ports[p.port_no] = p else: # Openflow 1.0, use features_request request = ofdef.ofp_msg() request.header.type = ofdef.OFPT_FEATURES_REQUEST for m in protocol.querywithreply(request): yield m reply = self.apiroutine.retvalue for p in reply.ports: ports[p.port_no] = p except ConnectionResetException: break except OpenflowProtocolException: break else: if (vhost, datapathid) not in self.managed_ports: self.apiroutine.retvalue = None return current_ports = set(self.managed_ports[(vhost, datapathid)]) # If a port is already removed remove.intersection_update(current_ports) # If a port is already added add.difference_update(current_ports) # If a port is not acquired, we do not add it acquired_keys = set(ports.keys()) add.difference_update(acquired_keys) # Add and remove previous added/removed ports current_ports.difference_update(remove) current_ports.update(add) # If there are changed ports, the changed ports may or may not appear in the acquired port list # We only deal with following situations: # 1. If both lack ports, we add them # 2. If both have additional ports, we remote them to_add = acquired_keys.difference( current_ports.union(last_ports)) to_remove = current_ports.intersection(last_ports).difference( acquired_keys) if not to_add and not to_remove and current_ports == last_ports: break else: add.update(to_add) remove.update(to_remove) current_ports.update(to_add) current_ports.difference_update(to_remove) last_ports = current_ports # Actual add and remove mports = self.managed_ports[(vhost, datapathid)] add_ports = [] remove_ports = [] for k in add: if k not in mports: add_ports.append(ports[k]) mports[k] = ports[k] for k in remove: try: oldport = mports.pop(k) except KeyError: pass else: remove_ports.append(oldport) for m in self.apiroutine.waitForSend( ModuleNotification(self.getServiceName(), 'update', datapathid=datapathid, connection=c, vhost=vhost, add=add_ports, remove=remove_ports, reason='resync')): yield m self.apiroutine.retvalue = None
class OVSDBPortManager(Module): ''' Manage Ports from OVSDB Protocol ''' service = True def __init__(self, server): Module.__init__(self, server) self.apiroutine = RoutineContainer(self.scheduler) self.apiroutine.main = self._manage_ports self.routines.append(self.apiroutine) self.managed_ports = {} self.managed_ids = {} self.monitor_routines = set() self.ports_uuids = {} self.wait_portnos = {} self.wait_names = {} self.wait_ids = {} self.bridge_datapathid = {} self.createAPI(api(self.getports, self.apiroutine), api(self.getallports, self.apiroutine), api(self.getportbyid, self.apiroutine), api(self.waitportbyid, self.apiroutine), api(self.getportbyname, self.apiroutine), api(self.waitportbyname, self.apiroutine), api(self.getportbyno, self.apiroutine), api(self.waitportbyno, self.apiroutine), api(self.resync, self.apiroutine) ) self._synchronized = False def _get_interface_info(self, connection, protocol, buuid, interface_uuid, port_uuid): try: method, params = ovsdb.transact('Open_vSwitch', ovsdb.wait('Interface', [["_uuid", "==", ovsdb.uuid(interface_uuid)]], ["ofport"], [{"ofport":ovsdb.oset()}], False, 5000), ovsdb.wait('Interface', [["_uuid", "==", ovsdb.uuid(interface_uuid)]], ["ifindex"], [{"ifindex":ovsdb.oset()}], False, 5000), ovsdb.select('Interface', [["_uuid", "==", ovsdb.uuid(interface_uuid)]], ["_uuid", "name", "ifindex", "ofport", "type", "external_ids"])) for m in protocol.querywithreply(method, params, connection, self.apiroutine): yield m r = self.apiroutine.jsonrpc_result[0] if 'error' in r: raise JsonRPCErrorResultException('Error while acquiring interface: ' + repr(r['error'])) r = self.apiroutine.jsonrpc_result[1] if 'error' in r: raise JsonRPCErrorResultException('Error while acquiring interface: ' + repr(r['error'])) r = self.apiroutine.jsonrpc_result[2] if 'error' in r: raise JsonRPCErrorResultException('Error while acquiring interface: ' + repr(r['error'])) if not r['rows']: self.apiroutine.retvalue = [] return r0 = r['rows'][0] if r0['ofport'] < 0: # Ignore this port because it is in an error state self.apiroutine.retvalue = [] return r0['_uuid'] = r0['_uuid'][1] r0['ifindex'] = ovsdb.getoptional(r0['ifindex']) r0['external_ids'] = ovsdb.getdict(r0['external_ids']) if buuid not in self.bridge_datapathid: self.apiroutine.retvalue = [] return else: datapath_id = self.bridge_datapathid[buuid] if 'iface-id' in r0['external_ids']: eid = r0['external_ids']['iface-id'] r0['id'] = eid id_ports = self.managed_ids.setdefault((protocol.vhost, eid), []) id_ports.append((datapath_id, r0)) else: r0['id'] = None self.managed_ports.setdefault((protocol.vhost, datapath_id),[]).append((port_uuid, r0)) notify = False if (protocol.vhost, datapath_id, r0['ofport']) in self.wait_portnos: notify = True del self.wait_portnos[(protocol.vhost, datapath_id, r0['ofport'])] if (protocol.vhost, datapath_id, r0['name']) in self.wait_names: notify = True del self.wait_names[(protocol.vhost, datapath_id, r0['name'])] if (protocol.vhost, r0['id']) in self.wait_ids: notify = True del self.wait_ids[(protocol.vhost, r0['id'])] if notify: for m in self.apiroutine.waitForSend(OVSDBPortUpNotification(connection, r0['name'], r0['ofport'], r0['id'], protocol.vhost, datapath_id, port = r0)): yield m self.apiroutine.retvalue = [r0] except JsonRPCProtocolException: self.apiroutine.retvalue = [] def _remove_interface_id(self, connection, protocol, datapath_id, port): eid = port['id'] eid_list = self.managed_ids.get((protocol.vhost, eid)) for i in range(0, len(eid_list)): if eid_list[i][1]['_uuid'] == port['_uuid']: del eid_list[i] break def _remove_interface(self, connection, protocol, datapath_id, interface_uuid, port_uuid): ports = self.managed_ports.get((protocol.vhost, datapath_id)) r = None if ports is not None: for i in range(0, len(ports)): if ports[i][1]['_uuid'] == interface_uuid: r = ports[i][1] if r['id']: self._remove_interface_id(connection, protocol, datapath_id, r) del ports[i] break if not ports: del self.managed_ports[(protocol.vhost, datapath_id)] return r def _remove_all_interface(self, connection, protocol, datapath_id, port_uuid, buuid): ports = self.managed_ports.get((protocol.vhost, datapath_id)) if ports is not None: removed_ports = [r for puuid, r in ports if puuid == port_uuid] not_removed_ports = [(puuid, r) for puuid, r in ports if puuid != port_uuid] ports[:len(not_removed_ports)] = not_removed_ports del ports[len(not_removed_ports):] for r in removed_ports: if r['id']: self._remove_interface_id(connection, protocol, datapath_id, r) if not ports: del self.managed_ports[(protocol.vhost, datapath_id)] return removed_ports if port_uuid in self.ports_uuids and self.ports_uuids[port_uuid] == buuid: del self.ports_uuids[port_uuid] return [] def _update_interfaces(self, connection, protocol, updateinfo, update = True): """ There are several kinds of updates, they may appear together: 1. New bridge created (or from initial updateinfo). We should add all the interfaces to the list. 2. Bridge removed. Remove all the ports. 3. name and datapath_id may be changed. We will consider this as a new bridge created, and an old bridge removed. 4. Bridge ports modification, i.e. add/remove ports. a) Normally a port record is created/deleted together. A port record cannot exist without a bridge containing it. b) It is also possible that a port is removed from one bridge and added to another bridge, in this case the ports do not appear in the updateinfo 5. Port interfaces modification, i.e. add/remove interfaces. The bridge record may not appear in this situation. We must consider these situations carefully and process them in correct order. """ port_update = updateinfo.get('Port', {}) bridge_update = updateinfo.get('Bridge', {}) working_routines = [] def process_bridge(buuid, uo): try: nv = uo['new'] if 'datapath_id' in nv: datapath_id = int(nv['datapath_id'], 16) self.bridge_datapathid[buuid] = datapath_id elif buuid in self.bridge_datapathid: datapath_id = self.bridge_datapathid[buuid] else: # This should not happen, but just in case... for m in callAPI(self.apiroutine, 'ovsdbmanager', 'waitbridge', {'connection': connection, 'name': nv['name'], 'timeout': 5}): yield m datapath_id = self.apiroutine.retvalue if 'ports' in nv: nset = set((p for _,p in ovsdb.getlist(nv['ports']))) else: nset = set() if 'old' in uo: ov = uo['old'] if 'ports' in ov: oset = set((p for _,p in ovsdb.getlist(ov['ports']))) else: # new ports are not really added; it is only sent because datapath_id is modified nset = set() oset = set() if 'datapath_id' in ov: old_datapathid = int(ov['datapath_id'], 16) else: old_datapathid = datapath_id else: oset = set() old_datapathid = datapath_id # For every deleted port, remove the interfaces with this port _uuid remove = [] add_routine = [] for puuid in oset - nset: remove += self._remove_all_interface(connection, protocol, old_datapathid, puuid, buuid) # For every port not changed, check if the interfaces are modified; for puuid in oset.intersection(nset): if puuid in port_update: # The port is modified, there should be an 'old' set and 'new' set pu = port_update[puuid] if 'old' in pu: poset = set((p for _,p in ovsdb.getlist(pu['old']['interfaces']))) else: poset = set() if 'new' in pu: pnset = set((p for _,p in ovsdb.getlist(pu['new']['interfaces']))) else: pnset = set() # Remove old interfaces remove += [r for r in (self._remove_interface(connection, protocol, datapath_id, iuuid, puuid) for iuuid in (poset - pnset)) if r is not None] # Prepare to add new interfaces add_routine += [self._get_interface_info(connection, protocol, buuid, iuuid, puuid) for iuuid in (pnset - poset)] # For every port added, add the interfaces def add_port_interfaces(puuid): # If the uuid does not appear in update info, we have no choice but to query interfaces with select # we cannot use data from other bridges; the port may be moved from a bridge which is not tracked try: method, params = ovsdb.transact('Open_vSwitch', ovsdb.select('Port', [["_uuid", "==", ovsdb.uuid(puuid)]], ["interfaces"])) for m in protocol.querywithreply(method, params, connection, self.apiroutine): yield m r = self.apiroutine.jsonrpc_result[0] if 'error' in r: raise JsonRPCErrorResultException('Error when query interfaces from port ' + repr(puuid) + ': ' + r['error']) if r['rows']: interfaces = ovsdb.getlist(r['rows'][0]['interfaces']) with closing(self.apiroutine.executeAll([self._get_interface_info(connection, protocol, buuid, iuuid, puuid) for _,iuuid in interfaces])) as g: for m in g: yield m self.apiroutine.retvalue = list(itertools.chain(r[0] for r in self.apiroutine.retvalue)) else: self.apiroutine.retvalue = [] except JsonRPCProtocolException: self.apiroutine.retvalue = [] except ConnectionResetException: self.apiroutine.retvalue = [] for puuid in nset - oset: self.ports_uuids[puuid] = buuid if puuid in port_update and 'new' in port_update[puuid] \ and 'old' not in port_update[puuid]: # Add all the interfaces in 'new' interfaces = ovsdb.getlist(port_update[puuid]['new']['interfaces']) add_routine += [self._get_interface_info(connection, protocol, buuid, iuuid, puuid) for _,iuuid in interfaces] else: add_routine.append(add_port_interfaces(puuid)) # Execute the add_routine try: with closing(self.apiroutine.executeAll(add_routine)) as g: for m in g: yield m except: add = [] raise else: add = list(itertools.chain(r[0] for r in self.apiroutine.retvalue)) finally: if update: self.scheduler.emergesend( ModuleNotification(self.getServiceName(), 'update', datapathid = datapath_id, connection = connection, vhost = protocol.vhost, add = add, remove = remove, reason = 'bridgemodify' if 'old' in uo else 'bridgeup' )) except JsonRPCProtocolException: pass except ConnectionResetException: pass except OVSDBBridgeNotAppearException: pass ignore_ports = set() for buuid, uo in bridge_update.items(): # Bridge removals are ignored because we process OVSDBBridgeSetup event instead if 'old' in uo: if 'ports' in uo['old']: oset = set((puuid for _, puuid in ovsdb.getlist(uo['old']['ports']))) ignore_ports.update(oset) if 'new' not in uo: if buuid in self.bridge_datapathid: del self.bridge_datapathid[buuid] if 'new' in uo: # If bridge contains this port is updated, we process the port update totally in bridge, # so we ignore it later if 'ports' in uo['new']: nset = set((puuid for _, puuid in ovsdb.getlist(uo['new']['ports']))) ignore_ports.update(nset) working_routines.append(process_bridge(buuid, uo)) def process_port(buuid, port_uuid, interfaces, remove_ids): if buuid not in self.bridge_datapathid: return datapath_id = self.bridge_datapathid[buuid] ports = self.managed_ports.get((protocol.vhost, datapath_id)) remove = [] if ports is not None: remove = [p for _,p in ports if p['_uuid'] in remove_ids] not_remove = [(_,p) for _,p in ports if p['_uuid'] not in remove_ids] ports[:len(not_remove)] = not_remove del ports[len(not_remove):] if interfaces: try: with closing(self.apiroutine.executeAll([self._get_interface_info(connection, protocol, buuid, iuuid, port_uuid) for iuuid in interfaces])) as g: for m in g: yield m add = list(itertools.chain((r[0] for r in self.apiroutine.retvalue if r[0]))) except Exception: self._logger.warning("Cannot get new port information", exc_info = True) add = [] else: add = [] if update: for m in self.apiroutine.waitForSend(ModuleNotification(self.getServiceName(), 'update', datapathid = datapath_id, connection = connection, vhost = protocol.vhost, add = add, remove = remove, reason = 'bridgemodify' )): yield m for puuid, po in port_update.items(): if puuid not in ignore_ports: bridge_id = self.ports_uuids.get(puuid) if bridge_id is not None: datapath_id = self.bridge_datapathid[bridge_id] if datapath_id is not None: # This port is modified if 'new' in po: nset = set((iuuid for _, iuuid in ovsdb.getlist(po['new']['interfaces']))) else: nset = set() if 'old' in po: oset = set((iuuid for _, iuuid in ovsdb.getlist(po['old']['interfaces']))) else: oset = set() working_routines.append(process_port(bridge_id, puuid, nset - oset, oset - nset)) if update: for r in working_routines: self.apiroutine.subroutine(r) else: try: with closing(self.apiroutine.executeAll(working_routines, None, ())) as g: for m in g: yield m finally: self.scheduler.emergesend(OVSDBConnectionPortsSynchronized(connection)) def _get_ports(self, connection, protocol): try: try: method, params = ovsdb.monitor('Open_vSwitch', 'ovsdb_port_manager_interfaces_monitor', { 'Bridge':[ovsdb.monitor_request(["name", "datapath_id", "ports"])], 'Port':[ovsdb.monitor_request(["interfaces"])] }) for m in protocol.querywithreply(method, params, connection, self.apiroutine): yield m if 'error' in self.apiroutine.jsonrpc_result: # The monitor is already set, cancel it first method2, params2 = ovsdb.monitor_cancel('ovsdb_port_manager_interfaces_monitor') for m in protocol.querywithreply(method2, params2, connection, self.apiroutine, False): yield m for m in protocol.querywithreply(method, params, connection, self.apiroutine): yield m if 'error' in self.apiroutine.jsonrpc_result: raise JsonRPCErrorResultException('OVSDB request failed: ' + repr(self.apiroutine.jsonrpc_result)) r = self.apiroutine.jsonrpc_result except: for m in self.apiroutine.waitForSend(OVSDBConnectionPortsSynchronized(connection)): yield m raise # This is the initial state, it should contains all the ids of ports and interfaces self.apiroutine.subroutine(self._update_interfaces(connection, protocol, r, False)) update_matcher = JsonRPCNotificationEvent.createMatcher('update', connection, connection.connmark, _ismatch = lambda x: x.params[0] == 'ovsdb_port_manager_interfaces_monitor') conn_state = protocol.statematcher(connection) while True: yield (update_matcher, conn_state) if self.apiroutine.matcher is conn_state: break else: self.apiroutine.subroutine(self._update_interfaces(connection, protocol, self.apiroutine.event.params[1], True)) except JsonRPCProtocolException: pass finally: if self.apiroutine.currentroutine in self.monitor_routines: self.monitor_routines.remove(self.apiroutine.currentroutine) def _get_existing_ports(self): for m in callAPI(self.apiroutine, 'ovsdbmanager', 'getallconnections', {'vhost':None}): yield m matchers = [] for c in self.apiroutine.retvalue: self.monitor_routines.add(self.apiroutine.subroutine(self._get_ports(c, c.protocol))) matchers.append(OVSDBConnectionPortsSynchronized.createMatcher(c)) for m in self.apiroutine.waitForAll(*matchers): yield m self._synchronized = True for m in self.apiroutine.waitForSend(ModuleNotification(self.getServiceName(), 'synchronized')): yield m def _wait_for_sync(self): if not self._synchronized: yield (ModuleNotification.createMatcher(self.getServiceName(), 'synchronized'),) def _manage_ports(self): try: self.apiroutine.subroutine(self._get_existing_ports()) connsetup = OVSDBConnectionSetup.createMatcher() bridgedown = OVSDBBridgeSetup.createMatcher(OVSDBBridgeSetup.DOWN) while True: yield (connsetup, bridgedown) e = self.apiroutine.event if self.apiroutine.matcher is connsetup: self.monitor_routines.add(self.apiroutine.subroutine(self._get_ports(e.connection, e.connection.protocol))) else: # Remove ports of the bridge ports = self.managed_ports.get((e.vhost, e.datapathid)) if ports is not None: ports_original = ports ports = [p for _,p in ports] for p in ports: if p['id']: self._remove_interface_id(e.connection, e.connection.protocol, e.datapathid, p) newdpid = getattr(e, 'new_datapath_id', None) buuid = e.bridgeuuid if newdpid is not None: # This bridge changes its datapath id if buuid in self.bridge_datapathid and self.bridge_datapathid[buuid] == e.datapathid: self.bridge_datapathid[buuid] = newdpid def re_add_interfaces(): with closing(self.apiroutine.executeAll( [self._get_interface_info(e.connection, e.connection.protocol, buuid, r['_uuid'], puuid) for puuid, r in ports_original])) as g: for m in g: yield m add = list(itertools.chain(r[0] for r in self.apiroutine.retvalue)) for m in self.apiroutine.waitForSend(ModuleNotification(self.getServiceName(), 'update', datapathid = e.datapathid, connection = e.connection, vhost = e.vhost, add = add, remove = [], reason = 'bridgeup' )): yield m self.apiroutine.subroutine(re_add_interfaces()) else: # The ports are removed for puuid, _ in ports_original: if puuid in self.ports_uuids[puuid] and self.ports_uuids[puuid] == buuid: del self.ports_uuids[puuid] del self.managed_ports[(e.vhost, e.datapathid)] self.scheduler.emergesend(ModuleNotification(self.getServiceName(), 'update', datapathid = e.datapathid, connection = e.connection, vhost = e.vhost, add = [], remove = ports, reason = 'bridgedown' )) finally: for r in list(self.monitor_routines): r.close() self.scheduler.emergesend(ModuleNotification(self.getServiceName(), 'unsynchronized')) def getports(self, datapathid, vhost = ''): "Return all ports of a specifed datapath" for m in self._wait_for_sync(): yield m self.apiroutine.retvalue = [p for _,p in self.managed_ports.get((vhost, datapathid), [])] def getallports(self, vhost = None): "Return all (datapathid, port, vhost) tuples, optionally filterd by vhost" for m in self._wait_for_sync(): yield m if vhost is None: self.apiroutine.retvalue = [(dpid, p, vh) for (vh, dpid),v in self.managed_ports.items() for _,p in v] else: self.apiroutine.retvalue = [(dpid, p, vh) for (vh, dpid),v in self.managed_ports.items() if vh == vhost for _,p in v] def getportbyno(self, datapathid, portno, vhost = ''): "Return port with specified portno" portno &= 0xffff for m in self._wait_for_sync(): yield m self.apiroutine.retvalue = self._getportbyno(datapathid, portno, vhost) def _getportbyno(self, datapathid, portno, vhost = ''): ports = self.managed_ports.get((vhost, datapathid)) if ports is None: return None else: for _, p in ports: if p['ofport'] == portno: return p return None def waitportbyno(self, datapathid, portno, timeout = 30, vhost = ''): "Wait for port with specified portno" portno &= 0xffff for m in self._wait_for_sync(): yield m def waitinner(): p = self._getportbyno(datapathid, portno, vhost) if p is not None: self.apiroutine.retvalue = p else: try: self.wait_portnos[(vhost, datapathid, portno)] = \ self.wait_portnos.get((vhost, datapathid, portno),0) + 1 yield (OVSDBPortUpNotification.createMatcher(None, None, portno, None, vhost, datapathid),) except: v = self.wait_portnos.get((vhost, datapathid, portno)) if v is not None: if v <= 1: del self.wait_portnos[(vhost, datapathid, portno)] else: self.wait_portnos[(vhost, datapathid, portno)] = v - 1 raise else: self.apiroutine.retvalue = self.apiroutine.event.port for m in self.apiroutine.executeWithTimeout(timeout, waitinner()): yield m if self.apiroutine.timeout: raise OVSDBPortNotAppearException('Port ' + repr(portno) + ' does not appear before timeout') def getportbyname(self, datapathid, name, vhost = ''): "Return port with specified name" if isinstance(name, bytes): name = name.decode('utf-8') for m in self._wait_for_sync(): yield m self.apiroutine.retvalue = self._getportbyname(datapathid, name, vhost) def _getportbyname(self, datapathid, name, vhost = ''): ports = self.managed_ports.get((vhost, datapathid)) if ports is None: return None else: for _, p in ports: if p['name'] == name: return p return None def waitportbyname(self, datapathid, name, timeout = 30, vhost = ''): "Wait for port with specified name" for m in self._wait_for_sync(): yield m def waitinner(): p = self._getportbyname(datapathid, name, vhost) if p is not None: self.apiroutine.retvalue = p else: try: self.wait_names[(vhost, datapathid, name)] = \ self.wait_portnos.get((vhost, datapathid, name) ,0) + 1 yield (OVSDBPortUpNotification.createMatcher(None, name, None, None, vhost, datapathid),) except: v = self.wait_names.get((vhost, datapathid, name)) if v is not None: if v <= 1: del self.wait_names[(vhost, datapathid, name)] else: self.wait_names[(vhost, datapathid, name)] = v - 1 raise else: self.apiroutine.retvalue = self.apiroutine.event.port for m in self.apiroutine.executeWithTimeout(timeout, waitinner()): yield m if self.apiroutine.timeout: raise OVSDBPortNotAppearException('Port ' + repr(name) + ' does not appear before timeout') def getportbyid(self, id, vhost = ''): "Return port with the specified id. The return value is a pair: (datapath_id, port)" for m in self._wait_for_sync(): yield m self.apiroutine = self._getportbyid(id, vhost) def _getportbyid(self, id, vhost = ''): ports = self.managed_ids.get((vhost, id)) if ports: return ports[0] else: return None def waitportbyid(self, id, timeout = 30, vhost = ''): "Wait for port with the specified id. The return value is a pair (datapath_id, port)" for m in self._wait_for_sync(): yield m def waitinner(): p = self._getportbyid(id, vhost) if p is None: try: self.wait_ids[(vhost, id)] = self.wait_ids.get((vhost, id), 0) + 1 yield (OVSDBPortUpNotification.createMatcher(None, None, None, id, vhost),) except: v = self.wait_ids.get((vhost, id)) if v is not None: if v <= 1: del self.wait_ids[(vhost, id)] else: self.wait_ids[(vhost, id)] = v - 1 raise else: self.apiroutine.retvalue = (self.apiroutine.event.datapathid, self.apiroutine.event.port) else: self.apiroutine.retvalue = p for m in self.apiroutine.executeWithTimeout(timeout, waitinner()): yield m if self.apiroutine.timeout: raise OVSDBPortNotAppearException('Port ' + repr(id) + ' does not appear before timeout') def resync(self, datapathid, vhost = ''): ''' Resync with current ports ''' # Sometimes when the OVSDB connection is very busy, monitor message may be dropped. # We must deal with this and recover from it # Save current manged_ports if (vhost, datapathid) not in self.managed_ports: self.apiroutine.retvalue = None return else: for m in callAPI(self.apiroutine, 'ovsdbmanager', 'getconnection', {'datapathid': datapathid, 'vhost':vhost}): yield m c = self.apiroutine.retvalue if c is not None: # For now, we restart the connection... for m in c.reconnect(False): yield m for m in self.apiroutine.waitWithTimeout(0.1): yield m for m in callAPI(self.apiroutine, 'ovsdbmanager', 'waitconnection', {'datapathid': datapathid, 'vhost': vhost}): yield m self.apiroutine.retvalue = None