Example #1
0
    def communicate(self, hostname, context):
        """
        Create and connect / bind a socket
        """
        log.info(
            "Creating socket to entity %s:%d", self._remote_addr, self._port
        )

        self._zmq = context.socket(Socket.ZTYPE[self.type])

        if self.type == Socket.TYPE_EP_REP:
            self._zmq.bind("tcp://%s:%s" % (self.config.LOCAL_ADDR, self._port))
        else:
            self._zmq.connect("tcp://%s:%s" % (self._remote_addr, self._port))

        if self.type == Socket.TYPE_ACL_SUB:
            self._zmq.setsockopt(zmq.IDENTITY, hostname)
            self._zmq.setsockopt(zmq.SUBSCRIBE, 'aclheartbeat')

            for ep_id in self._subscriptions:
                self._zmq.setsockopt(zmq.SUBSCRIBE, ep_id)

        # The socket connection event is always the time of last activity.
        self._last_activity = futils.time_ms()

        # We do not have a request outstanding.
        self._request_outstanding = False

        # Whatever is on the queues is gone.
        self.clear_queue()
Example #2
0
    def send(self, msg):
        """
        Send a specified message on a socket.
        """
        if self._request_outstanding:
            # Request socket with outstanding message; queue it.
            log.info("Queuing %s on socket %s", msg.descr, self.type)
            self._send_queue.appendleft(msg)
            return

        log.info("Sent %s on socket %s", msg.descr, self.type)
        self._last_activity = futils.time_ms()

        #*********************************************************************#
        #* We never expect any type of socket that we use to block since we  *#
        #* use only REQ or REP sockets - so if we get blocking then we       *#
        #* consider that something is wrong, and let the exception take down *#
        #* Felix.                                                            *#
        #*********************************************************************#
        try:
            self._zmq.send(msg.zmq_msg, zmq.NOBLOCK)

            if self.type in Socket.REQUEST_TYPES:
                self._request_outstanding = True
        except:
            log.exception("Socket %s blocked on send", self.type)
            raise
Example #3
0
    def resync_endpoints(self):
        """
        This function is called to resync all endpoint state, both periodically
        and during initialisation.
        """
        self.resync_id = str(uuid.uuid4())
        self.resync_recd = 0
        self.resync_expected = None

        #*********************************************************************#
        #* Log the version here, ensuring that we log it periodically (in    *#
        #* case we try to debug with logs that do not cover Felix starting). *#
        #*********************************************************************#
        log.info("Do total resync - ID : %s (version: %s)", self.resync_id,
                 pkg_resources.get_distribution('calico'))

        # Mark all the endpoints as expecting to be resynchronized.
        for ep in self.endpoints.values():
            ep.pending_resync = True

        # Since we are about to ask for ACLs for all endpoints too, we want to
        # clear that queue.
        self.sockets[Socket.TYPE_ACL_REQ].clear_queue()

        # Send the RESYNCSTATE message.
        fields = {
            'resync_id': self.resync_id,
            'issued': futils.time_ms(),
            'hostname': self.hostname,
        }
        self.send_request(Message(Message.TYPE_RESYNC, fields),
                          Socket.TYPE_EP_REQ)
Example #4
0
 def timed_out(self):
     """
     Returns True if the socket has been inactive for at least the timeout;
     all sockets must have keepalives on them.
     """
     return ((futils.time_ms() - self._last_activity) >=
             self.config.CONN_TIMEOUT_MS)
Example #5
0
    def resync_endpoints(self):
        """
        This function is called to resync all endpoint state, both periodically
        and during initialisation.
        """
        self.resync_id = str(uuid.uuid4())
        self.resync_recd = 0
        self.resync_expected = None

        log.info("Do total resync - ID : %s" % self.resync_id)

        # Mark all the endpoints as expecting to be resynchronized.
        for ep in self.endpoints.values():
            ep.pending_resync = True

        # If we had anything queued up to send, clear the queue - it is
        # superseded. Since we are about to ask for ACLs for all endpoints too,
        # we want to clear that queue as well.
        self.endpoint_queue.clear()
        self.acl_queue.clear()

        # Send the RESYNCSTATE message.
        fields = {
            'resync_id': self.resync_id,
            'issued': futils.time_ms(),
            'hostname': self.hostname,
        }
        self.send_request(Message(Message.TYPE_RESYNC, fields),
                          Socket.TYPE_EP_REQ)
