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()
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
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)
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)
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)
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
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)
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, {}))
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)
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)
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()
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
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)
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)
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)
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])
def test_time_ms(self): # Bit feeble, but validate that we can call it and get back something. time_ms = futils.time_ms()
def test_time_ms(self): # Bit feeble, but validate that we can call it and get back something. time_ms = futils.time_ms()
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)