Repository: faucetsdn/faucet
Issue: Moving host from one port to another doesn't trigger EXPIRE/LEARN events

I plugged a device into port 6, and Faucet L2 learned it, then I unplugged it and plugged it into port 8, where I would have expected an L2 expire on port 6, and then an L2 learn on port 8, but instead don't see either. Is this the expected behavior?

```
Dec 26 19:43:44 faucet.valve INFO     DPID 260367057655831 (0xeccd6dfb9817) x510 Configuring VLAN demo vid:10 untagged: Port 1,Port 2,Port 3,Port 4,Port 5,Port 6,Port 7,Port 8,Port 9,Port 10,Port 11,Port 12,Port 13,Port 14,Port 15,Port 16,Port 17,Port 18,Port 19,Port 20,Port 21,Port 22,Port 24
Dec 26 19:43:46 faucet.valve INFO     DPID 260367057655831 (0xeccd6dfb9817) x510 L2 learned on Port 6 00:1a:eb:dd:bd:e0 (L2 type 0x0800, L2 dst ff:ff:ff:ff:ff:ff, L3 src 0.0.0.0, L3 dst 255.255.255.255) Port 6 VLAN 10 (26 hosts total)
Dec 26 19:43:56 faucet.valve INFO     DPID 260367057655831 (0xeccd6dfb9817) x510 L2 learned on Port 24 c8:f7:33:db:2d:8d (L2 type 0x0800, L2 dst 01:00:5e:7f:ff:fa, L3 src 192.168.0.43, L3 dst 239.255.255.250) Port 24 VLAN 10 (26 hosts total)
Dec 26 19:44:01 faucet.valve INFO     DPID 260367057655831 (0xeccd6dfb9817) x510 L2 learned on Port 24 d0:73:d5:57:0d:2e (L2 type 0x0806, L2 dst ff:ff:ff:ff:ff:ff, L3 src 192.168.0.4, L3 dst 192.168.0.4) Port 24 VLAN 10 (26 hosts total)
Dec 26 19:44:05 faucet.valve INFO     DPID 260367057655831 (0xeccd6dfb9817) x510 L2 learned on Port 24 d0:73:d5:3d:21:f7 (L2 type 0x0806, L2 dst ff:ff:ff:ff:ff:ff, L3 src 192.168.0.14, L3 dst 192.168.0.14) Port 24 VLAN 10 (26 hosts total)
Dec 26 19:44:15 faucet.valve INFO     DPID 260367057655831 (0xeccd6dfb9817) x510 L2 learned on Port 24 6c:40:08:8c:80:98 (L2 type 0x0800, L2 dst ff:ff:ff:ff:ff:ff, L3 src 192.168.0.55, L3 dst 192.168.0.255) Port 24 VLAN 10 (26 hosts total)
Dec 26 19:44:21 faucet.valve INFO     DPID 260367057655831 (0xeccd6dfb9817) x510 24 recently active hosts on VLAN 10, expired [d0:73:d5:41:7b:8a on Port 24, d0:73:d5:2d:b8:f5 on Port 24]
Dec 26 19:44:25 faucet.valve INFO     DPID 260367057655831 (0xeccd6dfb9817) x510 L2 learned on Port 24 d0:73:d5:41:7b:8a (L2 type 0x0800, L2 dst 01:00:5e:00:00:fb, L3 src 192.168.0.20, L3 dst 224.0.0.251) Port 24 VLAN 10 (25 hosts total)
Dec 26 19:44:27 faucet.valve INFO     DPID 260367057655831 (0xeccd6dfb9817) x510 L2 learned on Port 24 d0:73:d5:2d:b8:f5 (L2 type 0x0800, L2 dst 01:00:5e:00:00:fb, L3 src 192.168.0.22, L3 dst 224.0.0.251) Port 24 VLAN 10 (26 hosts total)
Dec 26 19:44:27 faucet.valve INFO     DPID 260367057655831 (0xeccd6dfb9817) x510 L2 learned on Port 24 d0:73:d5:2d:9d:e1 (L2 type 0x0800, L2 dst 01:00:5e:00:00:fb, L3 src 192.168.0.12, L3 dst 224.0.0.251) Port 24 VLAN 10 (26 hosts total)
Dec 26 19:44:27 faucet.valve INFO     DPID 260367057655831 (0xeccd6dfb9817) x510 L2 learned on Port 24 68:5b:35:c9:76:15 (L2 type 0x0800, L2 dst 00:1f:c6:9c:d2:dd, L3 src 192.168.0.46, L3 dst 192.168.0.48) Port 24 VLAN 10 (26 hosts total)
Dec 26 19:45:02 faucet.valve INFO     DPID 260367057655831 (0xeccd6dfb9817) x510 25 recently active hosts on VLAN 10, expired [d0:73:d5:40:ba:95 on Port 24]
Dec 26 19:45:13 faucet.valve INFO     DPID 260367057655831 (0xeccd6dfb9817) x510 L2 learned on Port 24 d0:73:d5:3d:86:9a (L2 type 0x0806, L2 dst ff:ff:ff:ff:ff:ff, L3 src 192.168.0.11, L3 dst 192.168.0.11) Port 24 VLAN 10 (25 hosts total)
Dec 26 19:45:24 faucet.valve INFO     DPID 260367057655831 (0xeccd6dfb9817) x510 L2 learned on Port 2 00:1f:c6:9b:c2:05 (L2 type 0x0806, L2 dst 8c:59:73:26:49:91, L3 src 192.168.0.67, L3 dst 192.168.0.1) Port 2 VLAN 10 (25 hosts total)
Dec 26 19:45:58 faucet.valve INFO     DPID 260367057655831 (0xeccd6dfb9817) x510 Port 6 up status False reason MODIFY state 1
Dec 26 19:45:58 faucet.valve INFO     DPID 260367057655831 (0xeccd6dfb9817) x510 Port 6 (6) down
Dec 26 19:46:01 faucet.valve INFO     DPID 260367057655831 (0xeccd6dfb9817) x510 Port 8 up status True reason MODIFY state 0
Dec 26 19:46:01 faucet.valve INFO     DPID 260367057655831 (0xeccd6dfb9817) x510 Port 8 (8) up
Dec 26 19:46:01 faucet.valve INFO     DPID 260367057655831 (0xeccd6dfb9817) x510 Configuring VLAN demo vid:10 untagged: Port 1,Port 2,Port 3,Port 4,Port 5,Port 6,Port 7,Port 8,Port 9,Port 10,Port 11,Port 12,Port 13,Port 14,Port 15,Port 16,Port 17,Port 18,Port 19,Port 20,Port 21,Port 22,Port 24
Dec 26 19:46:12 faucet.valve INFO     DPID 260367057655831 (0xeccd6dfb9817) x510 L2 learned on Port 24 8c:59:73:26:49:91 (L2 type 0x0806, L2 dst ff:ff:ff:ff:ff:ff, L3 src 192.168.0.1, L3 dst 192.168.0.29) Port 24 VLAN 10 (24 hosts total)
Dec 26 19:46:16 faucet.valve INFO     DPID 260367057655831 (0xeccd6dfb9817) x510 L2 learned on Port 24 d0:73:d5:40:ba:95 (L2 type 0x0800, L2 dst 01:00:5e:00:00:fb, L3 src 192.168.0.21, L3 dst 224.0.0.251) Port 24 VLAN 10 (25 hosts total)
Dec 26 19:46:38 faucet.valve INFO     DPID 260367057655831 (0xeccd6dfb9817) x510 L2 learned on Port 22 00:e0:4c:68:17:14 (L2 type 0x0800, L2 dst 8c:59:73:26:49:91, L3 src 192.168.0.98, L3 dst 205.171.3.25) Port 22 VLAN 10 (25 hosts total)
Dec 26 19:46:43 faucet.valve INFO     DPID 260367057655831 (0xeccd6dfb9817) x510 L2 learned on Port 24 7c:10:15:00:f4:d2 (L2 type 0x0806, L2 dst ff:ff:ff:ff:ff:ff, L3 src 192.168.0.36, L3 dst 192.168.0.13) Port 24 VLAN 10 (25 hosts total)
Dec 26 19:47:33 faucet.valve INFO     DPID 260367057655831 (0xeccd6dfb9817) x510 24 recently active hosts on VLAN 10, expired [d0:73:d5:2d:9a:12 on Port 24]
```

