Ejemplo n.º 1
0
class OVSDBManager(Module):
    '''
    Manage Openflow Connections
    '''
    service = True
    # Bind to JsonRPCServer vHosts. If not None, should be a list of vHost names e.g. ``['']``
    _default_vhostbind = None
    # Only acquire information from bridges with this names
    _default_bridgenames = None

    def __init__(self, server):
        Module.__init__(self, server)
        self.apiroutine = RoutineContainer(self.scheduler)
        self.apiroutine.main = self._manage_conns
        self.routines.append(self.apiroutine)
        self.managed_conns = {}
        self.managed_systemids = {}
        self.managed_bridges = {}
        self.managed_routines = []
        self.endpoint_conns = {}
        self.createAPI(api(self.getconnection, self.apiroutine),
                       api(self.waitconnection, self.apiroutine),
                       api(self.getdatapathids, self.apiroutine),
                       api(self.getalldatapathids, self.apiroutine),
                       api(self.getallconnections, self.apiroutine),
                       api(self.getbridges, self.apiroutine),
                       api(self.getbridge, self.apiroutine),
                       api(self.getbridgebyuuid, self.apiroutine),
                       api(self.waitbridge, self.apiroutine),
                       api(self.waitbridgebyuuid, self.apiroutine),
                       api(self.getsystemids, self.apiroutine),
                       api(self.getallsystemids, self.apiroutine),
                       api(self.getconnectionbysystemid, self.apiroutine),
                       api(self.waitconnectionbysystemid, self.apiroutine),
                       api(self.getconnectionsbyendpoint, self.apiroutine),
                       api(self.getconnectionsbyendpointname, self.apiroutine),
                       api(self.getendpoints, self.apiroutine),
                       api(self.getallendpoints, self.apiroutine),
                       api(self.getallbridges, self.apiroutine),
                       api(self.getbridgeinfo, self.apiroutine),
                       api(self.waitbridgeinfo, self.apiroutine))
        self._synchronized = False

    def _update_bridge(self, connection, protocol, bridge_uuid, vhost):
        try:
            method, params = ovsdb.transact(
                'Open_vSwitch',
                ovsdb.wait(
                    'Bridge',
                    [["_uuid", "==", ovsdb.uuid(bridge_uuid)]],
                    ["datapath_id"], [{
                        "datapath_id": ovsdb.oset()
                    }], False, 5000),
                ovsdb.select(
                    'Bridge',
                    [["_uuid", "==", ovsdb.uuid(bridge_uuid)]],
                    ["datapath_id", "name"]))
            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 datapath-id: ' + repr(r['error']))
            r = self.apiroutine.jsonrpc_result[1]
            if 'error' in r:
                raise JsonRPCErrorResultException(
                    'Error while acquiring datapath-id: ' + repr(r['error']))
            if r['rows']:
                r0 = r['rows'][0]
                name = r0['name']
                dpid = int(r0['datapath_id'], 16)
                if self.bridgenames is None or name in self.bridgenames:
                    self.managed_bridges[connection].append(
                        (vhost, dpid, name, bridge_uuid))
                    self.managed_conns[(vhost, dpid)] = connection
                    for m in self.apiroutine.waitForSend(
                            OVSDBBridgeSetup(OVSDBBridgeSetup.UP, dpid,
                                             connection.ovsdb_systemid, name,
                                             connection, connection.connmark,
                                             vhost, bridge_uuid)):
                        yield m
        except JsonRPCProtocolException:
            pass

    def _get_bridges(self, connection, protocol):
        try:
            try:
                vhost = protocol.vhost
                if not hasattr(connection, 'ovsdb_systemid'):
                    method, params = ovsdb.transact(
                        'Open_vSwitch',
                        ovsdb.select('Open_vSwitch', [], ['external_ids']))
                    for m in protocol.querywithreply(method, params,
                                                     connection,
                                                     self.apiroutine):
                        yield m
                    result = self.apiroutine.jsonrpc_result[0]
                    system_id = ovsdb.omap_getvalue(
                        result['rows'][0]['external_ids'], 'system-id')
                    connection.ovsdb_systemid = system_id
                else:
                    system_id = connection.ovsdb_systemid
                if (vhost, system_id) in self.managed_systemids:
                    oc = self.managed_systemids[(vhost, system_id)]
                    ep = _get_endpoint(oc)
                    econns = self.endpoint_conns.get((vhost, ep))
                    if econns:
                        try:
                            econns.remove(oc)
                        except ValueError:
                            pass
                    del self.managed_systemids[(vhost, system_id)]
                self.managed_systemids[(vhost, system_id)] = connection
                self.managed_bridges[connection] = []
                ep = _get_endpoint(connection)
                self.endpoint_conns.setdefault((vhost, ep),
                                               []).append(connection)
                method, params = ovsdb.monitor(
                    'Open_vSwitch', 'ovsdb_manager_bridges_monitor',
                    {'Bridge': ovsdb.monitor_request(['name', 'datapath_id'])})
                try:
                    for m in protocol.querywithreply(method, params,
                                                     connection,
                                                     self.apiroutine):
                        yield m
                except JsonRPCErrorResultException:
                    # The monitor is already set, cancel it first
                    method, params = ovsdb.monitor_cancel(
                        'ovsdb_manager_bridges_monitor')
                    for m in protocol.querywithreply(method, params,
                                                     connection,
                                                     self.apiroutine, False):
                        yield m
                    method, params = ovsdb.monitor(
                        'Open_vSwitch', 'ovsdb_manager_bridges_monitor', {
                            'Bridge':
                            ovsdb.monitor_request(['name', 'datapath_id'])
                        })
                    for m in protocol.querywithreply(method, params,
                                                     connection,
                                                     self.apiroutine):
                        yield m
            except Exception:
                for m in self.apiroutine.waitForSend(
                        OVSDBConnectionSetup(system_id, connection,
                                             connection.connmark, vhost)):
                    yield m
                raise
            else:
                # Process initial bridges
                init_subprocesses = []
                if self.apiroutine.jsonrpc_result and 'Bridge' in self.apiroutine.jsonrpc_result:
                    init_subprocesses = [
                        self._update_bridge(connection, protocol, buuid, vhost)
                        for buuid in
                        self.apiroutine.jsonrpc_result['Bridge'].keys()
                    ]

                def init_process():
                    try:
                        with closing(
                                self.apiroutine.executeAll(init_subprocesses,
                                                           retnames=())) as g:
                            for m in g:
                                yield m
                    except Exception:
                        for m in self.apiroutine.waitForSend(
                                OVSDBConnectionSetup(system_id, connection,
                                                     connection.connmark,
                                                     vhost)):
                            yield m
                        raise
                    else:
                        for m in self.apiroutine.waitForSend(
                                OVSDBConnectionSetup(system_id, connection,
                                                     connection.connmark,
                                                     vhost)):
                            yield m

                self.apiroutine.subroutine(init_process())
            # Wait for notify
            notification = JsonRPCNotificationEvent.createMatcher(
                'update',
                connection,
                connection.connmark,
                _ismatch=lambda x: x.params[
                    0] == 'ovsdb_manager_bridges_monitor')
            conn_down = protocol.statematcher(connection)
            while True:
                yield (conn_down, notification)
                if self.apiroutine.matcher is conn_down:
                    break
                else:
                    for buuid, v in self.apiroutine.event.params[1][
                            'Bridge'].items():
                        # If a bridge's name or datapath-id is changed, we remove this bridge and add it again
                        if 'old' in v:
                            # A bridge is deleted
                            bridges = self.managed_bridges[connection]
                            for i in range(0, len(bridges)):
                                if buuid == bridges[i][3]:
                                    self.scheduler.emergesend(
                                        OVSDBBridgeSetup(
                                            OVSDBBridgeSetup.DOWN,
                                            bridges[i][1],
                                            system_id,
                                            bridges[i][2],
                                            connection,
                                            connection.connmark,
                                            vhost,
                                            bridges[i][3],
                                            new_datapath_id=int(
                                                v['new']['datapath_id'], 16)
                                            if 'new' in v
                                            and 'datapath_id' in v['new'] else
                                            None))
                                    del self.managed_conns[(vhost,
                                                            bridges[i][1])]
                                    del bridges[i]
                                    break
                        if 'new' in v:
                            # A bridge is added
                            self.apiroutine.subroutine(
                                self._update_bridge(connection, protocol,
                                                    buuid, vhost))
        except JsonRPCProtocolException:
            pass
        finally:
            del connection._ovsdb_manager_get_bridges

    def _manage_existing(self):
        for m in callAPI(self.apiroutine, "jsonrpcserver", "getconnections",
                         {}):
            yield m
        vb = self.vhostbind
        conns = self.apiroutine.retvalue
        for c in conns:
            if vb is None or c.protocol.vhost in vb:
                if not hasattr(c, '_ovsdb_manager_get_bridges'):
                    c._ovsdb_manager_get_bridges = self.apiroutine.subroutine(
                        self._get_bridges(c, c.protocol))
        matchers = [
            OVSDBConnectionSetup.createMatcher(None, c, c.connmark)
            for c in conns if vb is None or c.protocol.vhost in vb
        ]
        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_conns(self):
        try:
            self.apiroutine.subroutine(self._manage_existing())
            vb = self.vhostbind
            if vb is not None:
                conn_up = JsonRPCConnectionStateEvent.createMatcher(
                    state=JsonRPCConnectionStateEvent.CONNECTION_UP,
                    _ismatch=lambda x: x.createby.vhost in vb)
                conn_down = JsonRPCConnectionStateEvent.createMatcher(
                    state=JsonRPCConnectionStateEvent.CONNECTION_DOWN,
                    _ismatch=lambda x: x.createby.vhost in vb)
            else:
                conn_up = JsonRPCConnectionStateEvent.createMatcher(
                    state=JsonRPCConnectionStateEvent.CONNECTION_UP)
                conn_down = JsonRPCConnectionStateEvent.createMatcher(
                    state=JsonRPCConnectionStateEvent.CONNECTION_DOWN)
            while True:
                yield (conn_up, conn_down)
                if self.apiroutine.matcher is conn_up:
                    if not hasattr(self.apiroutine.event.connection,
                                   '_ovsdb_manager_get_bridges'):
                        self.apiroutine.event.connection._ovsdb_manager_get_bridges = self.apiroutine.subroutine(
                            self._get_bridges(self.apiroutine.event.connection,
                                              self.apiroutine.event.createby))
                else:
                    e = self.apiroutine.event
                    conn = e.connection
                    bridges = self.managed_bridges.get(conn)
                    if bridges is not None:
                        del self.managed_systemids[(e.createby.vhost,
                                                    conn.ovsdb_systemid)]
                        del self.managed_bridges[conn]
                        for vhost, dpid, name, buuid in bridges:
                            del self.managed_conns[(vhost, dpid)]
                            self.scheduler.emergesend(
                                OVSDBBridgeSetup(OVSDBBridgeSetup.DOWN, dpid,
                                                 conn.ovsdb_systemid, name,
                                                 conn, conn.connmark,
                                                 e.createby.vhost, buuid))
                        econns = self.endpoint_conns.get(_get_endpoint(conn))
                        if econns is not None:
                            try:
                                econns.remove(conn)
                            except ValueError:
                                pass
        finally:
            for c in self.managed_bridges.keys():
                if hasattr(c, '_ovsdb_manager_get_bridges'):
                    c._ovsdb_manager_get_bridges.close()
                bridges = self.managed_bridges.get(c)
                if bridges is not None:
                    for vhost, dpid, name, buuid in bridges:
                        del self.managed_conns[(vhost, dpid)]
                        self.scheduler.emergesend(
                            OVSDBBridgeSetup(OVSDBBridgeSetup.DOWN, dpid,
                                             c.ovsdb_systemid, name, c,
                                             c.connmark, c.protocol.vhost,
                                             buuid))

    def getconnection(self, datapathid, vhost=''):
        "Get current connection of datapath"
        for m in self._wait_for_sync():
            yield m
        self.apiroutine.retvalue = self.managed_conns.get((vhost, datapathid))

    def waitconnection(self, datapathid, timeout=30, vhost=''):
        "Wait for a datapath connection"
        for m in self.getconnection(datapathid, vhost):
            yield m
        c = self.apiroutine.retvalue
        if c is None:
            for m in self.apiroutine.waitWithTimeout(
                    timeout,
                    OVSDBBridgeSetup.createMatcher(state=OVSDBBridgeSetup.UP,
                                                   datapathid=datapathid,
                                                   vhost=vhost)):
                yield m
            if self.apiroutine.timeout:
                raise ConnectionResetException('Datapath is not connected')
            self.apiroutine.retvalue = self.apiroutine.event.connection
        else:
            self.apiroutine.retvalue = c

    def getdatapathids(self, vhost=''):
        "Get All datapath IDs"
        for m in self._wait_for_sync():
            yield m
        self.apiroutine.retvalue = [
            k[1] for k in self.managed_conns.keys() if k[0] == vhost
        ]

    def getalldatapathids(self):
        "Get all datapath IDs from any vhost. Return ``(vhost, datapathid)`` pair."
        for m in self._wait_for_sync():
            yield m
        self.apiroutine.retvalue = list(self.managed_conns.keys())

    def getallconnections(self, vhost=''):
        "Get all connections from vhost. If vhost is None, return all connections from any host"
        for m in self._wait_for_sync():
            yield m
        if vhost is None:
            self.apiroutine.retvalue = list(self.managed_bridges.keys())
        else:
            self.apiroutine.retvalue = list(
                k for k in self.managed_bridges.keys()
                if k.protocol.vhost == vhost)

    def getbridges(self, connection):
        "Get all ``(dpid, name, _uuid)`` tuple on this connection"
        for m in self._wait_for_sync():
            yield m
        bridges = self.managed_bridges.get(connection)
        if bridges is not None:
            self.apiroutine.retvalue = [(dpid, name, buuid)
                                        for _, dpid, name, buuid in bridges]
        else:
            self.apiroutine.retvalue = None

    def getallbridges(self, vhost=None):
        "Get all ``(dpid, name, _uuid)`` tuple for all connections, optionally filtered by vhost"
        for m in self._wait_for_sync():
            yield m
        if vhost is not None:
            self.apiroutine.retvalue = [
                (dpid, name, buuid)
                for c, bridges in self.managed_bridges.items()
                if c.protocol.vhost == vhost
                for _, dpid, name, buuid in bridges
            ]
        else:
            self.apiroutine.retvalue = [
                (dpid, name, buuid)
                for c, bridges in self.managed_bridges.items()
                for _, dpid, name, buuid in bridges
            ]

    def getbridge(self, connection, name):
        "Get datapath ID on this connection with specified name"
        for m in self._wait_for_sync():
            yield m
        bridges = self.managed_bridges.get(connection)
        if bridges is not None:
            for _, dpid, n, _ in bridges:
                if n == name:
                    self.apiroutine.retvalue = dpid
                    return
            self.apiroutine.retvalue = None
        else:
            self.apiroutine.retvalue = None

    def waitbridge(self, connection, name, timeout=30):
        "Wait for bridge with specified name appears and return the datapath-id"
        bnames = self.bridgenames
        if bnames is not None and name not in bnames:
            raise OVSDBBridgeNotAppearException(
                'Bridge ' + repr(name) +
                ' does not appear: it is not in the selected bridge names')
        for m in self.getbridge(connection, name):
            yield m
        if self.apiroutine.retvalue is None:
            bridge_setup = OVSDBBridgeSetup.createMatcher(
                OVSDBBridgeSetup.UP, None, None, name, connection)
            conn_down = JsonRPCConnectionStateEvent.createMatcher(
                JsonRPCConnectionStateEvent.CONNECTION_DOWN, connection,
                connection.connmark)
            for m in self.apiroutine.waitWithTimeout(timeout, bridge_setup,
                                                     conn_down):
                yield m
            if self.apiroutine.timeout:
                raise OVSDBBridgeNotAppearException('Bridge ' + repr(name) +
                                                    ' does not appear')
            elif self.apiroutine.matcher is conn_down:
                raise ConnectionResetException(
                    'Connection is down before bridge ' + repr(name) +
                    ' appears')
            else:
                self.apiroutine.retvalue = self.apiroutine.event.datapathid

    def getbridgebyuuid(self, connection, uuid):
        "Get datapath ID of bridge on this connection with specified _uuid"
        for m in self._wait_for_sync():
            yield m
        bridges = self.managed_bridges.get(connection)
        if bridges is not None:
            for _, dpid, _, buuid in bridges:
                if buuid == uuid:
                    self.apiroutine.retvalue = dpid
                    return
            self.apiroutine.retvalue = None
        else:
            self.apiroutine.retvalue = None

    def waitbridgebyuuid(self, connection, uuid, timeout=30):
        "Wait for bridge with specified _uuid appears and return the datapath-id"
        for m in self.getbridgebyuuid(connection, uuid):
            yield m
        if self.apiroutine.retvalue is None:
            bridge_setup = OVSDBBridgeSetup.createMatcher(
                state=OVSDBBridgeSetup.UP,
                connection=connection,
                bridgeuuid=uuid)
            conn_down = JsonRPCConnectionStateEvent.createMatcher(
                JsonRPCConnectionStateEvent.CONNECTION_DOWN, connection,
                connection.connmark)
            for m in self.apiroutine.waitWithTimeout(timeout, bridge_setup,
                                                     conn_down):
                yield m
            if self.apiroutine.timeout:
                raise OVSDBBridgeNotAppearException('Bridge ' + repr(uuid) +
                                                    ' does not appear')
            elif self.apiroutine.matcher is conn_down:
                raise ConnectionResetException(
                    'Connection is down before bridge ' + repr(uuid) +
                    ' appears')
            else:
                self.apiroutine.retvalue = self.apiroutine.event.datapathid

    def getsystemids(self, vhost=''):
        "Get All system-ids"
        for m in self._wait_for_sync():
            yield m
        self.apiroutine.retvalue = [
            k[1] for k in self.managed_systemids.keys() if k[0] == vhost
        ]

    def getallsystemids(self):
        "Get all system-ids from any vhost. Return ``(vhost, system-id)`` pair."
        for m in self._wait_for_sync():
            yield m
        self.apiroutine.retvalue = list(self.managed_systemids.keys())

    def getconnectionbysystemid(self, systemid, vhost=''):
        for m in self._wait_for_sync():
            yield m
        self.apiroutine.retvalue = self.managed_systemids.get(
            (vhost, systemid))

    def waitconnectionbysystemid(self, systemid, timeout=30, vhost=''):
        "Wait for a connection with specified system-id"
        for m in self.getconnectionbysystemid(systemid, vhost):
            yield m
        c = self.apiroutine.retvalue
        if c is None:
            for m in self.apiroutine.waitWithTimeout(
                    timeout,
                    OVSDBConnectionSetup.createMatcher(systemid, None, None,
                                                       vhost)):
                yield m
            if self.apiroutine.timeout:
                raise ConnectionResetException('Datapath is not connected')
            self.apiroutine.retvalue = self.apiroutine.event.connection
        else:
            self.apiroutine.retvalue = c

    def getconnectionsbyendpoint(self, endpoint, vhost=''):
        "Get connection by endpoint address (IP, IPv6 or UNIX socket address)"
        for m in self._wait_for_sync():
            yield m
        self.apiroutine.retvalue = self.endpoint_conns.get((vhost, endpoint))

    def getconnectionsbyendpointname(self, name, vhost='', timeout=30):
        "Get connection by endpoint name (Domain name, IP or IPv6 address)"
        # Resolve the name
        if not name:
            endpoint = ''
            for m in self.getconnectionbyendpoint(endpoint, vhost):
                yield m
        else:
            request = (name, 0, socket.AF_UNSPEC, socket.SOCK_STREAM,
                       socket.IPPROTO_TCP,
                       socket.AI_ADDRCONFIG | socket.AI_V4MAPPED)
            # Resolve hostname
            for m in self.apiroutine.waitForSend(ResolveRequestEvent(request)):
                yield m
            for m in self.apiroutine.waitWithTimeout(
                    timeout, ResolveResponseEvent.createMatcher(request)):
                yield m
            if self.apiroutine.timeout:
                # Resolve is only allowed through asynchronous resolver
                #try:
                #    self.addrinfo = socket.getaddrinfo(self.hostname, self.port, socket.AF_UNSPEC, socket.SOCK_DGRAM if self.udp else socket.SOCK_STREAM, socket.IPPROTO_UDP if self.udp else socket.IPPROTO_TCP, socket.AI_ADDRCONFIG|socket.AI_NUMERICHOST)
                #except:
                raise IOError('Resolve hostname timeout: ' + name)
            else:
                if hasattr(self.apiroutine.event, 'error'):
                    raise IOError('Cannot resolve hostname: ' + name)
                resp = self.apiroutine.event.response
                for r in resp:
                    raddr = r[4]
                    if isinstance(raddr, tuple):
                        # Ignore port
                        endpoint = raddr[0]
                    else:
                        # Unix socket? This should not happen, but in case...
                        endpoint = raddr
                    for m in self.getconnectionsbyendpoint(endpoint, vhost):
                        yield m
                    if self.apiroutine.retvalue is not None:
                        break

    def getendpoints(self, vhost=''):
        "Get all endpoints for vhost"
        for m in self._wait_for_sync():
            yield m
        self.apiroutine.retvalue = [
            k[1] for k in self.endpoint_conns if k[0] == vhost
        ]

    def getallendpoints(self):
        "Get all endpoints from any vhost. Return ``(vhost, endpoint)`` pairs."
        for m in self._wait_for_sync():
            yield m
        self.apiroutine.retvalue = list(self.endpoint_conns.keys())

    def getbridgeinfo(self, datapathid, vhost=''):
        "Get ``(bridgename, systemid, bridge_uuid)`` tuple from bridge datapathid"
        for m in self.getconnection(datapathid, vhost):
            yield m
        if self.apiroutine.retvalue is not None:
            c = self.apiroutine.retvalue
            bridges = self.managed_bridges.get(c)
            if bridges is not None:
                for _, dpid, n, buuid in bridges:
                    if dpid == datapathid:
                        self.apiroutine.retvalue = (n, c.ovsdb_systemid, buuid)
                        return
                self.apiroutine.retvalue = None
            else:
                self.apiroutine.retvalue = None

    def waitbridgeinfo(self, datapathid, timeout=30, vhost=''):
        "Wait for bridge with datapathid, and return ``(bridgename, systemid, bridge_uuid)`` tuple"
        for m in self.getbridgeinfo(datapathid, vhost):
            yield m
        if self.apiroutine.retvalue is None:
            for m in self.apiroutine.waitWithTimeout(
                    timeout,
                    OVSDBBridgeSetup.createMatcher(OVSDBBridgeSetup.UP,
                                                   datapathid, None, None,
                                                   None, None, vhost)):
                yield m
            if self.apiroutine.timeout:
                raise OVSDBBridgeNotAppearException(
                    'Bridge 0x%016x does not appear before timeout' %
                    (datapathid, ))
            e = self.apiroutine.event
            self.apiroutine.retvalue = (e.name, e.systemid, e.bridgeuuid)
