Example #1
0
    def test_validate_order(self):
        policy = {
            "selector": "a == 'b'",
            "order": 10,
            "inbound_rules": [],
            "outbound_rules": [],
        }
        common.validate_policy(TieredPolicyId("a", "b"), policy)

        policy = {
            "selector": "a == 'b'",
            "order": "10",
            "inbound_rules": [],
            "outbound_rules": [],
        }
        with self.assertRaises(ValidationFailed):
            common.validate_policy(TieredPolicyId("a", "b"), policy)

        policy = {
            "selector": "a == 'b'",
            "inbound_rules": [],
            "outbound_rules": [],
        }
        with self.assertRaises(ValidationFailed):
            common.validate_policy(TieredPolicyId("a", "b"), policy)

        policy = {
            "order": 10,
            "inbound_rules": [],
            "outbound_rules": [],
        }
        with self.assertRaises(ValidationFailed):
            common.validate_policy(TieredPolicyId("a", "b"), policy)
Example #2
0
 def test_replace_selector_with_object(self):
     """
     Checks that the validate_profile() method replaces selectors
     (with their object representations).
     """
     policy = {
         "selector":
         "a == 'b'",
         "inbound_rules": [{
             "src_selector": "b == 'c'",
             "dst_selector": "e == 'f'"
         }],
         "outbound_rules": [{
             "src_selector": "h == 'c'",
             "dst_selector": "i == 'f'"
         }],
         "order":
         10
     }
     common.validate_policy(TieredPolicyId("a", "b"), policy)
     self.assertEqual(policy["selector"], parse_selector("a == 'b'"))
     self.assertEqual(policy["inbound_rules"][0]["src_selector"],
                      parse_selector("b == 'c'"))
     self.assertEqual(policy["inbound_rules"][0]["dst_selector"],
                      parse_selector("e == 'f'"))
     self.assertEqual(policy["outbound_rules"][0]["src_selector"],
                      parse_selector("h == 'c'"))
     self.assertEqual(policy["outbound_rules"][0]["dst_selector"],
                      parse_selector("i == 'f'"))
Example #3
0
    def test_label_inheritance(self):
        # Make sure we have an endpoint so that we can check that it gets
        # put in the dirty set.  These have no labels at all so we test
        # that no labels gets translated to an empty dict.
        self.mgr.on_endpoint_update(ENDPOINT_ID, {"name": "tap12345",
                                                  "profile_ids": ["prof1"]},
                                    async=True)
        self.mgr.on_endpoint_update(ENDPOINT_ID_2, {"name": "tap23456",
                                                    "profile_ids": ["prof2"]},
                                    async=True)
        # And we need a selector to pick out one of the endpoints by the labels
        # attached to its parent.
        self.mgr.on_policy_selector_update(TieredPolicyId("a", "b"),
                                           parse_selector('a == "b"'),
                                           10,
                                           async=True)
        self.step_actor(self.mgr)

        with mock.patch.object(self.mgr, "_update_dirty_policy") as m_update:
            self.mgr.on_prof_labels_set("prof1", {"a": "b"}, async=True)
            self.step_actor(self.mgr)
            # Only the first endpoint should end up matching the selector.
            self.assertEqual(self.mgr.endpoints_with_dirty_policy,
                             set([ENDPOINT_ID]))
            # And an update should be triggered.
            self.assertEqual(m_update.mock_calls, [mock.call()])
Example #4
0
 def on_tiered_policy_delete(self, response, tier, policy_id):
     """Handler for tiered rules deletes, passes update to the splitter."""
     _log.debug("Rules for %s/%s deleted", tier, policy_id)
     _stats.increment("tiered rules deleted")
     policy_id = TieredPolicyId(tier, policy_id)
     self.splitter.on_rules_update(policy_id, None)
     self.splitter.on_policy_selector_update(policy_id, None, None)
Example #5
0
 def test_tiered_policy_chain_names(self):
     chain_names = self.iptables_generator.profile_chain_names(
         TieredPolicyId("tier", "pol")
     )
     self.assertEqual(chain_names,
                      set(['felix-p-tier/pol-o',
                           'felix-p-tier/pol-i']))