Changed files:
faucet/valve_flood.py, tests/unit/faucet/valve_test_lib.py, faucet/valve.py, faucet/valve_host.py, faucet/valves_manager.py, faucet/port.py, tests/integration/mininet_tests.py, faucet/faucet.py

diff --git a/faucet/faucet.py b/faucet/faucet.py
index b4d9d31a..62c4899e 100644
--- a/faucet/faucet.py
+++ b/faucet/faucet.py
@@ -326,7 +326,7 @@ class Faucet(RyuAppBase):
         valve, _, msg = self._get_valve(ryu_event, require_running=True)
         if valve is None:
             return
-        self.valves_manager.port_status_handler(valve, msg)
+        self.valves_manager.port_status_handler(valve, msg, time.time())
 
     @set_ev_cls(ofp_event.EventOFPFlowRemoved, MAIN_DISPATCHER) # pylint: disable=no-member
     @kill_on_exception(exc_logname)
diff --git a/faucet/port.py b/faucet/port.py
index 243a3772..b537ecbc 100644
--- a/faucet/port.py
+++ b/faucet/port.py
@@ -207,6 +207,7 @@ class Port(Conf):
         self.dyn_lldp_beacon_recv_time = None
         self.dyn_learn_ban_count = 0
         self.dyn_phys_up = False
