Пример #1
0
class TestSetDelta(TestCase):
    def setUp(self):
        self.set = set("abc")
        self.delta = SetDelta(self.set)

    def test_add(self):
        self.delta.add("c")
        self.delta.add("d")
        # Only "d" added, "c" was already present.
        self.assertEqual(self.delta.added_entries, set(["d"]))
        # Now apply, should mutate the set.
        self.assertEqual(self.set, set("abc"))
        self.delta.apply_and_reset()
        self.assertEqual(self.set, set("abcd"))
        self.assertEqual(self.delta.added_entries, set())

    def test_remove(self):
        self.delta.remove("c")
        self.delta.remove("d")
        # Only "c" added, "d" was already missing.
        self.assertEqual(self.delta.removed_entries, set(["c"]))
        # Now apply, should mutate the set.
        self.assertEqual(self.set, set("abc"))
        self.delta.apply_and_reset()
        self.assertEqual(self.set, set("ab"))
        self.assertEqual(self.delta.removed_entries, set())

    def test_add_and_remove(self):
        self.delta.add("c")  # No-op, already present.
        self.delta.add("d")  # Put in added set.
        self.delta.add("e")  # Will remain in added set.
        self.delta.remove("c")  # Recorded in remove set.
        self.delta.remove("d")  # Cancels the pending add only.
        self.delta.remove("f")  # No-op.

        self.assertEqual(self.delta.added_entries, set("e"))
        self.assertEqual(self.delta.removed_entries, set("c"))
        self.delta.apply_and_reset()
        self.assertEqual(self.set, set("abe"))
Пример #2
0
class IpsetActor(Actor):
    """
    Actor managing a single ipset.

    Batches up updates to minimise the number of actual dataplane updates.
    """

    def __init__(self, ipset, qualifier=None):
        """
        :param Ipset ipset: Ipset object to wrap.
        :param str qualifier: Actor qualifier string for logging.
        """
        super(IpsetActor, self).__init__(qualifier=qualifier)

        self._ipset = ipset
        # Members - which entries should be in the ipset.
        self.members = None
        # SetDelta, used to track a sequence of changes.
        self.changes = None

        self._force_reprogram = True
        self.stopped = False

    @property
    def ipset_name(self):
        """
        The name of the primary ipset.  Safe to access from another greenlet;
        only accesses immutable state.
        """
        return self._ipset.set_name

    def owned_ipset_names(self):
        """
        This method is safe to call from another greenlet; it only accesses
        immutable state.

        :return: set of name of ipsets that this Actor owns and manages.  the
                 sets may or may not be present.
        """
        return set([self._ipset.set_name, self._ipset.temp_set_name])

    @actor_message()
    def replace_members(self, members):
        """
        Replace the members of this ipset with the supplied set.

        :param set[str]|list[str] members: The IP address strings.  This
               method takes a copy of the contents.
        """
        _log.info("Replacing members of ipset %s", self.name)
        self.members = set(members)
        self._force_reprogram = True  # Force a full rewrite of the set.
        self.changes = SetDelta(self.members)  # Any changes now obsolete.

    @actor_message()
    def add_members(self, new_members):
        _log.debug("Adding %s to tag ipset %s", new_members, self.name)
        assert self.members is not None, (
            "add_members() called before init by replace_members()"
        )
        for member in new_members:
            self.changes.add(member)

    @actor_message()
    def remove_members(self, removed_members):
        _log.debug("Removing %s from tag ipset %s", removed_members, self.name)
        assert self.members is not None, (
            "remove_members() called before init by replace_members()"
        )
        for member in removed_members:
            self.changes.remove(member)

    def _finish_msg_batch(self, batch, results):
        _log.debug("IpsetActor._finish_msg_batch() called")
        if not self.stopped:
            self._sync_to_ipset()

    def _sync_to_ipset(self):
        _log.debug("Syncing %s to kernel", self.name)
        if self.changes.resulting_size > self._ipset.max_elem:
            _log.error("ipset %s exceeds maximum size %s.  ipset will not "
                       "be updated until size drops below %s.",
                       self.ipset_name, self._ipset.max_elem,
                       self._ipset.max_elem)
            return

        if not self._force_reprogram:
            # Just an incremental update, try to apply it as a delta.
            if not self.changes.empty:
                _log.debug("Normal update, attempting to apply as a delta:"
                           "added=%s, removed=%s", self.changes.added_entries,
                           self.changes.removed_entries)
                try:
                    self._ipset.apply_changes(self.changes.added_entries,
                                              self.changes.removed_entries)
                except FailedSystemCall as e:
                    _log.error("Failed to update ipset %s, attempting to "
                               "do a full rewrite RC=%s, err=%s",
                               self.name, e.retcode, e.stderr)
                    self._force_reprogram = True

        # Either we're now in sync or we're about to try rewriting the ipset
        # as a whole.  Either way, apply the changes to the members set.
        self.changes.apply_and_reset()

        if self._force_reprogram:
            # Initial update or post-failure, completely replace the ipset's
            # contents with an atomic swap.
            _log.debug("Replacing content of ipset %s with %s", self,
                       self.members)
            self._ipset.replace_members(self.members)
            self._force_reprogram = False
        _log.debug("Finished syncing %s to kernel", self.name)