Example #6
0
 def on_tiered_policy_update(self, msg):
     _log.debug("Rules for %s/%s set", msg.id.tier, msg.id.name)
     _stats.increment("Tiered rules created/updated")
     policy_id = TieredPolicyId(msg.id.tier, msg.id.name)
     rules = {
         "inbound_rules": convert_pb_rules(msg.policy.inbound_rules),
         "outbound_rules": convert_pb_rules(msg.policy.outbound_rules),
     }
     self.splitter.on_rules_update(policy_id, rules)
Example #7
0
    def test_validate_order(self):
        policy = {
            "selector": "a == 'b'",
            "order": 10,
            "inbound_rules": [],
            "outbound_rules": [],
        }
        common.validate_policy(TieredPolicyId("a", "b"), policy)

        policy = {
            "selector": "a == 'b'",
            "order": "10",
            "inbound_rules": [],
            "outbound_rules": [],
        }
        with self.assertRaises(ValidationFailed):
            common.validate_policy(TieredPolicyId("a", "b"), policy)

        policy = {
            "selector": "a == 'b'",
            "inbound_rules": [],
            "outbound_rules": [],
        }
        common.validate_policy(TieredPolicyId("a", "b"), policy)
        self.assertEqual(policy["order"], common.INFINITY)
        self.assertGreater(policy["order"], 9999999999999999999999999999999999)

        policy = {
            "selector": "a == 'b'",
            "inbound_rules": [],
            "outbound_rules": [],
            "order": "default",
        }
        common.validate_policy(TieredPolicyId("a", "b"), policy)
        self.assertEqual(policy["order"], common.INFINITY)
        self.assertGreater(policy["order"], 9999999999999999999999999999999999)

        policy = {
            "order": 10,
            "inbound_rules": [],
            "outbound_rules": [],
        }
        with self.assertRaises(ValidationFailed):
            common.validate_policy(TieredPolicyId("a", "b"), policy)
Example #8
0
 def on_tiered_policy_set(self, response, tier, policy_id):
     _log.debug("Rules for %s/%s set", tier, policy_id)
     _stats.increment("Tiered rules created/updated")
     policy_id = TieredPolicyId(tier, policy_id)
     rules = parse_policy(policy_id, response.value)
     if rules is not None:
         selector = rules.pop("selector")
         order = rules.pop("order")
         self.splitter.on_rules_update(policy_id, rules)
         self.splitter.on_policy_selector_update(policy_id, selector, order)
     else:
         self.splitter.on_rules_update(policy_id, None)
         self.splitter.on_policy_selector_update(policy_id, None, None)
Example #9
0
    def test_validate_policy(self):
        policy_id = TieredPolicyId("a", "b")
        with self.assertRaisesRegexp(ValidationFailed,
                                     "Expected policy 'a/b' to "
                                     "be a dict"):
            common.validate_policy(policy_id, [])

        rules = {'selector': "+abcd", # Bad selector
                 'inbound_rules': [],
                 'outbound_rules': []}
        with self.assertRaisesRegexp(ValidationFailed,
                                     "Failed to parse selector"):
            common.validate_policy(policy_id, rules)
Example #10
0
    def test_tiered_policy_mainline(self, m_devices):
        self.config.plugins["iptables_generator"] = self.m_ipt_gen
        ep = self.get_local_endpoint(ENDPOINT_ID, futils.IPV4)
        mac = stub_utils.get_mac()
        ep.on_endpoint_update(
            {
                'state': "active",
                'endpoint': "endpoint_id",
                'mac': mac,
                'name': "tap1234",
                'ipv4_nets': ["10.0.0.1"],
                'profile_ids': ["prof1"]
            },
            async=True)
        self.step_actor(ep)

        self.assertEqual(
            self.m_ipt_gen.endpoint_updates.mock_calls,
            [
                mock.call(4, 'd', '1234', mac, ['prof1'], {}),
            ]
        )
        self.m_ipt_gen.endpoint_updates.reset_mock()

        tiers = OrderedDict()
        t1_1 = TieredPolicyId("t1", "t1_1")
        t1_2 = TieredPolicyId("t1", "t1_2")
        tiers["t1"] = [t1_1, t1_2]
        t2_1 = TieredPolicyId("t2", "t2_1")
        tiers["t2"] = [t2_1]
        ep.on_tiered_policy_update(tiers, async=True)
        self.step_actor(ep)

        self.assertEqual(
            self.m_ipt_gen.endpoint_updates.mock_calls,
            [
                mock.call(4, 'd', '1234', mac, ['prof1'],
                          OrderedDict([('t1', [TieredPolicyId('t1','t1_1'),
                                               TieredPolicyId('t1','t1_2')]),
                                       ('t2', [TieredPolicyId('t2','t2_1')])]))
            ])