Example #6
0
    def _create_endpoint(self, endpoint_id, mac, interface):
        """
        Creates an endpoint after having been informed about it over the API.
        Does the state programming required to get future updates for this
        endpoint, and issues a request for its ACL state.

        This routine must only be called if the endpoint is not already known
        to Felix.
        """
        log.debug("Create endpoint %s", endpoint_id)

        endpoint = Endpoint(endpoint_id, mac, interface, self.iface_prefix)

        self.endpoints[endpoint_id] = endpoint

        # Start listening to the subscription for this endpoint.
        self.sockets[Socket.TYPE_ACL_SUB].subscribe(
            endpoint_id.encode('utf-8'))

        # Having subscribed, we can now request ACL state for this endpoint.
        fields = {'endpoint_id': endpoint_id, 'issued': futils.time_ms()}
        self.send_request(Message(Message.TYPE_GET_ACL, fields),
                          Socket.TYPE_ACL_REQ)

        return endpoint
Example #7
0
    def keepalive_due(self):
        """
        Returns True if we are due to send a keepalive on the socket.

        The caller is responsible for deciding which sockets need keepalives.
        """
        return ((futils.time_ms() - self.last_activity) >
                self.config.CONN_KEEPALIVE_MS)
Example #8
0
 def keepalive(self):
     """
     Send a keepalive if and only if we need to send one.
     """
     if ((self.type in Socket.REQUEST_TYPES         ) and
         (not self._request_outstanding             ) and
         (futils.time_ms() - self._last_activity >
                       self.config.CONN_KEEPALIVE_MS)     ):
         # Time for a keepalive
         log.debug("Sending keepalive on socket %s", self.type)
         self.send(Message(Message.TYPE_HEARTBEAT, {}))
Example #9
0
    def resync_acls(self):
        """
        Initiates a full ACL resynchronisation procedure.
        """
        # ACL resynchronization involves requesting ACLs for all endpoints
        # for which we have an ID.
        self.acl_queue.clear()

        for endpoint_id, endpoint in self.endpoints.iteritems():
            fields = {'endpoint_id': endpoint_id, 'issued': futils.time_ms()}
            self.send_request(Message(Message.TYPE_GET_ACL, fields),
                              Socket.TYPE_ACL_REQ)
Example #10
0
    def resync_acls(self):
        """
        Initiates a full ACL resynchronisation procedure.
        """
        # ACL resynchronization involves requesting ACLs for all endpoints
        # for which we have an ID. That means any queued requests are really
        # no longer relevant as they are duplicates.
        self.sockets[Socket.TYPE_ACL_REQ].clear_queue()

        for endpoint_id, endpoint in self.endpoints.iteritems():
            fields = {'endpoint_id': endpoint_id, 'issued': futils.time_ms()}
            self.send_request(Message(Message.TYPE_GET_ACL, fields),
                              Socket.TYPE_ACL_REQ)
Example #11
0
    def restart(self, hostname, context):
        """
        Restart the socket. This only restarts the underlying socket if it is
        valid to do so; we should restart connections on which we connect, not
        to which we bind.

        The point of calling this method for sockets that it just resets the
        timer (so that the application layer is not continually being told
        about a failed connection).
        """
        if self.type in Socket.RESTART_TYPES:
            self.close()
            self.communicate(hostname, context)
        else:
            self._last_activity = futils.time_ms()
Example #12
0
    def receive(self):
        """
        Receive a message on this socket. For subscriptions, this will return
        a list of bytes.
        """
        log.debug("Received something on %s", self.type)

        #*********************************************************************#
        #* We never expect any type of socket that we use to block since we  *#
        #* just polled to check - so if we get blocking then we consider     *#
        #* that something is wrong, and let the exception take down Felix.   *#
        #*********************************************************************#
        try:
            if self.type != Socket.TYPE_ACL_SUB:
                data = self._zmq.recv(zmq.NOBLOCK)
                uuid = None
            else:
                uuid, data = self._zmq.recv_multipart(zmq.NOBLOCK)
        except:
            log.exception("Socket %s blocked on receive", self.type)
            raise

        message = Message.parse_message(data, uuid)

        # Log that we received the message.
        log.info("Received %s on socket %s" % (message.descr, self.type))

        # If this is a response, we're no longer waiting for one.
        if self.type in Socket.REQUEST_TYPES:
            self._request_outstanding = False
            if len(self._send_queue):
                # Queued message; send it now.
                self.send(self._send_queue.pop())

        self._last_activity = futils.time_ms()

        # A special case: heartbeat messages on the subscription interface are
        # swallowed; the application code has no use for them.
        if (self.type == Socket.TYPE_ACL_SUB and
                message.type == Message.TYPE_HEARTBEAT):
            return None

        return message