+        self.dyn_update_time = None
         self.dyn_stack_current_state = STACK_STATE_NONE
         self.dyn_stack_probe_info = {}
 
diff --git a/faucet/valve.py b/faucet/valve.py
index b73cecd8..a20b4449 100644
--- a/faucet/valve.py
+++ b/faucet/valve.py
@@ -392,7 +392,7 @@ class Valve:
         all_up_port_nos = {port_no for port_no, status in port_status.items() if status}
         return (port_status, all_up_port_nos)
 
-    def _add_ports_and_vlans(self, discovered_up_port_nos):
+    def _add_ports_and_vlans(self, now, discovered_up_port_nos):
         """Add all configured and discovered ports and VLANs."""
         always_up_port_nos = {
             port.number for port in self.dp.ports.values() if not port.opstatus_reconf}
@@ -403,7 +403,7 @@ class Valve:
             discovered_up_port_nos, all_configured_port_nos)
 
         for port_no, status in port_status.items():
-            self._set_port_status(port_no, status)
+            self._set_port_status(port_no, status, now)
         self.notify({'PORTS_STATUS': port_status})
 
         ofmsgs = []
@@ -423,7 +423,7 @@ class Valve:
             dp_desc=valve_util.utf8_decode(body.dp_desc))
         self._set_var('of_dp_desc_stats', self.dp.dp_id, labels=labels)
 
-    def _set_port_status(self, port_no, port_status):
+    def _set_port_status(self, port_no, port_status, now):
         """Set port operational status."""
         if port_status:
             self.dp.dyn_up_port_nos.add(port_no)
@@ -434,8 +434,9 @@ class Valve:
             return
         port_labels = self.dp.port_labels(port.number)
         self._set_var('port_status', port_status, labels=port_labels)
+        port.dyn_update_time = now
 
-    def port_status_handler(self, port_no, reason, state, _other_valves):
+    def port_status_handler(self, port_no, reason, state, _other_valves, now):
         """Return OpenFlow messages responding to port operational status change."""
 
         port_status_codes = {
@@ -455,7 +456,7 @@ class Valve:
                 'reason': _decode_port_status(reason),
                 'state': state,
                 'status': port_status}})
-        self._set_port_status(port_no, port_status)
+        self._set_port_status(port_no, port_status, now)
 
         if not self.dp.port_no_valid(port_no):
             return {}
@@ -468,18 +469,33 @@ class Valve:
             return {}
 
         ofmsgs_by_valve = {self: []}
-        self.logger.info('%s up status %s reason %s state %s' % (
-            port, port_status, _decode_port_status(reason), state))
         new_port_status = (
             reason == valve_of.ofp.OFPPR_ADD or
             (reason == valve_of.ofp.OFPPR_MODIFY and port_status))