Ejemplo n.º 2
0
class OVSDBManager(Module):
    '''
    Manage Openflow Connections
    '''
    service = True
    _default_vhostbind = None
    _default_bridgenames = None
    def __init__(self, server):
        Module.__init__(self, server)
        self.apiroutine = RoutineContainer(self.scheduler)
        self.apiroutine.main = self._manage_conns
        self.routines.append(self.apiroutine)
        self.managed_conns = {}
        self.managed_systemids = {}
        self.managed_bridges = {}
        self.managed_routines = []
        self.endpoint_conns = {}
        self.createAPI(api(self.getconnection, self.apiroutine),
                       api(self.waitconnection, self.apiroutine),
                       api(self.getdatapathids, self.apiroutine),
                       api(self.getalldatapathids, self.apiroutine),
                       api(self.getallconnections, self.apiroutine),
                       api(self.getbridges, self.apiroutine),
                       api(self.getbridge, self.apiroutine),
                       api(self.getbridgebyuuid, self.apiroutine),
                       api(self.waitbridge, self.apiroutine),
                       api(self.waitbridgebyuuid, self.apiroutine),
                       api(self.getsystemids, self.apiroutine),
                       api(self.getallsystemids, self.apiroutine),
                       api(self.getconnectionbysystemid, self.apiroutine),
                       api(self.waitconnectionbysystemid, self.apiroutine),
                       api(self.getconnectionsbyendpoint, self.apiroutine),
                       api(self.getconnectionsbyendpointname, self.apiroutine),
                       api(self.getendpoints, self.apiroutine),
                       api(self.getallendpoints, self.apiroutine),
                       api(self.getallbridges, self.apiroutine),
                       api(self.getbridgeinfo, self.apiroutine),
                       api(self.waitbridgeinfo, self.apiroutine)
                       )
        self._synchronized = False
    def _update_bridge(self, connection, protocol, bridge_uuid, vhost):
        try:
            method, params = ovsdb.transact('Open_vSwitch',
                                            ovsdb.wait('Bridge', [["_uuid", "==", ovsdb.uuid(bridge_uuid)]],
                                                        ["datapath_id"], [{"datapath_id": ovsdb.oset()}], False, 5000),
                                            ovsdb.select('Bridge', [["_uuid", "==", ovsdb.uuid(bridge_uuid)]],
                                                                         ["datapath_id","name"]))
            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 datapath-id: ' + repr(r['error']))
            r = self.apiroutine.jsonrpc_result[1]
            if 'error' in r:
                raise JsonRPCErrorResultException('Error while acquiring datapath-id: ' + repr(r['error']))
            if r['rows']:
                r0 = r['rows'][0]
                name = r0['name']
                dpid = int(r0['datapath_id'], 16)
                if self.bridgenames is None or name in self.bridgenames:
                    self.managed_bridges[connection].append((vhost, dpid, name, bridge_uuid))
                    self.managed_conns[(vhost, dpid)] = connection
                    for m in self.apiroutine.waitForSend(OVSDBBridgeSetup(OVSDBBridgeSetup.UP,
                                                               dpid,
                                                               connection.ovsdb_systemid,
                                                               name,
                                                               connection,
                                                               connection.connmark,
                                                               vhost,
                                                               bridge_uuid)):
                        yield m
        except JsonRPCProtocolException:
            pass
    def _get_bridges(self, connection, protocol):
        try:
            try:
                vhost = protocol.vhost
                if not hasattr(connection, 'ovsdb_systemid'):
                    method, params = ovsdb.transact('Open_vSwitch', ovsdb.select('Open_vSwitch', [], ['external_ids']))
                    for m in protocol.querywithreply(method, params, connection, self.apiroutine):
                        yield m
                    result = self.apiroutine.jsonrpc_result[0]
                    system_id = ovsdb.omap_getvalue(result['rows'][0]['external_ids'], 'system-id')
                    connection.ovsdb_systemid = system_id
                else:
                    system_id = connection.ovsdb_systemid
                if (vhost, system_id) in self.managed_systemids:
                    oc = self.managed_systemids[(vhost, system_id)]
                    ep = _get_endpoint(oc)
                    econns = self.endpoint_conns.get((vhost, ep))
                    if econns:
                        try:
                            econns.remove(oc)
                        except ValueError:
                            pass
                    del self.managed_systemids[(vhost, system_id)]
                self.managed_systemids[(vhost, system_id)] = connection
                self.managed_bridges[connection] = []
                ep = _get_endpoint(connection)
                self.endpoint_conns.setdefault((vhost, ep), []).append(connection)
                method, params = ovsdb.monitor('Open_vSwitch', 'ovsdb_manager_bridges_monitor', {'Bridge':ovsdb.monitor_request(['name', 'datapath_id'])})
                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
                    method, params = ovsdb.monitor_cancel('ovsdb_manager_bridges_monitor')
                    for m in protocol.querywithreply(method, params, connection, self.apiroutine, False):
                        yield m
                    method, params = ovsdb.monitor('Open_vSwitch', 'ovsdb_manager_bridges_monitor', {'Bridge':ovsdb.monitor_request(['name', 'datapath_id'])})
                    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))
            except Exception:
                for m in self.apiroutine.waitForSend(OVSDBConnectionSetup(system_id, connection, connection.connmark, vhost)):
                    yield m
                raise
            else:
                # Process initial bridges
                init_subprocesses = [self._update_bridge(connection, protocol, buuid, vhost)
                                    for buuid in self.apiroutine.jsonrpc_result['Bridge'].keys()]
                def init_process():
                    try:
                        with closing(self.apiroutine.executeAll(init_subprocesses, retnames = ())) as g:
                            for m in g:
                                yield m
                    except Exception:
                        for m in self.apiroutine.waitForSend(OVSDBConnectionSetup(system_id, connection, connection.connmark, vhost)):
                            yield m
                        raise
                    else:
                        for m in self.apiroutine.waitForSend(OVSDBConnectionSetup(system_id, connection, connection.connmark, vhost)):
                            yield m
                self.apiroutine.subroutine(init_process())
            # Wait for notify
            notification = JsonRPCNotificationEvent.createMatcher('update', connection, connection.connmark, _ismatch = lambda x: x.params[0] == 'ovsdb_manager_bridges_monitor')
            conn_down = protocol.statematcher(connection)
            while True:
                yield (conn_down, notification)
                if self.apiroutine.matcher is conn_down:
                    break
                else:
                    for buuid, v in self.apiroutine.event.params[1]['Bridge'].items():
                        # If a bridge's name or datapath-id is changed, we remove this bridge and add it again
                        if 'old' in v:
                            # A bridge is deleted
                            bridges = self.managed_bridges[connection]
                            for i in range(0, len(bridges)):
                                if buuid == bridges[i][3]:
                                    self.scheduler.emergesend(OVSDBBridgeSetup(OVSDBBridgeSetup.DOWN,
                                                                               bridges[i][1],
                                                                               system_id,
                                                                               bridges[i][2],
                                                                               connection,
                                                                               connection.connmark,
                                                                               vhost,
                                                                               bridges[i][3],
                                                                               new_datapath_id =
                                                                                int(v['new']['datapath_id'], 16) if 'new' in v and 'datapath_id' in v['new']
                                                                                else None))
                                    del self.managed_conns[(vhost, bridges[i][1])]
                                    del bridges[i]
                                    break
                        if 'new' in v:
                            # A bridge is added
                            self.apiroutine.subroutine(self._update_bridge(connection, protocol, buuid, vhost))
        except JsonRPCProtocolException:
            pass
        finally:
            del connection._ovsdb_manager_get_bridges
    def _manage_existing(self):
        for m in callAPI(self.apiroutine, "jsonrpcserver", "getconnections", {}):
            yield m
        vb = self.vhostbind
        conns = self.apiroutine.retvalue
        for c in conns:
            if vb is None or c.protocol.vhost in vb:
                if not hasattr(c, '_ovsdb_manager_get_bridges'):
                    c._ovsdb_manager_get_bridges = self.apiroutine.subroutine(self._get_bridges(c, c.protocol))
        matchers = [OVSDBConnectionSetup.createMatcher(None, c, c.connmark) for c in conns]
        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_conns(self):
        try:
            self.apiroutine.subroutine(self._manage_existing())
            vb = self.vhostbind
            if vb is not None:
                conn_up = JsonRPCConnectionStateEvent.createMatcher(state = JsonRPCConnectionStateEvent.CONNECTION_UP,
                                                                     _ismatch = lambda x: x.createby.vhost in vb)
                conn_down = JsonRPCConnectionStateEvent.createMatcher(state = JsonRPCConnectionStateEvent.CONNECTION_DOWN,
                                                                     _ismatch = lambda x: x.createby.vhost in vb)
            else:
                conn_up = JsonRPCConnectionStateEvent.createMatcher(state = JsonRPCConnectionStateEvent.CONNECTION_UP)
                conn_down = JsonRPCConnectionStateEvent.createMatcher(state = JsonRPCConnectionStateEvent.CONNECTION_DOWN)
            while True:
                yield (conn_up, conn_down)
                if self.apiroutine.matcher is conn_up:
                    if not hasattr(self.apiroutine.event.connection, '_ovsdb_manager_get_bridges'):
                        self.apiroutine.event.connection._ovsdb_manager_get_bridges = self.apiroutine.subroutine(self._get_bridges(self.apiroutine.event.connection, self.apiroutine.event.createby))
                else:
                    e = self.apiroutine.event
                    conn = e.connection
                    bridges = self.managed_bridges.get(conn)
                    if bridges is not None:
                        del self.managed_systemids[(e.createby.vhost, conn.ovsdb_systemid)]
                        del self.managed_bridges[conn]
                        for vhost, dpid, name, buuid in bridges:
                            del self.managed_conns[(vhost, dpid)]
                            self.scheduler.emergesend(OVSDBBridgeSetup(OVSDBBridgeSetup.DOWN,
                                                                       dpid,
                                                                       conn.ovsdb_systemid,
                                                                       name,
                                                                       conn,
                                                                       conn.connmark,
                                                                       e.createby.vhost,
                                                                       buuid))
                        econns = self.endpoint_conns.get(_get_endpoint(conn))
                        if econns is not None:
                            try:
                                econns.remove(conn)
                            except ValueError:
                                pass
        finally:
            for c in self.managed_bridges.keys():
                if hasattr(c, '_ovsdb_manager_get_bridges'):
                    c._ovsdb_manager_get_bridges.close()
                bridges = self.managed_bridges.get(c)
                if bridges is not None:
                    for vhost, dpid, name, buuid in bridges:
                        del self.managed_conns[(vhost, dpid)]
                        self.scheduler.emergesend(OVSDBBridgeSetup(OVSDBBridgeSetup.DOWN,
                                                                   dpid, 
                                                                   c.ovsdb_systemid, 
                                                                   name, 
                                                                   c, 
                                                                   c.connmark, 
                                                                   c.protocol.vhost,
                                                                   buuid))
    def getconnection(self, datapathid, vhost = ''):
        "Get current connection of datapath"
        for m in self._wait_for_sync():
            yield m
        self.apiroutine.retvalue = self.managed_conns.get((vhost, datapathid))
    def waitconnection(self, datapathid, timeout = 30, vhost = ''):
        "Wait for a datapath connection"
        for m in self.getconnection(datapathid, vhost):
            yield m
        c = self.apiroutine.retvalue
        if c is None:
            for m in self.apiroutine.waitWithTimeout(timeout, 
                            OVSDBBridgeSetup.createMatcher(
                                    state = OVSDBBridgeSetup.UP,
                                    datapathid = datapathid, vhost = vhost)):
                yield m
            if self.apiroutine.timeout:
                raise ConnectionResetException('Datapath is not connected')
            self.apiroutine.retvalue = self.apiroutine.event.connection
        else:
            self.apiroutine.retvalue = c
    def getdatapathids(self, vhost = ''):
        "Get All datapath IDs"
        for m in self._wait_for_sync():
            yield m
        self.apiroutine.retvalue = [k[1] for k in self.managed_conns.keys() if k[0] == vhost]
    def getalldatapathids(self):
        "Get all datapath IDs from any vhost. Return (vhost, datapathid) pair."
        for m in self._wait_for_sync():
            yield m
        self.apiroutine.retvalue = list(self.managed_conns.keys())
    def getallconnections(self, vhost = ''):
        "Get all connections from vhost. If vhost is None, return all connections from any host"
        for m in self._wait_for_sync():
            yield m
        if vhost is None:
            self.apiroutine.retvalue = list(self.managed_bridges.keys())
        else:
            self.apiroutine.retvalue = list(k for k in self.managed_bridges.keys() if k.protocol.vhost == vhost)
    def getbridges(self, connection):
        "Get all (dpid, name, _uuid) tuple on this connection"
        for m in self._wait_for_sync():
            yield m
        bridges = self.managed_bridges.get(connection)
        if bridges is not None:
            self.apiroutine.retvalue = [(dpid, name, buuid) for _, dpid, name, buuid in bridges]
        else:
            self.apiroutine.retvalue = None
    def getallbridges(self, vhost = None):
        "Get all (dpid, name, _uuid) tuple for all connections, optionally filtered by vhost"
        for m in self._wait_for_sync():
            yield m
        if vhost is not None:
            self.apiroutine.retvalue = [(dpid, name, buuid)
                                        for c, bridges in self.managed_bridges.items()
                                        if c.protocol.vhost == vhost
                                        for _, dpid, name, buuid in bridges]
        else:
            self.apiroutine.retvalue = [(dpid, name, buuid)
                                        for c, bridges in self.managed_bridges.items()
                                        for _, dpid, name, buuid in bridges]
    def getbridge(self, connection, name):
        "Get datapath ID on this connection with specified name"
        for m in self._wait_for_sync():
            yield m
        bridges = self.managed_bridges.get(connection)
        if bridges is not None:
            for _, dpid, n, _ in bridges:
                if n == name:
                    self.apiroutine.retvalue = dpid
                    return
            self.apiroutine.retvalue = None
        else:
            self.apiroutine.retvalue = None
    def waitbridge(self, connection, name, timeout = 30):
        "Wait for bridge with specified name appears and return the datapath-id"
        bnames = self.bridgenames
        if bnames is not None and name not in bnames:
            raise OVSDBBridgeNotAppearException('Bridge ' + repr(name) + ' does not appear: it is not in the selected bridge names')
        for m in self.getbridge(connection, name):
            yield m
        if self.apiroutine.retvalue is None:
            bridge_setup = OVSDBBridgeSetup.createMatcher(OVSDBBridgeSetup.UP,
                                                         None,
                                                         None,
                                                         name,
                                                         connection
                                                         )
            conn_down = JsonRPCConnectionStateEvent.createMatcher(JsonRPCConnectionStateEvent.CONNECTION_DOWN,
                                                                  connection,
                                                                  connection.connmark)
            for m in self.apiroutine.waitWithTimeout(timeout, bridge_setup, conn_down):
                yield m
            if self.apiroutine.timeout:
                raise OVSDBBridgeNotAppearException('Bridge ' + repr(name) + ' does not appear')
            elif self.apiroutine.matcher is conn_down:
                raise ConnectionResetException('Connection is down before bridge ' + repr(name) + ' appears')
            else:
                self.apiroutine.retvalue = self.apiroutine.event.datapathid
    def getbridgebyuuid(self, connection, uuid):
        "Get datapath ID of bridge on this connection with specified _uuid"
        for m in self._wait_for_sync():
            yield m
        bridges = self.managed_bridges.get(connection)
        if bridges is not None:
            for _, dpid, _, buuid in bridges:
                if buuid == uuid:
                    self.apiroutine.retvalue = dpid
                    return
            self.apiroutine.retvalue = None
        else:
            self.apiroutine.retvalue = None
    def waitbridgebyuuid(self, connection, uuid, timeout = 30):
        "Wait for bridge with specified _uuid appears and return the datapath-id"
        for m in self.getbridgebyuuid(connection, uuid):
            yield m
        if self.apiroutine.retvalue is None:
            bridge_setup = OVSDBBridgeSetup.createMatcher(state = OVSDBBridgeSetup.UP,
                                                         connection = connection,
                                                         bridgeuuid = uuid
                                                         )
            conn_down = JsonRPCConnectionStateEvent.createMatcher(JsonRPCConnectionStateEvent.CONNECTION_DOWN,
                                                                  connection,
                                                                  connection.connmark)
            for m in self.apiroutine.waitWithTimeout(timeout, bridge_setup, conn_down):
                yield m
            if self.apiroutine.timeout:
                raise OVSDBBridgeNotAppearException('Bridge ' + repr(uuid) + ' does not appear')
            elif self.apiroutine.matcher is conn_down:
                raise ConnectionResetException('Connection is down before bridge ' + repr(uuid) + ' appears')
            else:
                self.apiroutine.retvalue = self.apiroutine.event.datapathid
    def getsystemids(self, vhost = ''):
        "Get All system-ids"
        for m in self._wait_for_sync():
            yield m
        self.apiroutine.retvalue = [k[1] for k in self.managed_systemids.keys() if k[0] == vhost]
    def getallsystemids(self):
        "Get all system-ids from any vhost. Return (vhost, system-id) pair."
        for m in self._wait_for_sync():
            yield m
        self.apiroutine.retvalue = list(self.managed_systemids.keys())
    def getconnectionbysystemid(self, systemid, vhost = ''):
        for m in self._wait_for_sync():
            yield m
        self.apiroutine.retvalue = self.managed_systemids.get((vhost, systemid))
    def waitconnectionbysystemid(self, systemid, timeout = 30, vhost = ''):
        "Wait for a connection with specified system-id"
        for m in self.getconnectionbysystemid(systemid, vhost):
            yield m
        c = self.apiroutine.retvalue
        if c is None:
            for m in self.apiroutine.waitWithTimeout(timeout, 
                            OVSDBConnectionSetup.createMatcher(
                                    systemid, None, None, vhost)):
                yield m
            if self.apiroutine.timeout:
                raise ConnectionResetException('Datapath is not connected')
            self.apiroutine.retvalue = self.apiroutine.event.connection
        else:
            self.apiroutine.retvalue = c
    def getconnectionsbyendpoint(self, endpoint, vhost = ''):
        "Get connection by endpoint address (IP, IPv6 or UNIX socket address)"
        for m in self._wait_for_sync():
            yield m
        self.apiroutine.retvalue = self.endpoint_conns.get((vhost, endpoint))
    def getconnectionsbyendpointname(self, name, vhost = '', timeout = 30):
        "Get connection by endpoint name (Domain name, IP or IPv6 address)"
        # Resolve the name
        if not name:
            endpoint = ''
            for m in self.getconnectionbyendpoint(endpoint, vhost):
                yield m
        else:
            request = (name, 0, socket.AF_UNSPEC, socket.SOCK_STREAM, socket.IPPROTO_TCP, socket.AI_ADDRCONFIG | socket.AI_V4MAPPED)
            # Resolve hostname
            for m in self.apiroutine.waitForSend(ResolveRequestEvent(request)):
                yield m
            for m in self.apiroutine.waitWithTimeout(timeout, ResolveResponseEvent.createMatcher(request)):
                yield m
            if self.apiroutine.timeout:
                # Resolve is only allowed through asynchronous resolver
                #try:
                #    self.addrinfo = socket.getaddrinfo(self.hostname, self.port, socket.AF_UNSPEC, socket.SOCK_DGRAM if self.udp else socket.SOCK_STREAM, socket.IPPROTO_UDP if self.udp else socket.IPPROTO_TCP, socket.AI_ADDRCONFIG|socket.AI_NUMERICHOST)
                #except:
                raise IOError('Resolve hostname timeout: ' + name)
            else:
                if hasattr(self.apiroutine.event, 'error'):
                    raise IOError('Cannot resolve hostname: ' + name)
                resp = self.apiroutine.event.response
                for r in resp:
                    raddr = r[4]
                    if isinstance(raddr, tuple):
                        # Ignore port
                        endpoint = raddr[0]
                    else:
                        # Unix socket? This should not happen, but in case...
                        endpoint = raddr
                    for m in self.getconnectionsbyendpoint(endpoint, vhost):
                        yield m
                    if self.apiroutine.retvalue is not None:
                        break
    def getendpoints(self, vhost = ''):
        "Get all endpoints for vhost"
        for m in self._wait_for_sync():
            yield m
        self.apiroutine.retvalue = [k[1] for k in self.endpoint_conns if k[0] == vhost]
    def getallendpoints(self):
        "Get all endpoints from any vhost. Return (vhost, endpoint) pairs."
        for m in self._wait_for_sync():
            yield m
        self.apiroutine.retvalue = list(self.endpoint_conns.keys())
    def getbridgeinfo(self, datapathid, vhost = ''):
        "Get (bridgename, systemid, bridge_uuid) tuple from bridge datapathid"
        for m in self.getconnection(datapathid, vhost):
            yield m
        if self.apiroutine.retvalue is not None:
            c = self.apiroutine.retvalue
            bridges = self.managed_bridges.get(c)
            if bridges is not None:
                for _, dpid, n, buuid in bridges:
                    if dpid == datapathid:
                        self.apiroutine.retvalue = (n, c.ovsdb_systemid, buuid)
                        return
                self.apiroutine.retvalue = None
            else:
                self.apiroutine.retvalue = None
    def waitbridgeinfo(self, datapathid, timeout = 30, vhost = ''):
        "Wait for bridge with datapathid, and return (bridgename, systemid, bridge_uuid) tuple"
        for m in self.getbridgeinfo(datapathid, vhost):
            yield m
        if self.apiroutine.retvalue is None:
            for m in self.apiroutine.waitWithTimeout(timeout,
                        OVSDBBridgeSetup.createMatcher(
                                    OVSDBBridgeSetup.UP, datapathid,
                                    None, None, None, None,
                                    vhost)):
                yield m
            if self.apiroutine.timeout:
                raise OVSDBBridgeNotAppearException('Bridge 0x%016x does not appear before timeout' % (datapathid,))
            e = self.apiroutine.event
            self.apiroutine.retvalue = (e.name, e.systemid, e.bridgeuuid)
Ejemplo n.º 3
0
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
Ejemplo n.º 4
0
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