Example #13
0
    def complete_endpoint_resync(self, successful):
        """
        Resync has finished
        """
        log.debug("Finishing resynchronisation, success = %s", successful)
        self.resync_id = None
        self.resync_recd = None
        self.resync_expected = None
        self.resync_time = futils.time_ms()

        if successful:
            for uuid in self.endpoints.keys():
                ep = self.endpoints[uuid]
                if ep.pending_resync:
                    log.info(
                        "Remove endpoint %s that is no longer being managed" %
                        ep.uuid)
                    ep.remove(self.iptables_state)
                    del self.endpoints[uuid]

        #*********************************************************************#
        #* Now remove rules for any endpoints that should no longer          *#
        #* exist. This method returns a set of endpoint suffices.            *#
        #*********************************************************************#
        known_suffices = set(ep.suffix for ep in self.endpoints.values())

        for type in [futils.IPV4, futils.IPV6]:
            found_suffices = frules.list_eps_with_rules(
                self.iptables_state, type)

            for found_suffix in found_suffices:
                if found_suffix not in known_suffices:
                    # Found rules which we own for an endpoint which does not
                    # exist.  Remove those rules.
                    log.warning("Removing %s rules for removed object %s",
                                type, found_suffix)
                    frules.del_rules(self.iptables_state, found_suffix, type)
Example #14
0
    def run(self):
        """
        Executes one iteration of the main agent loop.
        """
        # Issue a poll request on all active sockets.
        endpoint_resync_needed = False
        acl_resync_needed = False

        if self.iface_prefix:
            poll_list = self.sockets.values()
        else:
            # Not got an first resync response (as no interface prefix), so
            # ignore all sockets except the EP_REQ socket until we do.
            poll_list = [self.sockets[Socket.TYPE_EP_REQ]]

        active_sockets = fsocket.poll(poll_list, self.config.EP_RETRY_INT_MS)

        # For each active socket, pull the message off and handle it.
        for sock in active_sockets:
            message = sock.receive()

            if message is not None:
                try:
                    self.handlers[message.type](message, sock)
                except KeyError:
                    # We are going down, but raise a better exception.
                    raise InvalidRequest("Unrecognised message type",
                                         message.fields)

        for sock in self.sockets.values():
            #*****************************************************************#
            #* See if anything else is required on this socket. First, check *#
            #* whether any have timed out.  A timed out socket needs to be   *#
            #* reconnected. Also, whatever API it belongs to needs to be     *#
            #* resynchronised.                                               *#
            #*****************************************************************#
            if sock.timed_out():
                log.error("Timed out remote entity : %s", sock.descr)

                #*************************************************************#
                #* If we lost the connection on which we would receive       *#
                #* ENDPOINTCREATED messages, we need to trigger a total      *#
                #* endpoint resync, and similarly for ACLs if we have lost   *#
                #* the connection on which we would receive ACLUPDATE        *#
                #* messages.                                                 *#
                #*************************************************************#
                if sock.type == Socket.TYPE_EP_REP:
                    #*********************************************************#
                    #* We lost the connection on which we would receive      *#
                    #* ENDPOINTCREATED messages. We may be out of step, so   *#
                    #* need a total endpoint update.                         *#
                    #*********************************************************#
                    endpoint_resync_needed = True
                elif (sock.type == Socket.TYPE_ACL_SUB
                      or sock.type == Socket.TYPE_ACL_REQ):
                    #*********************************************************#
                    #* We lost the connection on which we would receive      *#
                    #* ACLUPDATE messages, or might have lost some queued    *#
                    #* GETACLSTATE messages. We may be out of step, so we    *#
                    #* need a total ACL resync.                              *#
                    #*********************************************************#
                    acl_resync_needed = True

                if (self.resync_id is not None and sock.type
                        in (Socket.TYPE_EP_REQ, Socket.TYPE_EP_REP)):
                    #*********************************************************#
                    #* A resync was in progress, but we may have lost the    *#
                    #* RESYNCSTATE request or response or (potentially) an   *#
                    #* ENDPOINTCREATED message due to a lost                 *#
                    #* connection. That means we have to give up on this     *#
                    #* resync, tidy up and retry.                            *#
                    #*********************************************************#
                    self.complete_endpoint_resync(False)
                    endpoint_resync_needed = True

                # Recreate the socket.
                sock.restart(self.hostname, self.zmq_context)

        # Now, check if we need to resynchronize and do it.
        if (self.resync_id is None and (futils.time_ms() - self.resync_time >
                                        self.config.RESYNC_INT_SEC * 1000)):
            # Time for a total resync of all endpoints
            endpoint_resync_needed = True

        if endpoint_resync_needed:
            self.resync_endpoints()
        elif acl_resync_needed:
            #*****************************************************************#
            #* Note that an endpoint resync implicitly involves an ACL       *#
            #* resync, so there is no point in triggering one when an        *#
            #* endpoint resync has just started (as opposed to when we are   *#
            #* in the middle of an endpoint resync and just lost our         *#
            #* connection).                                                  *#
            #*****************************************************************#
            self.resync_acls()

        # We are not about to send any more messages; send any required
        # keepalives.
        for sock in self.sockets.values():
            sock.keepalive()

        #*********************************************************************#
        #* Finally, retry any endpoints which need retrying. We remove them  *#
        #* from ep_retry if they no longer exist or if the retry succeeds;   *#
        #* the simplest way to do this is to copy the list, clear ep_retry   *#
        #* then add them back if necessary.                                  *#
        #*********************************************************************#
        retry_list = list(self.ep_retry)
        self.ep_retry.clear()
        for uuid in retry_list:
            if uuid in self.endpoints:
                endpoint = self.endpoints[uuid]
                log.debug("Retry program of %s" % endpoint.suffix)
                if endpoint.program_endpoint(self.iptables_state):
                    # Failed again - put back on list
                    self.ep_retry.add(uuid)
                else:
                    # Programmed OK, so apply any ACLs we might have.
                    endpoint.update_acls()
            else:
                log.debug("No retry programming %s - no longer exists" % uuid)