-        if new_port_status:
-            if port.dyn_phys_up:
-                self.logger.info('%s already up, assuming flap as missing down event' % port)
-                ofmsgs_by_valve[self].extend(self.port_delete(port_no))
-            ofmsgs_by_valve[self].extend(self.port_add(port_no))
+        blocked_down_state = (
+            (state & valve_of.ofp.OFPPS_BLOCKED) or (state & valve_of.ofp.OFPPS_LINK_DOWN))
+        live_state = state & valve_of.ofp.OFPPS_LIVE
+        decoded_reason = _decode_port_status(reason)
+        state_description = '%s up status %s reason %s state %s' % (
+            port, port_status, decoded_reason, state)
+        ofmsgs = []
+        if new_port_status != port.dyn_phys_up:
+            self.logger.info('status change: %s' % state_description)
+            if new_port_status:
+                ofmsgs = self.port_add(port_no)
+            else:
+                ofmsgs = self.port_delete(port_no, keep_cache=True)
         else:
-            ofmsgs_by_valve[self].extend(self.port_delete(port_no))
+            self.logger.info('status did not change: %s' % state_description)
+            if new_port_status:
+                if blocked_down_state:
+                    self.logger.info(
+                        '%s state down or blocked despite status up, setting to status down' % port)
+                    ofmsgs = self.port_delete(port_no, keep_cache=True)
+                if not live_state:
+                    self.logger.info(
+                        '%s state OFPPS_LIVE reset, ignoring in expectation of port down' % port)
+        ofmsgs_by_valve[self].extend(ofmsgs)
         return ofmsgs_by_valve
 
     def advertise(self, now, _other_values):
@@ -607,7 +623,12 @@ class Valve:
                     'state': port.dyn_stack_current_state
                     }})
                 stack_changes += 1
-                port_stack_up = port.is_stack_up()
+                port_stack_up = False
+                if port.is_stack_up():
+                    port_stack_up = True
+                else:
+                    if port.is_stack_init() and port.stack['port'].is_stack_up():
+                        port_stack_up = True
                 for valve in stacked_valves:
                     valve.flood_manager.update_stack_topo(port_stack_up, self.dp, port)
         if stack_changes:
@@ -623,7 +644,8 @@ class Valve:
                     ofmsgs_by_valve[valve].extend(valve.host_manager.del_port(port))
                 path_port = valve.dp.shortest_path_port(valve.dp.stack_root_name)
                 path_port_number = path_port.number if path_port else 0.0
-                self._set_var('dp_root_hop_port', path_port_number, labels=valve.dp.base_prom_labels())
+                self._set_var(
+                    'dp_root_hop_port', path_port_number, labels=valve.dp.base_prom_labels())
                 notify_dps.setdefault(valve.dp.name, {})['root_hop_port'] = path_port_number
 
             # Find the first valve with a valid stack and trigger notification.
@@ -689,7 +711,7 @@ class Valve:
         ofmsgs.extend(self._add_default_flows())
         for manager in self._get_managers():
             ofmsgs.extend(manager.initialise_tables())