Example #11
0
    def test_validate_rules(self):
        profile_id = "valid_name-ok."
        rules = {'inbound_rules': [], 'outbound_rules': []}
        common.validate_profile(profile_id, rules.copy())

        with self.assertRaisesRegexp(
                ValidationFailed, "Expected profile 'valid_name-ok.' to "
                "be a dict"):
            common.validate_profile(profile_id, [])

        with self.assertRaisesRegexp(ValidationFailed, "Invalid profile ID"):
            common.validate_profile("a&b", rules.copy())
        with self.assertRaisesRegexp(ValidationFailed, "Invalid profile ID"):
            common.validate_policy(TieredPolicyId("+123", "abc"), rules.copy())
        with self.assertRaisesRegexp(ValidationFailed, "Invalid profile ID"):
            common.validate_policy(TieredPolicyId("abc", "+"), rules.copy())

        # No rules.
        with self.assertRaisesRegexp(ValidationFailed, "No outbound_rules"):
            common.validate_profile(profile_id, {'inbound_rules': []})
        with self.assertRaisesRegexp(ValidationFailed, "No inbound_rules"):
            common.validate_profile(profile_id, {'outbound_rules': []})

        rules = {'inbound_rules': 3, 'outbound_rules': []}
        with self.assertRaisesRegexp(
                ValidationFailed,
                "Expected rules\[inbound_rules\] to be a list"):
            common.validate_profile(profile_id, rules.copy())

        rule = "not a dict"
        rules = {'inbound_rules': [rule], 'outbound_rules': []}
        with self.assertRaisesRegexp(ValidationFailed,
                                     "Rule should be a dict"):
            common.validate_profile(profile_id, rules.copy())

        rule = {'bad_key': ""}
        rules = {'inbound_rules': [rule], 'outbound_rules': []}
        with self.assertRaisesRegexp(ValidationFailed,
                                     "Rule contains unknown keys"):
            common.validate_profile(profile_id, rules)

        rule = {'protocol': "bloop"}
        rules = {'inbound_rules': [rule], 'outbound_rules': []}
        with self.assertRaisesRegexp(
                ValidationFailed, "Invalid protocol bloop in rule "
                "{'protocol': 'bloop'}"):
            common.validate_profile(profile_id, rules)

        rule = {'ip_version': 5}
        rules = {'inbound_rules': [rule], 'outbound_rules': []}
        with self.assertRaisesRegexp(ValidationFailed,
                                     "Invalid ip_version in rule"):
            common.validate_profile(profile_id, rules)

        rule = {'ip_version': 4, 'protocol': "icmpv6"}
        rules = {'inbound_rules': [rule], 'outbound_rules': []}
        with self.assertRaisesRegexp(ValidationFailed,
                                     "Using icmpv6 with IPv4"):
            common.validate_profile(profile_id, rules)

        rule = {'ip_version': 6, 'protocol': "icmp"}
        rules = {'inbound_rules': [rule], 'outbound_rules': []}
        with self.assertRaisesRegexp(ValidationFailed, "Using icmp with IPv6"):
            common.validate_profile(profile_id, rules)

        rule = {'src_tag': "abc", 'protocol': "icmp"}
        rules = {'inbound_rules': [rule], 'outbound_rules': []}
        common.validate_profile(profile_id, rules)

        rule = {'src_tag': "abc", 'protocol': "123"}
        rules = {'inbound_rules': [rule], 'outbound_rules': []}
        common.validate_profile(profile_id, rules)

        rule = {'protocol': "256"}
        rules = {'inbound_rules': [rule], 'outbound_rules': []}
        with self.assertRaisesRegexp(ValidationFailed,
                                     "Invalid protocol 256 in rule"):
            common.validate_profile(profile_id, rules)

        rule = {'protocol': "0"}
        rules = {'inbound_rules': [rule], 'outbound_rules': []}
        with self.assertRaisesRegexp(ValidationFailed,
                                     "Invalid protocol 0 in rule"):
            common.validate_profile(profile_id, rules)

        rule = {'src_tag': "a!b", 'protocol': "icmp"}
        rules = {'inbound_rules': [rule], 'outbound_rules': []}
        with self.assertRaisesRegexp(ValidationFailed, "Invalid src_tag"):
            common.validate_profile(profile_id, rules)

        rule = {'dst_tag': "x,y", 'protocol': "icmp"}
        rules = {'inbound_rules': [rule], 'outbound_rules': []}
        with self.assertRaisesRegexp(ValidationFailed, "Invalid dst_tag"):
            common.validate_profile(profile_id, rules)

        rule = {'src_selector': "a!b"}
        rules = {'inbound_rules': [rule], 'outbound_rules': []}
        with self.assertRaisesRegexp(ValidationFailed, "Invalid src_selector"):
            common.validate_profile(profile_id, rules)

        rule = {'dst_selector': "+b"}
        rules = {'inbound_rules': [rule], 'outbound_rules': []}
        with self.assertRaisesRegexp(ValidationFailed, "Invalid dst_selector"):
            common.validate_profile(profile_id, rules)

        rule = {'src_net': "nonsense"}
        rules = {'inbound_rules': [rule], 'outbound_rules': []}
        with self.assertRaisesRegexp(ValidationFailed, "Invalid CIDR"):
            common.validate_profile(profile_id, rules)

        rule = {'dst_net': "1.2.3.4/16", 'ip_version': 6}
        rules = {'inbound_rules': [rule], 'outbound_rules': []}
        with self.assertRaisesRegexp(ValidationFailed, "Invalid CIDR"):
            common.validate_profile(profile_id, rules)

        rule = {'src_ports': "nonsense"}
        rules = {'inbound_rules': [rule], 'outbound_rules': []}
        with self.assertRaisesRegexp(ValidationFailed,
                                     "Expected ports to be a list"):
            common.validate_profile(profile_id, rules)

        rule = {'dst_ports': [32, "nonsense"]}
        rules = {'inbound_rules': [rule], 'outbound_rules': []}
        with self.assertRaisesRegexp(ValidationFailed, "Invalid port"):
            common.validate_profile(profile_id, rules)

        rule = {'action': "nonsense"}
        rules = {'inbound_rules': [rule], 'outbound_rules': []}
        with self.assertRaisesRegexp(ValidationFailed, "Invalid action"):
            common.validate_profile(profile_id, rules)

        rule = {'icmp_type': "nonsense"}
        rules = {'inbound_rules': [rule], 'outbound_rules': []}
        with self.assertRaisesRegexp(ValidationFailed,
                                     "ICMP type is not an integer"):
            common.validate_profile(profile_id, rules)

        rule = {'icmp_type': -1}
        rules = {'inbound_rules': [rule], 'outbound_rules': []}
        with self.assertRaisesRegexp(ValidationFailed,
                                     "ICMP type is out of range"):
            common.validate_profile(profile_id, rules)

        rule = {'icmp_type': 256}
        rules = {'inbound_rules': [rule], 'outbound_rules': []}
        with self.assertRaisesRegexp(ValidationFailed,
                                     "ICMP type is out of range"):
            common.validate_profile(profile_id, rules)

        rule = {'icmp_type': 22, 'icmp_code': "2"}
        rules = {'inbound_rules': [rule], 'outbound_rules': []}
        with self.assertRaisesRegexp(ValidationFailed,
                                     "ICMP code is not an integer"):
            common.validate_profile(profile_id, rules)

        rule = {'icmp_type': 0, 'icmp_code': -1}
        rules = {'inbound_rules': [rule], 'outbound_rules': []}
        with self.assertRaisesRegexp(ValidationFailed,
                                     "ICMP code is out of range"):
            common.validate_profile(profile_id, rules)

        rule = {'icmp_type': 0, 'icmp_code': 256}
        rules = {'inbound_rules': [rule], 'outbound_rules': []}
        with self.assertRaisesRegexp(ValidationFailed,
                                     "ICMP code is out of range"):
            common.validate_profile(profile_id, rules)

        rule = {'icmp_code': 2}
        rules = {'inbound_rules': [rule], 'outbound_rules': []}
        with self.assertRaisesRegexp(ValidationFailed,
                                     "ICMP code specified without ICMP type"):
            common.validate_profile(profile_id, rules)