Example #15
0
    def run(self):
        """
        Executes one iteration of the main agent loop.
        """
        # Issue a poll request on all active sockets.
        endpoint_resync_needed = False
        acl_resync_needed = False

        lPoller = zmq.Poller()
        for sock in self.sockets.values():
            # Easier just to poll all sockets, even if we expect nothing.
            lPoller.register(sock._zmq, zmq.POLLIN)

        polled_sockets = dict(lPoller.poll(self.config.EP_RETRY_INT_MS))

        # Get all the sockets with activity.
        active_sockets = (s for s in self.sockets.values()
                          if s._zmq in polled_sockets
                          and polled_sockets[s._zmq] == zmq.POLLIN)

        # For each active socket, pull the message off and handle it.
        for sock in active_sockets:
            message = sock.receive()

            if message is not None:
                self.handlers[message.type](message, sock)

        for sock in self.sockets.values():
            #*****************************************************************#
            #* See if anything else is required on this socket. First, check *#
            #* whether any have timed out.  A timed out socket needs to be   *#
            #* reconnected. Also, whatever API it belongs to needs to be     *#
            #* resynchronised.                                               *#
            #*****************************************************************#
            if sock.timed_out():
                log.warning("Socket %s timed out", sock.type)
                sock.close()

                #*************************************************************#
                #* If we lost the connection on which we would receive       *#
                #* ENDPOINTCREATED messages, we need to trigger a total      *#
                #* endpoint resync, and similarly for ACLs if we have lost   *#
                #* the connection on which we would receive ACLUPDATE        *#
                #* messages.                                                 *#
                #*************************************************************#
                if sock.type == Socket.TYPE_EP_REP:
                    endpoint_resync_needed = True
                elif sock.type == Socket.TYPE_ACL_SUB:
                    acl_resync_needed = True

                # Flush the message queue.
                if sock.type == Socket.TYPE_EP_REQ:
                    self.endpoint_queue.clear()
                elif sock.type == Socket.TYPE_ACL_REQ:
                    self.acl_queue.clear()

                # Recreate the socket.
                sock.communicate(self.hostname, self.zmq_context)

                # If this is the ACL SUB socket, then subscribe for all
                # endpoints.
                if sock.type == Socket.TYPE_ACL_SUB:
                    for endpoint_id in self.endpoints:
                        sock._zmq.setsockopt(zmq.SUBSCRIBE,
                                             endpoint_id.encode('utf-8'))

        # If we have any queued messages to send, we should do so.
        endpoint_socket = self.sockets[Socket.TYPE_EP_REQ]
        acl_socket = self.sockets[Socket.TYPE_ACL_REQ]

        if (len(self.endpoint_queue)
                and not endpoint_socket.request_outstanding):
            message = self.endpoint_queue.pop()
            endpoint_socket.send(message)
        elif (endpoint_socket.keepalive_due()
              and not endpoint_socket.request_outstanding):
            endpoint_socket.send(Message(Message.TYPE_HEARTBEAT, {}))

        if len(self.acl_queue) and not acl_socket.request_outstanding:
            message = self.acl_queue.pop()
            acl_socket.send(message)
        elif (acl_socket.keepalive_due()
              and not acl_socket.request_outstanding):
            acl_socket.send(Message(Message.TYPE_HEARTBEAT, {}))

        # Now, check if we need to resynchronize and do it.
        if (self.resync_id is None and (futils.time_ms() - self.resync_time >
                                        self.config.RESYNC_INT_SEC * 1000)):
            # Time for a total resync of all endpoints
            endpoint_resync_needed = True

        if endpoint_resync_needed:
            self.resync_endpoints()
        elif acl_resync_needed:
            #*****************************************************************#
            #* Note that an endpoint resync implicitly involves an ACL       *#
            #* resync, so there is no point in triggering one when an        *#
            #* endpoint resync has just started (as opposed to when we are   *#
            #* in the middle of an endpoint resync and just lost our         *#
            #* connection).                                                  *#
            #*****************************************************************#
            self.resync_acls()

        #*********************************************************************#
        #* Finally, retry any endpoints which need retrying. We remove them  *#
        #* from ep_retry if they no longer exist or if the retry succeeds;   *#
        #* the simplest way to do this is to copy the list, clear ep_retry   *#
        #* then add them back if necessary.                                  *#
        #*********************************************************************#
        retry_list = list(self.ep_retry)
        self.ep_retry.clear()
        for uuid in retry_list:
            if uuid in self.endpoints:
                endpoint = self.endpoints[uuid]
                log.debug("Retry program of %s" % endpoint.suffix)
                if endpoint.program_endpoint():
                    # Failed again - put back on list
                    self.ep_retry.add(uuid)
                else:
                    # Programmed OK, so apply any ACLs we might have.
                    endpoint.update_acls()
            else:
                log.debug("No retry programming %s - no longer exists" % uuid)