-        ofmsgs.extend(self._add_ports_and_vlans(discovered_up_ports))
+        ofmsgs.extend(self._add_ports_and_vlans(now, discovered_up_ports))
         ofmsgs.append(
             valve_of.faucet_async(
                 packet_in=True,
@@ -757,13 +779,14 @@ class Valve:
             ofmsgs.extend(manager.del_port(port))
         return ofmsgs
 
-    def _port_delete_flows_state(self, port):
+    def _port_delete_flows_state(self, port, keep_cache=False):
         """Delete flows/state for a port."""
         ofmsgs = []
         for route_manager in self._route_manager_by_ipv.values():
             ofmsgs.extend(route_manager.expire_port_nexthops(port))
         ofmsgs.extend(self._delete_all_port_match_flows(port))
-        ofmsgs.extend(self._port_delete_manager_state(port))
+        if not keep_cache:
+            ofmsgs.extend(self._port_delete_manager_state(port))
         return ofmsgs
 
     def _coprocessor_flows(self, port):
@@ -889,7 +912,7 @@ class Valve:
         """
         return self.ports_add([port_num])
 
-    def ports_delete(self, port_nums, log_msg='down'):
+    def ports_delete(self, port_nums, log_msg='down', keep_cache=False):
         """Handle the deletion of ports.
 
         Args:
@@ -921,24 +944,22 @@ class Valve:
             if port.lacp:
                 ofmsgs.extend(self.lacp_down(port))
             else:
-                ofmsgs.extend(self._port_delete_flows_state(port))
+                ofmsgs.extend(self._port_delete_flows_state(port, keep_cache=keep_cache))
 
         for vlan in vlans_with_deleted_ports:
             ofmsgs.extend(self.flood_manager.update_vlan(vlan))
 
         return ofmsgs
 
-    def port_delete(self, port_num):
+    def port_delete(self, port_num, keep_cache=False):
         """Return flow messages that delete port from pipeline."""
-        return self.ports_delete([port_num])
+        return self.ports_delete([port_num], keep_cache=keep_cache)
 
     def _reset_lacp_status(self, port):
         lacp_state = port.lacp_state()
         self._set_var('port_lacp_state', lacp_state, labels=self.dp.port_labels(port.number))
         self.notify(
-                {'LAG_CHANGE': {
-                    'port_no': port.number,
-                    'state': lacp_state}})
+            {'LAG_CHANGE': {'port_no': port.number, 'state': lacp_state}})
 
     def lacp_down(self, port, cold_start=False, lacp_pkt=None):
         """Return OpenFlow messages when LACP is down on a port."""
diff --git a/faucet/valve_flood.py b/faucet/valve_flood.py
index a420aeff..3d1f6b0f 100644
--- a/faucet/valve_flood.py
+++ b/faucet/valve_flood.py
@@ -298,7 +298,7 @@ class ValveFloodManager(ValveManagerBase):
         return ofmsgs
 
     @staticmethod
-    def update_stack_topo(event, dp, port=None): # pylint: disable=unused-argument,invalid-name
+    def update_stack_topo(event, dp, port):  # pylint: disable=unused-argument,invalid-name
         """Update the stack topology. It has nothing to do for non-stacking DPs."""
         return
 
@@ -538,39 +538,16 @@ class ValveFloodStackManagerBase(ValveFloodManager):
 
         return ofmsgs
 
-    def update_stack_topo(self, event, dp, port=None):
+    def update_stack_topo(self, event, dp, port):
         """Update the stack topo according to the event."""
 
         if self.graph is None:
             return
 
-        def _stack_topo_up_dp(_dp): # pylint: disable=invalid-name
-            for port in _dp.stack_ports:
-                if port.is_stack_up():
-                    _stack_topo_up_port(_dp, port)
-                else:
-                    _stack_topo_down_port(_dp, port)
-
-        def _stack_topo_down_dp(_dp): # pylint: disable=invalid-name
-            for port in _dp.stack_ports:
-                _stack_topo_down_port(_dp, port)
-
-        def _stack_topo_up_port(_dp, _port): # pylint: disable=invalid-name
-            _dp.add_stack_link(self.graph, _dp, _port)
-
-        def _stack_topo_down_port(_dp, _port): # pylint: disable=invalid-name
-            _dp.remove_stack_link(self.graph, _dp, _port)
-
-        if port:
-            if event:
-                _stack_topo_up_port(dp, port)
-            else:
-                _stack_topo_down_port(dp, port)
+        if event:
+            dp.add_stack_link(self.graph, dp, port)
         else:
-            if event:
-                _stack_topo_up_dp(dp)
-            else:
-                _stack_topo_down_dp(dp)
+            dp.remove_stack_link(self.graph, dp, port)
 
         self._reset_peer_distances()
 
diff --git a/faucet/valve_host.py b/faucet/valve_host.py
index 783f319d..29d4ab2e 100644
--- a/faucet/valve_host.py
+++ b/faucet/valve_host.py
@@ -319,8 +319,10 @@ class ValveHostManager(ValveManagerBase):
                 # locally learned hosts on a stack.
                 if same_lag or local_stack_learn:
                     guard_time = 2
-                # recent cache update, don't do anything.
-                if cache_age < guard_time:
+                # port didn't change status, and recent cache update, don't do anything.
+                if (cache_age < guard_time and
+                        port.dyn_update_time is not None and
+                        port.dyn_update_time <= entry.cache_time):
                     return (ofmsgs, cache_port, False)
                 # skip delete if host didn't change ports or on same LAG.
                 if cache_port == port or same_lag:
diff --git a/faucet/valves_manager.py b/faucet/valves_manager.py
index 9dd19f5c..3373052d 100644
--- a/faucet/valves_manager.py
+++ b/faucet/valves_manager.py
@@ -322,10 +322,10 @@ class ValvesManager:
         return [other_valve for other_valve in self.valves.values()
                 if valve != other_valve and other_valve.dp.dyn_running]
 
-    def port_status_handler(self, valve, msg):
+    def port_status_handler(self, valve, msg, now):
         """Handle a port status change message."""
         ofmsgs_by_valve = valve.port_status_handler(
-            msg.desc.port_no, msg.reason, msg.desc.state, self._other_running_valves(valve))
+            msg.desc.port_no, msg.reason, msg.desc.state, self._other_running_valves(valve), now)
         self._send_ofmsgs_by_valve(ofmsgs_by_valve)
 
     def valve_packet_in(self, now, valve, msg):