Пример #3
0
class IpsetActor(Actor):
    """
    Actor managing a single ipset.

    Batches up updates to minimise the number of actual dataplane updates.
    """
    def __init__(self, ipset, qualifier=None):
        """
        :param Ipset ipset: Ipset object to wrap.
        :param str qualifier: Actor qualifier string for logging.
        """
        super(IpsetActor, self).__init__(qualifier=qualifier)

        self._ipset = ipset
        # Members - which entries should be in the ipset.
        self.members = None
        # SetDelta, used to track a sequence of changes.
        self.changes = None

        self._force_reprogram = True
        self.stopped = False

    @property
    def ipset_name(self):
        """
        The name of the primary ipset.  Safe to access from another greenlet;
        only accesses immutable state.
        """
        return self._ipset.set_name

    def owned_ipset_names(self):
        """
        This method is safe to call from another greenlet; it only accesses
        immutable state.

        :return: set of name of ipsets that this Actor owns and manages.  the
                 sets may or may not be present.
        """
        return set([self._ipset.set_name, self._ipset.temp_set_name])

    @actor_message()
    def replace_members(self, members):
        """
        Replace the members of this ipset with the supplied set.

        :param set[str]|list[str] members: The IP address strings.  This
               method takes a copy of the contents.
        """
        _log.info("Replacing members of ipset %s", self.name)
        self.members = set(members)
        self._force_reprogram = True  # Force a full rewrite of the set.
        self.changes = SetDelta(self.members)  # Any changes now obsolete.

    @actor_message()
    def add_members(self, new_members):
        _log.debug("Adding %s to tag ipset %s", new_members, self.name)
        assert self.members is not None, (
            "add_members() called before init by replace_members()")
        for member in new_members:
            self.changes.add(member)

    @actor_message()
    def remove_members(self, removed_members):
        _log.debug("Removing %s from tag ipset %s", removed_members, self.name)
        assert self.members is not None, (
            "remove_members() called before init by replace_members()")
        for member in removed_members:
            self.changes.remove(member)

    def _finish_msg_batch(self, batch, results):
        _log.debug("IpsetActor._finish_msg_batch() called")
        if not self.stopped:
            self._sync_to_ipset()

    def _sync_to_ipset(self):
        _log.debug("Syncing %s to kernel", self.name)
        if self.changes.resulting_size > self._ipset.max_elem:
            _log.error(
                "ipset %s exceeds maximum size %s.  ipset will not "
                "be updated until size drops below %s.", self.ipset_name,
                self._ipset.max_elem, self._ipset.max_elem)
            return

        if not self._force_reprogram:
            # Just an incremental update, try to apply it as a delta.
            if not self.changes.empty:
                _log.debug(
                    "Normal update, attempting to apply as a delta:"
                    "added=%s, removed=%s", self.changes.added_entries,
                    self.changes.removed_entries)
                try:
                    self._ipset.apply_changes(self.changes.added_entries,
                                              self.changes.removed_entries)
                except FailedSystemCall as e:
                    _log.error(
                        "Failed to update ipset %s, attempting to "
                        "do a full rewrite RC=%s, err=%s", self.name,
                        e.retcode, e.stderr)
                    self._force_reprogram = True

        # Either we're now in sync or we're about to try rewriting the ipset
        # as a whole.  Either way, apply the changes to the members set.
        self.changes.apply_and_reset()

        if self._force_reprogram:
            # Initial update or post-failure, completely replace the ipset's
            # contents with an atomic swap.
            _log.debug("Replacing content of ipset %s with %s", self,
                       self.members)
            self._ipset.replace_members(self.members)
            self._force_reprogram = False
        _log.debug("Finished syncing %s to kernel", self.name)