Example #16
0
    def test_resync(self):
        """
        Test the resync flows.
        """
        common.default_logging()
        context = stub_zmq.Context()
        agent = felix.FelixAgent(config_path, context)

        #*********************************************************************#
        #* Set the resync timeout to 5 seconds, and the KEEPALIVE timeout to *#
        #* much more.                                                        *#
        #*********************************************************************#
        agent.config.RESYNC_INT_SEC = 5
        agent.config.CONN_TIMEOUT_MS = 50000
        agent.config.CONN_KEEPALIVE_MS = 50000

        # Get started.
        context.add_poll_result(0)
        agent.run()

        # Now we should have got a resync request.
        resync_req = context.sent_data[TYPE_EP_REQ].pop()
        log.debug("Resync request : %s" % resync_req)
        self.assertFalse(context.sent_data_present())
        resync_id = resync_req['resync_id']
        resync_rsp = {
            'type': "RESYNCSTATE",
            'endpoint_count': "0",
            'rc': "SUCCESS",
            'message': "hello"
        }

        poll_result = context.add_poll_result(1000)
        poll_result.add(TYPE_EP_REQ, resync_rsp)
        agent.run()
        # nothing yet
        self.assertFalse(context.sent_data_present())

        poll_result = context.add_poll_result(5999)
        agent.run()
        # nothing yet - 4999 ms since last request
        self.assertFalse(context.sent_data_present())

        poll_result = context.add_poll_result(6001)
        agent.run()

        # We should have got another resync request.
        resync_req = context.sent_data[TYPE_EP_REQ].pop()
        log.debug("Resync request : %s" % resync_req)
        self.assertFalse(context.sent_data_present())
        resync_id = resync_req['resync_id']
        resync_rsp = {
            'type': "RESYNCSTATE",
            'endpoint_count': "2",
            'rc': "SUCCESS",
            'message': "hello"
        }

        # No more resyncs until enough data has arrived.
        poll_result = context.add_poll_result(15000)
        poll_result.add(TYPE_EP_REQ, resync_rsp)
        agent.run()
        self.assertFalse(context.sent_data_present())

        # Send an endpoint created message to Felix.
        endpoint_id = str(uuid.uuid4())
        log.debug("Build first endpoint created : %s" % endpoint_id)
        mac = stub_utils.get_mac()
        suffix = endpoint_id[:11]
        tap = "tap" + suffix
        addr = '1.2.3.4'
        endpoint_created_req = {
            'type': "ENDPOINTCREATED",
            'endpoint_id': endpoint_id,
            'resync_id': resync_id,
            'issued': futils.time_ms(),
            'mac': mac,
            'state': Endpoint.STATE_ENABLED,
            'addrs': [{
                'gateway': "1.2.3.1",
                'addr': addr
            }]
        }

        poll_result = context.add_poll_result(15001)
        poll_result.add(TYPE_EP_REP, endpoint_created_req)
        agent.run()

        # We stop using sent_data_present, since there are ACL requests around.
        endpoint_created_rsp = context.sent_data[TYPE_EP_REP].pop()
        self.assertEqual(endpoint_created_rsp['rc'], "SUCCESS")
        self.assertFalse(context.sent_data[TYPE_EP_REQ])

        # Send a second endpoint created message to Felix - triggers another resync.
        endpoint_id = str(uuid.uuid4())
        log.debug("Build second endpoint created : %s" % endpoint_id)
        mac = stub_utils.get_mac()
        suffix = endpoint_id[:11]
        tap = "tap" + suffix
        addr = '1.2.3.5'
        endpoint_created_req = {
            'type': "ENDPOINTCREATED",
            'endpoint_id': endpoint_id,
            'resync_id': resync_id,
            'issued': futils.time_ms(),
            'mac': mac,
            'state': Endpoint.STATE_ENABLED,
            'addrs': [{
                'gateway': "1.2.3.1",
                'addr': addr
            }]
        }

        poll_result = context.add_poll_result(15002)
        poll_result.add(TYPE_EP_REP, endpoint_created_req)
        agent.run()

        endpoint_created_rsp = context.sent_data[TYPE_EP_REP].pop()
        self.assertEqual(endpoint_created_rsp['rc'], "SUCCESS")
        self.assertFalse(context.sent_data[TYPE_EP_REQ])

        # No more resyncs until enough 5000 ms after last rsp.
        poll_result = context.add_poll_result(20000)
        poll_result.add(TYPE_EP_REQ, resync_rsp)
        agent.run()
        self.assertFalse(context.sent_data[TYPE_EP_REQ])

        # We should have got another resync request.
        poll_result = context.add_poll_result(20003)
        poll_result.add(TYPE_EP_REP, endpoint_created_req)
        agent.run()
        resync_req = context.sent_data[TYPE_EP_REQ].pop()
        log.debug("Resync request : %s" % resync_req)
        self.assertFalse(context.sent_data[TYPE_EP_REQ])