diff --git a/tests/integration/mininet_tests.py b/tests/integration/mininet_tests.py
index 2a35f406..603bf7b0 100644
--- a/tests/integration/mininet_tests.py
+++ b/tests/integration/mininet_tests.py
@@ -3596,15 +3596,19 @@ vlans:
 class FaucetUntaggedHostMoveTest(FaucetUntaggedTest):
 
     def test_untagged(self):
+        self._enable_event_log()
         first_host, second_host = self.hosts_name_ordered()[0:2]
-        self.retry_net_ping(hosts=(first_host, second_host))
-        self.swap_host_macs(first_host, second_host)
-        self.ping((first_host, second_host))
-        for host, in_port in (
-                (first_host, self.port_map['port_1']),
-                (second_host, self.port_map['port_2'])):
-            self.require_host_learned(host, in_port=in_port)
-        self.retry_net_ping(hosts=(first_host, second_host))
+        for _ in range(2):
+            self.retry_net_ping(hosts=(first_host, second_host))
+            self.ping((first_host, second_host))
+            for host, in_port in (
+                    (first_host, self.port_map['port_1']),
+                    (second_host, self.port_map['port_2'])):
+                self.require_host_learned(host, in_port=in_port)
+            self.swap_host_macs(first_host, second_host)
+        for port in (self.port_map['port_1'], self.port_map['port_2']):
+            self.wait_until_matching_lines_from_file(
+                r'.+L2_LEARN.+"previous_port_no": %u.+' % port, self.event_log)
 
 
 class FaucetUntaggedHostPermanentLearnTest(FaucetUntaggedTest):
diff --git a/tests/unit/faucet/valve_test_lib.py b/tests/unit/faucet/valve_test_lib.py
index 6d7d3c40..3c6915a2 100644
--- a/tests/unit/faucet/valve_test_lib.py
+++ b/tests/unit/faucet/valve_test_lib.py
@@ -28,6 +28,7 @@ import pstats
 import shutil
 import socket
 import tempfile
+import time
 import unittest
 
 from ryu.lib import mac
@@ -650,13 +651,13 @@ class ValveTestBases:
         def set_port_down(self, port_no):
             """Set port status of port to down."""
             self.apply_ofmsgs(self.valve.port_status_handler(
-                port_no, ofp.OFPPR_DELETE, ofp.OFPPS_LINK_DOWN, []).get(self.valve, []))
+                port_no, ofp.OFPPR_DELETE, ofp.OFPPS_LINK_DOWN, [], time.time()).get(self.valve, []))
             self.port_expected_status(port_no, 0)
 
         def set_port_up(self, port_no):
             """Set port status of port to up."""
             self.apply_ofmsgs(self.valve.port_status_handler(
-                port_no, ofp.OFPPR_ADD, 0, []).get(self.valve, []))
+                port_no, ofp.OFPPR_ADD, 0, [], time.time()).get(self.valve, []))
             self.port_expected_status(port_no, 1)
 
         def flap_port(self, port_no):
@@ -1644,7 +1645,7 @@ meters:
             """Set port status modify."""
             for port_status in (0, 1):
                 self.apply_ofmsgs(self.valve.port_status_handler(
-                    1, ofp.OFPPR_MODIFY, port_status, [])[self.valve])
+                    1, ofp.OFPPR_MODIFY, port_status, [], time.time())[self.valve])
 
         def test_unknown_port_status(self):
             """Test unknown port status message."""
@@ -1652,7 +1653,7 @@ meters:
             unknown_messages = list(set(range(0, len(known_messages) + 1)) - known_messages)
             self.assertTrue(unknown_messages)
             self.assertFalse(self.valve.port_status_handler(
-                1, unknown_messages[0], 1, []).get(self.valve, []))
+                1, unknown_messages[0], 1, [], time.time()).get(self.valve, []))
 
         def test_move_port(self):
             """Test host moves a port."""