Example #12
0
    "ipv6_nets": ["dead::beef/128"]
}
ENDPOINT_STR = json.dumps(VALID_ENDPOINT)

RULES = {
    "inbound_rules": [],
    "outbound_rules": [],
}
RULES_STR = json.dumps(RULES)

TAGS = ["a", "b"]
TAGS_STR = json.dumps(TAGS)

ETCD_ADDRESS = 'localhost:4001'

POLICY_ID = TieredPolicyId("tiername", "polname")
POLICY = {
    "selector": "a == 'b'",
    "inbound_rules": [],
    "outbound_rules": [],
    "order": 10,
}
SELECTOR = parse_selector(POLICY["selector"])
POLICY_PARSED = {
    "inbound_rules": [],
    "outbound_rules": [],
}
POLICY_STR = json.dumps(POLICY)


class TestEtcdAPI(BaseTestCase):
Example #13
0
    def _apply_endpoint_update(self):
        pending_endpoint = self._pending_endpoint
        if pending_endpoint == self.endpoint:
            _log.debug("Endpoint hasn't changed, nothing to do")
            return

        # Calculate the set of IPs that we had before this update.  Needed on
        # the update and delete code paths below.
        if self.endpoint:
            old_ips = set(
                futils.net_to_ip(n)
                for n in self.endpoint.get(self.nets_key, []))
            old_nat_mappings = self.endpoint.get(self.nat_key, [])
        else:
            old_ips = set()
            old_nat_mappings = []
        all_old_ips = old_ips | set([n["ext_ip"] for n in old_nat_mappings])

        if pending_endpoint:
            # Update/create.
            if pending_endpoint.get('mac') != self._mac:
                # Either we have not seen this MAC before, or it has changed.
                _log.debug("Endpoint MAC changed to %s",
                           pending_endpoint.get("mac"))
                self._mac = pending_endpoint.get('mac')
                self._mac_changed = True
                # MAC change requires refresh of iptables rules and ARP table.
                self._iptables_in_sync = False
                self._device_in_sync = False

            new_iface_name = pending_endpoint["name"]
            # Interface renames are handled in the EndpointManager by
            # simulating a delete then an add.  We shouldn't see one here.
            assert (self.endpoint is None
                    or self._iface_name == new_iface_name), (
                        "Unexpected change of interface name.")
            if self.endpoint is None:
                # This is the first time we have seen the endpoint, so extract
                # the interface name and endpoint ID.
                self._iface_name = new_iface_name
                self._suffix = interface_to_chain_suffix(
                    self.config, self._iface_name)
                _log.debug("Learned interface name/suffix: %s/%s",
                           self._iface_name, self._suffix)
                # First time through, need to program everything.
                self._iptables_in_sync = False
                self._device_in_sync = False
                if self._device_is_up is None:
                    _log.debug("Learned interface name, checking if device "
                               "is up.")
                    self._device_is_up = (
                        devices.interface_exists(self._iface_name)
                        and devices.interface_up(self._iface_name))

            # Check if the profile ID or IP addresses have changed, requiring
            # a refresh of the dataplane.
            profile_ids = set(pending_endpoint.get("profile_ids", []))
            if profile_ids != self._explicit_profile_ids:
                # Profile ID update requires iptables update but not device
                # update.
                _log.debug(
                    "Profile IDs changed from %s to %s, need to update "
                    "iptables", self._rules_ref_helper.required_refs,
                    profile_ids)
                self._explicit_profile_ids = profile_ids
                self._iptables_in_sync = False
                self._profile_ids_dirty = True

            # Check for changes to values that require a device update.
            if self.endpoint:
                if self.endpoint.get("state") != pending_endpoint.get("state"):
                    _log.debug("Desired interface state updated.")
                    self._device_in_sync = False
                    self._iptables_in_sync = False
                new_ips = set(
                    futils.net_to_ip(n)
                    for n in pending_endpoint.get(self.nets_key, []))
                if old_ips != new_ips:
                    # IP addresses have changed, need to update the routing
                    # table.
                    _log.debug("IP addresses changed, need to update routing")
                    self._device_in_sync = False
                new_nat_mappings = pending_endpoint.get(self.nat_key, [])
                if old_nat_mappings != new_nat_mappings:
                    _log.debug("NAT mappings have changed, refreshing.")
                    self._device_in_sync = False
                    self._iptables_in_sync = False
                all_new_ips = new_ips | set(
                    [n["ext_ip"] for n in new_nat_mappings])
                if all_old_ips != all_new_ips:
                    # Ensure we clean up any conntrack entries for IPs that
                    # have been removed.
                    _log.debug("Set of all IPs changed from %s to %s",
                               all_old_ips, all_new_ips)
                    self._removed_ips |= all_old_ips
                    self._removed_ips -= all_new_ips

            tiers = pending_endpoint.get("tiers", [])
            tier_dict = OrderedDict()
            for tier in tiers or []:
                pols = []
                for pol in tier["policies"] or []:
                    pol_id = TieredPolicyId(tier["name"], pol)
                    pols.append(pol_id)
                tier_dict[tier["name"]] = pols
            if self._pol_ids_by_tier.items() != tier_dict.items():
                self._pol_ids_by_tier = tier_dict
                self._iptables_in_sync = False
                self._profile_ids_dirty = True

        else:
            # Delete of the endpoint.  Need to resync everything.
            self._profile_ids_dirty = True
            self._iptables_in_sync = False
            self._device_in_sync = False
            self._removed_ips |= all_old_ips

        self.endpoint = pending_endpoint
        self._endpoint_update_pending = False
        self._pending_endpoint = None