Example #17
0
 def test_time_ms(self):
     # Bit feeble, but validate that we can call it and get back something.
     time_ms = futils.time_ms()
Example #18
0
 def test_time_ms(self):
     # Bit feeble, but validate that we can call it and get back something.
     time_ms = futils.time_ms()
Example #19
0
    def test_main_flow(self):
        """
        Test starting up and going through some of the basic flow.
        """
        common.default_logging()
        context = stub_zmq.Context()
        agent = felix.FelixAgent(config_path, context)
        context.add_poll_result(0)
        agent.run()

        # Now we want to reply to the RESYNC request.
        resync_req = context.sent_data[TYPE_EP_REQ].pop()
        log.debug("Resync request : %s" % resync_req)
        self.assertFalse(context.sent_data_present())
        resync_id = resync_req['resync_id']
        resync_rsp = {
            'type': "RESYNCSTATE",
            'endpoint_count': 1,
            'rc': "SUCCESS",
            'message': "hello"
        }

        poll_result = context.add_poll_result(50)
        poll_result.add(TYPE_EP_REQ, resync_rsp)
        agent.run()

        # Felix expects one endpoint created message - give it what it wants
        endpoint_id = str(uuid.uuid4())
        log.debug("Build first endpoint created : %s" % endpoint_id)
        mac = stub_utils.get_mac()
        suffix = endpoint_id[:11]
        tap = "tap" + suffix
        addr = '1.2.3.4'
        endpoint_created_req = {
            'type': "ENDPOINTCREATED",
            'endpoint_id': endpoint_id,
            'resync_id': resync_id,
            'issued': futils.time_ms(),
            'mac': mac,
            'state': Endpoint.STATE_ENABLED,
            'addrs': [{
                'gateway': "1.2.3.1",
                'addr': addr
            }]
        }

        poll_result = context.add_poll_result(100)
        poll_result.add(TYPE_EP_REP, endpoint_created_req)
        agent.run()

        log.debug("Create tap interface %s" % tap)
        tap_obj = stub_devices.TapInterface(tap)
        stub_devices.add_tap(tap_obj)
        poll_result = context.add_poll_result(150)
        agent.run()

        #*********************************************************************#
        #* As soon as that endpoint has been made to exist, we should see an *#
        #* ACL request coming through, and a response to the endpoint        *#
        #* created.  We send a reply to that now.                            *#
        #*********************************************************************#
        endpoint_created_rsp = context.sent_data[TYPE_EP_REP].pop()
        self.assertEqual(endpoint_created_rsp['rc'], "SUCCESS")

        acl_req = context.sent_data[TYPE_ACL_REQ].pop()
        self.assertFalse(context.sent_data_present())
        self.assertEqual(acl_req['endpoint_id'], endpoint_id)

        acl_rsp = {'type': "GETACLSTATE", 'rc': "SUCCESS", 'message': ""}
        poll_result = context.add_poll_result(200)
        poll_result.add(TYPE_ACL_REQ, acl_rsp)

        # Check the rules are what we expect.
        set_expected_global_rules()
        add_endpoint_rules(suffix, tap, addr, None, mac)
        stub_fiptables.check_state(expected_iptables)
        add_endpoint_ipsets(suffix)
        stub_ipsets.check_state(expected_ipsets)

        # OK - now try giving it some ACLs, and see if they get applied correctly.
        acls = get_blank_acls()
        acls['v4']['outbound'].append({
            'cidr': "0.0.0.0/0",
            'protocol': "icmp"
        })
        acls['v4']['outbound'].append({
            'cidr': "1.2.3.0/24",
            'protocol': "tcp"
        })
        acls['v4']['outbound'].append({
            'cidr': "0.0.0.0/0",
            'protocol': "tcp",
            'port': "80"
        })
        acls['v4']['inbound'].append({
            'cidr': "1.2.2.0/24",
            'protocol': "icmp"
        })
        acls['v4']['inbound'].append({
            'cidr': "0.0.0.0/0",
            'protocol': "tcp",
            'port': "8080"
        })
        acls['v4']['inbound'].append({
            'cidr': "2.4.6.8/32",
            'protocol': "udp",
            'port': "8080"
        })
        acls['v4']['inbound'].append({'cidr': "1.2.3.3/32"})
        acls['v4']['inbound'].append({
            'cidr': "3.6.9.12/32",
            'protocol': "tcp",
            'port': ['10', '50']
        })

        acls['v4']['inbound'].append({
            'cidr': "5.4.3.2/32",
            'protocol': "icmp",
            'icmp_type': "3",
            'icmp_code': "2"
        })

        acls['v4']['inbound'].append({
            'cidr': "5.4.3.2/32",
            'protocol': "icmp",
            'icmp_type': "9"
        })

        acls['v4']['inbound'].append({
            'cidr': "5.4.3.2/32",
            'protocol': "icmp",
            'icmp_type': "blah"
        })

        # We include a couple of invalid rules that Felix will just ignore (and log).
        acls['v4']['inbound'].append({
            'cidr': "4.3.2.1/32",
            'protocol': "tcp",
            'port': ['blah', 'blah']
        })
        acls['v4']['inbound'].append({
            'cidr': "4.3.2.1/32",
            'protocol': "tcp",
            'port': ['1', '2', '3']
        })
        acls['v4']['inbound'].append({
            'cidr': "4.3.2.1/32",
            'protocol': "tcp",
            'port': 'flibble'
        })
        acls['v4']['inbound'].append({'protocol': "tcp"})
        acls['v4']['inbound'].append({'cidr': "4.3.2.1/32", 'port': "123"})
        acls['v4']['inbound'].append({
            'cidr': "4.3.2.1/32",
            'protocol': "icmp",
            'icmp_code': "blah"
        })
        acls['v4']['inbound'].append({
            'cidr': "4.3.2.1/32",
            'protocol': "icmp",
            'port': "1"
        })
        acls['v4']['inbound'].append({
            'cidr': "4.3.2.1/32",
            'protocol': "rsvp",
            'port': "1"
        })

        acl_req = {'type': "ACLUPDATE", 'acls': acls}

        poll_result.add(TYPE_ACL_SUB, acl_req, endpoint_id)
        agent.run()

        stub_fiptables.check_state(expected_iptables)
        expected_ipsets.add("felix-from-icmp-" + suffix, "0.0.0.0/1")
        expected_ipsets.add("felix-from-icmp-" + suffix, "128.0.0.0/1")
        expected_ipsets.add("felix-from-port-" + suffix, "1.2.3.0/24,tcp:0")
        expected_ipsets.add("felix-from-port-" + suffix, "0.0.0.0/1,tcp:80")
        expected_ipsets.add("felix-from-port-" + suffix, "128.0.0.0/1,tcp:80")

        expected_ipsets.add("felix-to-icmp-" + suffix, "1.2.2.0/24")
        expected_ipsets.add("felix-to-port-" + suffix, "0.0.0.0/1,tcp:8080")
        expected_ipsets.add("felix-to-port-" + suffix, "128.0.0.0/1,tcp:8080")
        expected_ipsets.add("felix-to-port-" + suffix, "2.4.6.8/32,udp:8080")
        expected_ipsets.add("felix-to-addr-" + suffix, "1.2.3.3/32")
        expected_ipsets.add("felix-to-port-" + suffix, "3.6.9.12/32,tcp:10-50")
        expected_ipsets.add("felix-to-port-" + suffix, "5.4.3.2/32,icmp:3/2")
        expected_ipsets.add("felix-to-port-" + suffix, "5.4.3.2/32,icmp:9/0")
        expected_ipsets.add("felix-to-port-" + suffix, "5.4.3.2/32,icmp:blah")

        stub_ipsets.check_state(expected_ipsets)

        # Add another endpoint, and check the state.
        endpoint_id2 = str(uuid.uuid4())
        log.debug("Build second endpoint created : %s" % endpoint_id2)
        mac2 = stub_utils.get_mac()
        suffix2 = endpoint_id2[:11]
        tap2 = "tap" + suffix2
        addr2 = '1.2.3.5'
        endpoint_created_req = {
            'type': "ENDPOINTCREATED",
            'endpoint_id': endpoint_id2,
            'issued': futils.time_ms(),
            'mac': mac2,
            'state': Endpoint.STATE_ENABLED,
            'addrs': [{
                'gateway': "1.2.3.1",
                'addr': addr2
            }]
        }

        poll_result = context.add_poll_result(250)
        poll_result.add(TYPE_EP_REP, endpoint_created_req)
        tap_obj2 = stub_devices.TapInterface(tap2)
        stub_devices.add_tap(tap_obj2)
        agent.run()

        # Check that we got what we expected - i.e. a success response, a GETACLSTATE,
        # and the rules in the right state.
        endpoint_created_rsp = context.sent_data[TYPE_EP_REP].pop()
        self.assertEqual(endpoint_created_rsp['rc'], "SUCCESS")

        acl_req = context.sent_data[TYPE_ACL_REQ].pop()
        self.assertEqual(acl_req['endpoint_id'], endpoint_id2)
        self.assertFalse(context.sent_data_present())

        add_endpoint_rules(suffix2, tap2, addr2, None, mac2)
        stub_fiptables.check_state(expected_iptables)
        add_endpoint_ipsets(suffix2)
        stub_ipsets.check_state(expected_ipsets)

        # OK, finally wind down with an ENDPOINTDESTROYED message for that second endpoint.
        endpoint_destroyed_req = {
            'type': "ENDPOINTDESTROYED",
            'endpoint_id': endpoint_id2,
            'issued': futils.time_ms()
        }

        poll_result = context.add_poll_result(300)
        poll_result.add(TYPE_EP_REP, endpoint_destroyed_req)
        stub_devices.del_tap(tap2)
        agent.run()

        # Rebuild and recheck the state.
        set_expected_global_rules()
        add_endpoint_rules(suffix, tap, addr, None, mac)
        stub_fiptables.check_state(expected_iptables)