Example #14
0
 def on_tiered_policy_remove(self, msg):
     _log.debug("Rules for %s/%s set", msg.id.tier, msg.id.name)
     _stats.increment("Tiered rules created/updated")
     policy_id = TieredPolicyId(msg.id.tier, msg.id.name)
     self.splitter.on_rules_update(policy_id, None)
Example #15
0
    def test_tiered_policy_ordering_and_updates(self):
        """
        Check that the tier_sequence ordering is updated correctly as we
        add and remove tiers and policies.
        """
        # Make sure we have an endpoint so that we can check that it gets
        # put in the dirty set.
        self.mgr.on_datamodel_in_sync(async=True)
        self.mgr.on_endpoint_update(ENDPOINT_ID,
                                    {"name": "tap12345"},
                                    async=True)
        self.step_actor(self.mgr)

        # Pretend that the endpoint is alive so that we'll send updates to id.
        m_endpoint = Mock(spec=LocalEndpoint)
        self.mgr.objects_by_id[ENDPOINT_ID] = m_endpoint
        self.mgr._is_starting_or_live = Mock(return_value=True)

        # Add a profile into the tier so it'll apply to the endpoint.
        pol_id_a = TieredPolicyId("a", "a1")
        self.mgr.on_policy_selector_update(pol_id_a, parse_selector("all()"),
                                           10, async=True)
        pol_id_b = TieredPolicyId("b", "b1")
        self.mgr.on_policy_selector_update(pol_id_b, parse_selector("all()"),
                                           10, async=True)
        pol_id_c1 = TieredPolicyId("c1", "c1")
        self.mgr.on_policy_selector_update(pol_id_c1, parse_selector("all()"),
                                           10, async=True)
        pol_id_c2 = TieredPolicyId("c2", "c2")
        self.mgr.on_policy_selector_update(pol_id_c2, parse_selector("all()"),
                                           10, async=True)
        pol_id_c3 = TieredPolicyId("c3", "c3")
        self.mgr.on_policy_selector_update(pol_id_c3, parse_selector("all()"),
                                           10, async=True)
        self.step_actor(self.mgr)
        # Since we haven't set the tier ID yet, the policy won't get applied...
        self.assertEqual(m_endpoint.on_tiered_policy_update.mock_calls,
                         [mock.call(OrderedDict(), async=True)] * 5)
        m_endpoint.on_tiered_policy_update.reset_mock()

        # Adding a tier should trigger an update, adding the tier and policy.
        self.mgr.on_tier_data_update("a", {"order": 1}, async=True)
        self.step_actor(self.mgr)
        self.assertEqual(self.mgr.endpoints_with_dirty_policy, set())
        tiers = OrderedDict()
        tiers["a"] = [pol_id_a]
        self.assertEqual(m_endpoint.on_tiered_policy_update.mock_calls,
                         [mock.call(tiers, async=True)])
        m_endpoint.on_tiered_policy_update.reset_mock()

        # Idempotent update should get squashed.
        self.mgr.on_tier_data_update("a", {"order": 2}, async=True)
        self.mgr.on_tier_data_update("a", {"order": 2}, async=True)
        self.step_actor(self.mgr)
        self.assertEqual(m_endpoint.on_tiered_policy_update.mock_calls, [])

        # Adding another tier should trigger an update.
        self.mgr.on_tier_data_update("b", {"order": 3}, async=True)
        self.step_actor(self.mgr)
        tiers = OrderedDict()
        tiers["a"] = [pol_id_a]
        tiers["b"] = [pol_id_b]
        self.assertEqual(m_endpoint.on_tiered_policy_update.mock_calls,
                         [mock.call(tiers, async=True)])
        m_endpoint.on_tiered_policy_update.reset_mock()

        # Swapping the order should trigger an update.
        self.mgr.on_tier_data_update("b", {"order": 1}, async=True)
        self.step_actor(self.mgr)
        tiers = OrderedDict()
        tiers["b"] = [pol_id_b]
        tiers["a"] = [pol_id_a]
        self.assertEqual(m_endpoint.on_tiered_policy_update.mock_calls,
                         [mock.call(tiers, async=True)])
        m_endpoint.on_tiered_policy_update.reset_mock()

        # Check deletion and that it's idempotent.
        self.mgr.on_tier_data_update("b", None, async=True)
        self.step_actor(self.mgr)
        self.mgr.on_policy_selector_update(pol_id_b, None, None, async=True)
        self.mgr.on_policy_selector_update(pol_id_b, None, None, async=True)
        self.step_actor(self.mgr)
        self.mgr.on_tier_data_update("b", None, async=True)
        self.step_actor(self.mgr)
        self.mgr.on_policy_selector_update(pol_id_b, None, None, async=True)
        self.mgr.on_policy_selector_update(pol_id_b, None, None, async=True)
        self.step_actor(self.mgr)
        tiers = OrderedDict()
        tiers["a"] = [pol_id_a]
        self.assertEqual(
            m_endpoint.on_tiered_policy_update.mock_calls,
            [mock.call(tiers, async=True)] * 2  # One for policy, one for tier.
        )
        m_endpoint.on_tiered_policy_update.reset_mock()

        # Check lexicographic tie-breaker.
        self.mgr.on_tier_data_update("c1", {"order": 0}, async=True)
        self.mgr.on_tier_data_update("c2", {"order": 0}, async=True)
        self.mgr.on_tier_data_update("c3", {"order": 0}, async=True)
        self.step_actor(self.mgr)
        tiers = OrderedDict()
        # All 'c's should sort before 'a' due to explicit ordering but 'c's
        # should sort in lexicographic order.
        tiers["c1"] = [pol_id_c1]
        tiers["c2"] = [pol_id_c2]
        tiers["c3"] = [pol_id_c3]
        tiers["a"] = [pol_id_a]
        actual_call = m_endpoint.on_tiered_policy_update.mock_calls[-1]
        expected_call = mock.call(tiers, async=True)
        self.assertEqual(actual_call, expected_call,
                         msg="\nExpected: %s\n Got:     %s" %
                             (expected_call, actual_call))
        m_endpoint.on_tiered_policy_update.reset_mock()