Beispiel #1
0
 def __topology_changed(self, ignored, watch_topology_function):
     """Internal callback used by _watch_topology()."""
     # Need to guard on the client being connected in the case
     # 1) a watch is waiting to run (in the reactor);
     # 2) and the connection is closed.
     #
     # It remains the reponsibility of `watch_topology_function` to
     # raise `StopWatcher`, per the doc of `_topology_changed`.
     if not self._client.connected:
         return
     try:
         get, watch = self._client.get_and_watch("/topology")
         content, stat = yield get
     except NoNodeException:
         # WTF? The node went away! This is an unexpected bug
         # which we try to hide from the callback to simplify
         # things.  We'll set the watch back, and once the new
         # content comes up, we'll present the delta as usual.
         log.warning("The /topology node went missing!")
         self._watch_topology(watch_topology_function)
     else:
         new_topology = InternalTopology()
         new_topology.parse(content)
         try:
             yield watch_topology_function(self._old_topology, new_topology)
         except StopWatcher:
             return
         self._old_topology = new_topology
         watch.addCallback(self.__topology_changed, watch_topology_function)
Beispiel #2
0
 def __topology_changed(self, ignored, watch_topology_function):
     """Internal callback used by _watch_topology()."""
     # Need to guard on the client being connected in the case
     # 1) a watch is waiting to run (in the reactor);
     # 2) and the connection is closed.
     #
     # It remains the reponsibility of `watch_topology_function` to
     # raise `StopWatcher`, per the doc of `_topology_changed`.
     if not self._client.connected:
         return
     try:
         get, watch = self._client.get_and_watch("/topology")
         content, stat = yield get
     except NoNodeException:
         # WTF? The node went away! This is an unexpected bug
         # which we try to hide from the callback to simplify
         # things.  We'll set the watch back, and once the new
         # content comes up, we'll present the delta as usual.
         log.warning("The /topology node went missing!")
         self._watch_topology(watch_topology_function)
     else:
         new_topology = InternalTopology()
         new_topology.parse(content)
         try:
             yield watch_topology_function(self._old_topology, new_topology)
         except StopWatcher:
             return
         self._old_topology = new_topology
         watch.addCallback(self.__topology_changed, watch_topology_function)
Beispiel #3
0
 def test_read_empty_topology(self):
     """
     When the state is empty (no /topology) file, reading the
     topology should return an empty one.
     """
     topology = yield self.base._read_topology()
     empty_topology = InternalTopology()
     self.assertEquals(topology.dump(), empty_topology.dump())
Beispiel #4
0
 def test_read_empty_topology(self):
     """
     When the state is empty (no /topology) file, reading the
     topology should return an empty one.
     """
     topology = yield self.base._read_topology()
     empty_topology = InternalTopology()
     self.assertEquals(topology.dump(), empty_topology.dump())
Beispiel #5
0
 def test_non_empty_topology(self):
     """
     When there's something in /topology already, it should of
     course be read and parsed when we read the topology.
     """
     test_topology = InternalTopology()
     test_topology.add_machine("m-0")
     topology_dump = test_topology.dump()
     yield self.client.create("/topology", topology_dump)
     topology = yield self.base._read_topology()
     self.assertEquals(topology.dump(), topology_dump)
Beispiel #6
0
 def test_non_empty_topology(self):
     """
     When there's something in /topology already, it should of
     course be read and parsed when we read the topology.
     """
     test_topology = InternalTopology()
     test_topology.add_machine("m-0")
     topology_dump = test_topology.dump()
     yield self.client.create("/topology", topology_dump)
     topology = yield self.base._read_topology()
     self.assertEquals(topology.dump(), topology_dump)
Beispiel #7
0
    def test_watch_topology_may_defer(self):
        """
        The watch topology may return a deferred so that it performs
        some of its logic asynchronously.  In this case, it must not
        be called a second time before its postponed logic is finished
        completely.
        """
        wait_callback = [Deferred() for i in range(10)]
        finish_callback = [Deferred() for i in range(10)]

        calls = []

        def watch_topology(old_topology, new_topology):
            calls.append((old_topology, new_topology))
            wait_callback[len(calls)-1].callback(True)
            return finish_callback[len(calls)-1]

        # Start watching.
        self.base._watch_topology(watch_topology)

        # Create the topology.
        topology = InternalTopology()
        topology.add_machine("m-0")
        yield self.set_topology(topology)

        # Hold off until callback is started.
        yield wait_callback[0]

        # Change the topology again.
        topology.add_machine("m-1")
        yield self.set_topology(topology)

        # Give a chance for something bad to happen.
        yield self.poke_zk()

        # Ensure we still have a single call.
        self.assertEquals(len(calls), 1)

        # Allow the first call to be completed, and wait on the
        # next one.
        finish_callback[0].callback(None)
        yield wait_callback[1]
        finish_callback[1].callback(None)

        # We should have the second change now.
        self.assertEquals(len(calls), 2)
        old_topology, new_topology = calls[1]
        self.assertEquals(old_topology.has_machine("m-0"), True)
        self.assertEquals(old_topology.has_machine("m-1"), False)
        self.assertEquals(new_topology.has_machine("m-0"), True)
        self.assertEquals(new_topology.has_machine("m-1"), True)
Beispiel #8
0
    def test_watch_topology_when_it_already_exists(self):
        """
        It should also be possible to start watching the topology when
        the topology already exists, of course. In this case, the callback
        should fire immediately, and should have an old_topology of None
        so that the callback has a chance to understand that it's the
        first time a topology is being processed, even if it already
        existed before.
        """
        # Create the topology ahead of time.
        topology = InternalTopology()
        topology.add_machine("m-0")
        yield self.set_topology(topology)

        wait_callback = [Deferred() for i in range(10)]

        calls = []

        def watch_topology(old_topology, new_topology):
            calls.append((old_topology, new_topology))
            wait_callback[len(calls)-1].callback(True)

        # Start watching, and wait on callback immediately.
        self.base._watch_topology(watch_topology)
        yield wait_callback[0]

        # The first callback must have been fired, and it must have None
        # as the first argument because that's the first topology seen.
        self.assertEquals(len(calls), 1)
        old_topology, new_topology = calls[0]
        self.assertEquals(old_topology, None)
        self.assertEquals(new_topology.has_machine("m-0"), True)
        self.assertEquals(new_topology.has_machine("m-1"), False)

        # Change the topology again.
        topology.add_machine("m-1")
        yield self.set_topology(topology)
        yield wait_callback[1]

        # Give a chance for something bad to happen.
        yield self.poke_zk()

        # Now the watch callback must have been fired with two
        # different topologies.  The old one, and the new one.
        self.assertEquals(len(calls), 2)
        old_topology, new_topology = calls[1]
        self.assertEquals(old_topology.has_machine("m-0"), True)
        self.assertEquals(old_topology.has_machine("m-1"), False)
        self.assertEquals(new_topology.has_machine("m-0"), True)
        self.assertEquals(new_topology.has_machine("m-1"), True)
Beispiel #9
0
    def test_watch_topology_when_it_goes_missing(self):
        """
        We consider a deleted /topology to be an error, and will not
        warn the callback about it. Instead, we'll wait until a new
        topology is brought up, and then will fire the callback with
        the full delta.
        """
        # Create the topology ahead of time.
        topology = InternalTopology()
        topology.add_machine("m-0")
        yield self.set_topology(topology)

        wait_callback = [Deferred() for i in range(10)]

        calls = []

        def watch_topology(old_topology, new_topology):
            calls.append((old_topology, new_topology))
            wait_callback[len(calls)-1].callback(True)

        # Start watching, and wait on callback immediately.
        self.base._watch_topology(watch_topology)

        # Ignore the first callback with the initial state, as
        # this is already tested above.
        yield wait_callback[0]

        log = self.capture_logging()

        # Kill the /topology node entirely.
        yield self.client.delete("/topology")

        # Create a new topology from the ground up.
        topology.add_machine("m-1")
        yield self.set_topology(topology)
        yield wait_callback[1]

        # The issue should have been logged.
        self.assertIn("The /topology node went missing!",
                      log.getvalue())

        # Check that we've only perceived the delta.
        self.assertEquals(len(calls), 2)
        old_topology, new_topology = calls[1]
        self.assertEquals(old_topology.has_machine("m-0"), True)
        self.assertEquals(old_topology.has_machine("m-1"), False)
        self.assertEquals(new_topology.has_machine("m-0"), True)
        self.assertEquals(new_topology.has_machine("m-1"), True)
Beispiel #10
0
    def test_watch_topology_when_being_created(self):
        """
        It should be possible to start watching the topology even
        before it is created.  In this case, the callback will be
        made when it's actually introduced.
        """
        wait_callback = [Deferred() for i in range(10)]

        calls = []

        def watch_topology(old_topology, new_topology):
            calls.append((old_topology, new_topology))
            wait_callback[len(calls)-1].callback(True)

        # Start watching.
        self.base._watch_topology(watch_topology)

        # Callback is still untouched.
        self.assertEquals(calls, [])

        # Create the topology, and wait for callback.
        topology = InternalTopology()
        topology.add_machine("m-0")
        yield self.set_topology(topology)
        yield wait_callback[0]

        # The first callback must have been fired, and it must have None
        # as the first argument because that's the first topology seen.
        self.assertEquals(len(calls), 1)
        old_topology, new_topology = calls[0]
        self.assertEquals(old_topology, None)
        self.assertEquals(new_topology.has_machine("m-0"), True)
        self.assertEquals(new_topology.has_machine("m-1"), False)

        # Change the topology again.
        topology.add_machine("m-1")
        yield self.set_topology(topology)
        yield wait_callback[1]

        # Now the watch callback must have been fired with two
        # different topologies.  The old one, and the new one.
        self.assertEquals(len(calls), 2)
        old_topology, new_topology = calls[1]
        self.assertEquals(old_topology.has_machine("m-0"), True)
        self.assertEquals(old_topology.has_machine("m-1"), False)
        self.assertEquals(new_topology.has_machine("m-0"), True)
        self.assertEquals(new_topology.has_machine("m-1"), True)
Beispiel #11
0
    def test_watch_stops_on_closed_connection(self):
        """Verify watches stops when the connection is closed."""

        # Use a separate client connection for watching so it can be
        # disconnected.
        watch_client = ZookeeperClient(get_test_zookeeper_address())
        yield watch_client.connect()
        watch_base = StateBase(watch_client)

        wait_callback = Deferred()
        finish_callback = Deferred()
        calls = []

        def watcher(old_topology, new_topology):
            calls.append((old_topology, new_topology))
            wait_callback.callback(True)
            return finish_callback

        # Start watching.
        yield watch_base._watch_topology(watcher)

        # Create the topology.
        topology = InternalTopology()
        topology.add_machine("m-0")
        yield self.set_topology(topology)
        
        # Hold off until callback is started.
        yield wait_callback

        # Change the topology.
        topology.add_machine("m-1")
        yield self.set_topology(topology)

        # Ensure that the watch has been called just once so far
        # (although still pending due to the finish_callback).
        self.assertEquals(len(calls), 1)

        # Now disconnect the client.
        watch_client.close()
        self.assertFalse(watch_client.connected)
        self.assertTrue(self.client.connected)

        # Change the topology again.
        topology.add_machine("m-2")
        yield self.set_topology(topology)

        # Allow the first call to be completed, starting a process of
        # watching for the next change. At this point, the watch will
        # encounter that the client is disconnected.
        finish_callback.callback(True)

        # Give a chance for something bad to happen.
        yield self.poke_zk()

        # Ensure the watch was still not called.
        self.assertEquals(len(calls), 1)
Beispiel #12
0
    def _read_topology(self):
        """Read the /topology node and return an InternalTopology object.

        This object should be used with read-only semantics. For changing the
        topology, check out the _retry_topology_change() method.

        Note that this method name is underlined to mean "protected", not
        "private", since the only purpose of this method is to be used by
        subclasses.
        """
        topology = InternalTopology()
        try:
            content, stat = yield self._client.get("/topology")
            topology.parse(content)
        except NoNodeException:
            pass
        returnValue(topology)
Beispiel #13
0
    def _read_topology(self):
        """Read the /topology node and return an InternalTopology object.

        This object should be used with read-only semantics. For changing the
        topology, check out the _retry_topology_change() method.

        Note that this method name is underlined to mean "protected", not
        "private", since the only purpose of this method is to be used by
        subclasses.
        """
        topology = InternalTopology()
        try:
            content, stat = yield self._client.get("/topology")
            topology.parse(content)
        except NoNodeException:
            pass
        returnValue(topology)
Beispiel #14
0
    def test_change_non_empty_topology(self):
        """
        Attempting to change a pre-existing topology should modify
        it accordingly.
        """
        test_topology = InternalTopology()
        test_topology.add_machine("m-0")
        topology_dump = test_topology.dump()
        yield self.client.create("/topology", topology_dump)

        def change_topology(topology):
            topology.add_machine("m-1")
        yield self.base._retry_topology_change(change_topology)

        content, stat = yield self.client.get("/topology")
        topology = self.parse_topology(content)
        self.assertTrue(topology.has_machine("m-0"))
        self.assertTrue(topology.has_machine("m-1"))
Beispiel #15
0
    def test_change_non_empty_topology(self):
        """
        Attempting to change a pre-existing topology should modify
        it accordingly.
        """
        test_topology = InternalTopology()
        test_topology.add_machine("m-0")
        topology_dump = test_topology.dump()
        yield self.client.create("/topology", topology_dump)

        def change_topology(topology):
            topology.add_machine("m-1")
        yield self.base._retry_topology_change(change_topology)

        content, stat = yield self.client.get("/topology")
        topology = self.parse_topology(content)
        self.assertTrue(topology.has_machine("m-0"))
        self.assertTrue(topology.has_machine("m-1"))
Beispiel #16
0
    def test_watch_stops_on_early_closed_connection(self):
        """Verify watches stops when the connection is closed early.

        _watch_topology chains from an exists_and_watch to a
        get_and_watch. This test ensures that this chaining will fail
        gracefully if the connection is closed before this chaining
        can occur.
        """
        # Use a separate client connection for watching so it can be
        # disconnected.
        watch_client = self.get_zookeeper_client()
        yield watch_client.connect()
        watch_base = StateBase(watch_client)

        calls = []

        @inlineCallbacks
        def watcher(old_topology, new_topology):
            calls.append((old_topology, new_topology))

        # Create the topology.
        topology = InternalTopology()
        topology.add_machine("m-0")
        yield self.set_topology(topology)

        # Now disconnect the client.
        watch_client.close()
        self.assertFalse(watch_client.connected)
        self.assertTrue(self.client.connected)

        # Start watching.
        yield watch_base._watch_topology(watcher)

        # Change the topology, this will trigger the watch.
        topology.add_machine("m-1")
        yield self.set_topology(topology)

        # Give a chance for something bad to happen.
        yield self.poke_zk()

        # Ensure the watcher was never called, because its client was
        # disconnected.
        self.assertEquals(len(calls), 0)
Beispiel #17
0
    def test_watch_stops_on_closed_connection(self):
        """Verify watches stops when the connection is closed."""

        # Use a separate client connection for watching so it can be
        # disconnected.
        watch_client = self.get_zookeeper_client()
        yield watch_client.connect()
        watch_base = StateBase(watch_client)

        wait_callback = Deferred()
        finish_callback = Deferred()
        calls = []

        def watcher(old_topology, new_topology):
            calls.append((old_topology, new_topology))
            wait_callback.callback(True)
            return finish_callback

        # Start watching.
        yield watch_base._watch_topology(watcher)

        # Create the topology.
        topology = InternalTopology()
        topology.add_machine("m-0")
        yield self.set_topology(topology)
        
        # Hold off until callback is started.
        yield wait_callback

        # Change the topology.
        topology.add_machine("m-1")
        yield self.set_topology(topology)

        # Ensure that the watch has been called just once so far
        # (although still pending due to the finish_callback).
        self.assertEquals(len(calls), 1)

        # Now disconnect the client.
        watch_client.close()
        self.assertFalse(watch_client.connected)
        self.assertTrue(self.client.connected)

        # Change the topology again.
        topology.add_machine("m-2")
        yield self.set_topology(topology)

        # Allow the first call to be completed, starting a process of
        # watching for the next change. At this point, the watch will
        # encounter that the client is disconnected.
        finish_callback.callback(True)

        # Give a chance for something bad to happen.
        yield self.poke_zk()

        # Ensure the watch was still not called.
        self.assertEquals(len(calls), 1)
Beispiel #18
0
    def test_watch_topology_may_defer(self):
        """
        The watch topology may return a deferred so that it performs
        some of its logic asynchronously.  In this case, it must not
        be called a second time before its postponed logic is finished
        completely.
        """
        wait_callback = [Deferred() for i in range(10)]
        finish_callback = [Deferred() for i in range(10)]

        calls = []

        def watch_topology(old_topology, new_topology):
            calls.append((old_topology, new_topology))
            wait_callback[len(calls)-1].callback(True)
            return finish_callback[len(calls)-1]

        # Start watching.
        self.base._watch_topology(watch_topology)

        # Create the topology.
        topology = InternalTopology()
        topology.add_machine("m-0")
        yield self.set_topology(topology)

        # Hold off until callback is started.
        yield wait_callback[0]

        # Change the topology again.
        topology.add_machine("m-1")
        yield self.set_topology(topology)

        # Give a chance for something bad to happen.
        yield self.poke_zk()

        # Ensure we still have a single call.
        self.assertEquals(len(calls), 1)

        # Allow the first call to be completed, and wait on the
        # next one.
        finish_callback[0].callback(None)
        yield wait_callback[1]
        finish_callback[1].callback(None)

        # We should have the second change now.
        self.assertEquals(len(calls), 2)
        old_topology, new_topology = calls[1]
        self.assertEquals(old_topology.has_machine("m-0"), True)
        self.assertEquals(old_topology.has_machine("m-1"), False)
        self.assertEquals(new_topology.has_machine("m-0"), True)
        self.assertEquals(new_topology.has_machine("m-1"), True)
Beispiel #19
0
    def test_stop_watch(self):
        """
        A watch that fires a `StopWatcher` exception will end the
        watch."""
        wait_callback = [Deferred() for i in range(5)]
        calls = []

        def watcher(old_topology, new_topology):
            calls.append((old_topology, new_topology))
            wait_callback[len(calls)-1].callback(True)
            if len(calls) == 2:
                raise StopWatcher()

        # Start watching.
        self.base._watch_topology(watcher)

        # Create the topology.
        topology = InternalTopology()
        topology.add_machine("m-0")
        yield self.set_topology(topology)

        # Hold off until callback is started.
        yield wait_callback[0]

        # Change the topology again.
        topology.add_machine("m-1")
        yield self.set_topology(topology)

        yield wait_callback[1]
        self.assertEqual(len(calls), 2)

        # Change the topology again, we shouldn't see this.
        topology.add_machine("m-2")
        yield self.set_topology(topology)

        # Give a chance for something bad to happen.
        yield self.poke_zk()

        # Ensure we still have a single call.
        self.assertEquals(len(calls), 2)
Beispiel #20
0
    def test_watch_topology_when_it_already_exists(self):
        """
        It should also be possible to start watching the topology when
        the topology already exists, of course. In this case, the callback
        should fire immediately, and should have an old_topology of None
        so that the callback has a chance to understand that it's the
        first time a topology is being processed, even if it already
        existed before.
        """
        # Create the topology ahead of time.
        topology = InternalTopology()
        topology.add_machine("m-0")
        yield self.set_topology(topology)

        wait_callback = [Deferred() for i in range(10)]

        calls = []

        def watch_topology(old_topology, new_topology):
            calls.append((old_topology, new_topology))
            wait_callback[len(calls)-1].callback(True)

        # Start watching, and wait on callback immediately.
        self.base._watch_topology(watch_topology)
        yield wait_callback[0]

        # The first callback must have been fired, and it must have None
        # as the first argument because that's the first topology seen.
        self.assertEquals(len(calls), 1)
        old_topology, new_topology = calls[0]
        self.assertEquals(old_topology, None)
        self.assertEquals(new_topology.has_machine("m-0"), True)
        self.assertEquals(new_topology.has_machine("m-1"), False)

        # Change the topology again.
        topology.add_machine("m-1")
        yield self.set_topology(topology)
        yield wait_callback[1]

        # Give a chance for something bad to happen.
        yield self.poke_zk()

        # Now the watch callback must have been fired with two
        # different topologies.  The old one, and the new one.
        self.assertEquals(len(calls), 2)
        old_topology, new_topology = calls[1]
        self.assertEquals(old_topology.has_machine("m-0"), True)
        self.assertEquals(old_topology.has_machine("m-1"), False)
        self.assertEquals(new_topology.has_machine("m-0"), True)
        self.assertEquals(new_topology.has_machine("m-1"), True)
Beispiel #21
0
    def test_watch_topology_when_it_goes_missing(self):
        """
        We consider a deleted /topology to be an error, and will not
        warn the callback about it. Instead, we'll wait until a new
        topology is brought up, and then will fire the callback with
        the full delta.
        """
        # Create the topology ahead of time.
        topology = InternalTopology()
        topology.add_machine("m-0")
        yield self.set_topology(topology)

        wait_callback = [Deferred() for i in range(10)]

        calls = []

        def watch_topology(old_topology, new_topology):
            calls.append((old_topology, new_topology))
            wait_callback[len(calls)-1].callback(True)

        # Start watching, and wait on callback immediately.
        self.base._watch_topology(watch_topology)

        # Ignore the first callback with the initial state, as
        # this is already tested above.
        yield wait_callback[0]

        log = self.capture_logging()

        # Kill the /topology node entirely.
        yield self.client.delete("/topology")

        # Create a new topology from the ground up.
        topology.add_machine("m-1")
        yield self.set_topology(topology)
        yield wait_callback[1]

        # The issue should have been logged.
        self.assertIn("The /topology node went missing!",
                      log.getvalue())

        # Check that we've only perceived the delta.
        self.assertEquals(len(calls), 2)
        old_topology, new_topology = calls[1]
        self.assertEquals(old_topology.has_machine("m-0"), True)
        self.assertEquals(old_topology.has_machine("m-1"), False)
        self.assertEquals(new_topology.has_machine("m-0"), True)
        self.assertEquals(new_topology.has_machine("m-1"), True)
Beispiel #22
0
    def test_watch_topology_when_being_created(self):
        """
        It should be possible to start watching the topology even
        before it is created.  In this case, the callback will be
        made when it's actually introduced.
        """
        wait_callback = [Deferred() for i in range(10)]

        calls = []

        def watch_topology(old_topology, new_topology):
            calls.append((old_topology, new_topology))
            wait_callback[len(calls)-1].callback(True)

        # Start watching.
        self.base._watch_topology(watch_topology)

        # Callback is still untouched.
        self.assertEquals(calls, [])

        # Create the topology, and wait for callback.
        topology = InternalTopology()
        topology.add_machine("m-0")
        yield self.set_topology(topology)
        yield wait_callback[0]

        # The first callback must have been fired, and it must have None
        # as the first argument because that's the first topology seen.
        self.assertEquals(len(calls), 1)
        old_topology, new_topology = calls[0]
        self.assertEquals(old_topology, None)
        self.assertEquals(new_topology.has_machine("m-0"), True)
        self.assertEquals(new_topology.has_machine("m-1"), False)

        # Change the topology again.
        topology.add_machine("m-1")
        yield self.set_topology(topology)
        yield wait_callback[1]

        # Now the watch callback must have been fired with two
        # different topologies.  The old one, and the new one.
        self.assertEquals(len(calls), 2)
        old_topology, new_topology = calls[1]
        self.assertEquals(old_topology.has_machine("m-0"), True)
        self.assertEquals(old_topology.has_machine("m-1"), False)
        self.assertEquals(new_topology.has_machine("m-0"), True)
        self.assertEquals(new_topology.has_machine("m-1"), True)
Beispiel #23
0
    def test_stop_watch(self):
        """
        A watch that fires a `StopWatcher` exception will end the
        watch."""
        wait_callback = [Deferred() for i in range(5)]
        calls = []

        def watcher(old_topology, new_topology):
            calls.append((old_topology, new_topology))
            wait_callback[len(calls)-1].callback(True)
            if len(calls) == 2:
                raise StopWatcher()

        # Start watching.
        self.base._watch_topology(watcher)

        # Create the topology.
        topology = InternalTopology()
        topology.add_machine("m-0")
        yield self.set_topology(topology)

        # Hold off until callback is started.
        yield wait_callback[0]

        # Change the topology again.
        topology.add_machine("m-1")
        yield self.set_topology(topology)

        yield wait_callback[1]
        self.assertEqual(len(calls), 2)

        # Change the topology again, we shouldn't see this.
        topology.add_machine("m-2")
        yield self.set_topology(topology)

        # Give a chance for something bad to happen.
        yield self.poke_zk()

        # Ensure we still have a single call.
        self.assertEquals(len(calls), 2)
Beispiel #24
0
    def test_watch_stops_on_early_closed_connection(self):
        """Verify watches stops when the connection is closed early.

        _watch_topology chains from an exists_and_watch to a
        get_and_watch. This test ensures that this chaining will fail
        gracefully if the connection is closed before this chaining
        can occur.
        """
        # Use a separate client connection for watching so it can be
        # disconnected.
        watch_client = ZookeeperClient(get_test_zookeeper_address())
        yield watch_client.connect()
        watch_base = StateBase(watch_client)

        calls = []

        @inlineCallbacks
        def watcher(old_topology, new_topology):
            calls.append((old_topology, new_topology))

        # Create the topology.
        topology = InternalTopology()
        topology.add_machine("m-0")
        yield self.set_topology(topology)
        
        # Now disconnect the client.
        watch_client.close()
        self.assertFalse(watch_client.connected)
        self.assertTrue(self.client.connected)

        # Start watching.
        yield watch_base._watch_topology(watcher)

        # Change the topology, this will trigger the watch.
        topology.add_machine("m-1")
        yield self.set_topology(topology)

        # Give a chance for something bad to happen.
        yield self.poke_zk()

        # Ensure the watcher was never called, because its client was
        # disconnected.
        self.assertEquals(len(calls), 0)
Beispiel #25
0
 def change_content_function(content, stat):
     topology = InternalTopology()
     if content:
         topology.parse(content)
     change_topology_function(topology)
     return topology.dump()
Beispiel #26
0
 def setUp(self):
     self.topology = InternalTopology()
Beispiel #27
0
class InternalTopologyMapTest(TestCase):

    def setUp(self):
        self.topology = InternalTopology()

    def test_add_machine(self):
        """
        The topology map is stored as YAML at the moment, so it
        should be able to read it.
        """
        self.topology.add_machine("m-0")
        self.topology.add_machine("m-1")
        self.assertEquals(sorted(self.topology.get_machines()),
                          ["m-0", "m-1"])

    def test_add_duplicated_machine(self):
        """
        Adding a machine which is already registered should fail.
        """
        self.topology.add_machine("m-0")
        self.assertRaises(InternalTopologyError,
                          self.topology.add_machine, "m-0")

    def test_has_machine(self):
        """
        Testing if a machine is registered should be possible.
        """
        self.assertFalse(self.topology.has_machine("m-0"))
        self.topology.add_machine("m-0")
        self.assertTrue(self.topology.has_machine("m-0"))
        self.assertFalse(self.topology.has_machine("m-1"))

    def test_get_machines(self):
        """
        get_machines() must return a list of machine ids
        previously registered.
        """
        self.assertEquals(self.topology.get_machines(), [])
        self.topology.add_machine("m-0")
        self.topology.add_machine("m-1")
        self.assertEquals(sorted(self.topology.get_machines()),
                          ["m-0", "m-1"])

    def test_remove_machine(self):
        """
        Removing machines should take them off the state.
        """
        self.topology.add_machine("m-0")
        self.topology.add_machine("m-1")

        # Add a non-assigned unit, to test that the logic of
        # checking for assigned units validates this.
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service_unit("s-0", "u-0")

        self.topology.remove_machine("m-0")
        self.assertFalse(self.topology.has_machine("m-0"))
        self.assertTrue(self.topology.has_machine("m-1"))

    def test_remove_non_existent_machine(self):
        """
        Removing non-existing machines should raise an error.
        """
        self.assertRaises(InternalTopologyError,
                          self.topology.remove_machine, "m-0")

    def test_remove_machine_with_assigned_units(self):
        """
        A machine can't be removed when it has assigned units.
        """
        self.topology.add_machine("m-0")
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service_unit("s-0", "u-0")
        self.topology.add_service_unit("s-0", "u-1")
        self.topology.assign_service_unit_to_machine("s-0", "u-1", "m-0")
        self.assertRaises(InternalTopologyError,
                          self.topology.remove_machine, "m-0")

    def test_machine_has_units(self):
        """Test various ways a machine might or might not be assigned."""
        self.topology.add_machine("m-0")
        self.topology.add_machine("m-1")
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service_unit("s-0", "u-0")
        self.topology.add_service_unit("s-0", "u-1")
        self.topology.assign_service_unit_to_machine("s-0", "u-1", "m-0")
        self.assertTrue(self.topology.machine_has_units("m-0"))
        self.assertFalse(self.topology.machine_has_units("m-1"))
        self.assertRaises(
            InternalTopologyError,
            self.topology.machine_has_units, "m-nonesuch")

    def test_add_service(self):
        """
        The topology map is stored as YAML at the moment, so it
        should be able to read it.
        """
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service("s-1", "mysql")
        self.assertEquals(sorted(self.topology.get_services()),
                          ["s-0", "s-1"])

    def test_add_duplicated_service(self):
        """
        Adding a service which is already registered should fail.
        """
        self.topology.add_service("s-0", "wordpress")
        self.assertRaises(InternalTopologyError,
                          self.topology.add_service, "s-0", "wp")

    def test_add_services_with_duplicated_names(self):
        """
        Adding a service which is already registered should fail.
        """
        self.topology.add_service("s-0", "wordpress")
        self.assertRaises(InternalTopologyError,
                          self.topology.add_service, "s-1", "wordpress")

    def test_has_service(self):
        """
        Testing if a service is registered should be possible.
        """
        self.assertFalse(self.topology.has_service("s-0"))
        self.topology.add_service("s-0", "wordpress")
        self.assertTrue(self.topology.has_service("s-0"))
        self.assertFalse(self.topology.has_service("s-1"))

    def test_find_service_with_name(self):
        """
        find_service_with_name() must return the service_id for
        the service with the given name, or None if no service
        is found with that name.
        """
        self.assertEquals(
            self.topology.find_service_with_name("wordpress"), None)
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service("s-1", "mysql")
        self.assertEquals(
            self.topology.find_service_with_name("wordpress"), "s-0")
        self.assertEquals(
            self.topology.find_service_with_name("mysql"), "s-1")

    def test_get_service_name(self):
        """
        get_service_name() should return the service name for the
        given service_id.
        """
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service("s-1", "mysql")
        self.assertEquals(
            self.topology.get_service_name("s-0"), "wordpress")
        self.assertEquals(
            self.topology.get_service_name("s-1"), "mysql")

    def test_get_service_name_with_non_existing_service(self):
        """
        get_service_name() should raise an error if the service
        does not exist.
        """
        # Without any state:
        self.assertRaises(InternalTopologyError,
                          self.topology.get_service_name, "s-0")

        self.topology.add_service("s-0", "wordpress")

        # With some state:
        self.assertRaises(InternalTopologyError,
                          self.topology.get_service_name, "s-1")

    def test_get_services(self):
        """
        Retrieving a list of available services must be possible.
        """
        self.assertEquals(self.topology.get_services(), [])
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service("s-1", "mysql")
        self.assertEquals(sorted(self.topology.get_services()),
                          ["s-0", "s-1"])

    def test_remove_service(self):
        """
        Removing a service should work properly, so that the service
        isn't available anymore after it happens (duh!).
        """
        self.topology.add_service("m-0", "wordpress")
        self.topology.add_service("m-1", "mysql")
        self.topology.remove_service("m-0")
        self.assertFalse(self.topology.has_service("m-0"))
        self.assertTrue(self.topology.has_service("m-1"))

    def test_remove_non_existent_service(self):
        """
        Attempting to remove a non-existing service should be an
        error.
        """
        self.assertRaises(InternalTopologyError,
                          self.topology.remove_service, "m-0")

    def test_add_service_unit(self):
        """
        add_service_unit() should register a new service unit for a
        given service, and should return a sequence number for the
        unit.  The sequence number increases monotonically for each
        service, and is helpful to provide nice unit names.
        """
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service("s-1", "mysql")
        self.assertEquals(self.topology.add_service_unit("s-0", "u-05"), 0)
        self.assertEquals(self.topology.add_service_unit("s-0", "u-12"), 1)
        self.assertEquals(self.topology.add_service_unit("s-1", "u-07"), 0)
        self.assertEquals(sorted(self.topology.get_service_units("s-0")),
                          ["u-05", "u-12"])
        self.assertEquals(self.topology.get_service_units("s-1"),
                          ["u-07"])

    def test_global_unique_service_names(self):
        """Service unit names are unique.

        Even if the underlying service is destroyed and a new
        service with the same name is created, we'll never
        get a duplicate service unit name.
        """
        self.topology.add_service("s-0", "wordpress")
        sequence = self.topology.add_service_unit("s-0", "u-0")
        self.assertEqual(sequence, 0)
        sequence = self.topology.add_service_unit("s-0", "u-1")
        self.assertEqual(sequence, 1)
        self.topology.remove_service("s-0")
        self.topology.add_service("s-0", "wordpress")
        sequence = self.topology.add_service_unit("s-0", "u-1")
        self.assertEqual(sequence, 2)
        self.assertEqual(
            self.topology.get_service_unit_name("s-0", "u-1"),
            "wordpress/2")

    def test_add_duplicated_service_unit(self):
        """
        Adding the same unit to the same service must not be
        possible.
        """
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service_unit("s-0", "u-0")
        self.assertRaises(InternalTopologyError,
                          self.topology.add_service_unit,
                          "s-0", "u-0")

    def test_add_service_unit_to_non_existing_service(self):
        """
        Adding a service unit requires the service to have been
        previously created.
        """
        self.assertRaises(InternalTopologyError,
                          self.topology.add_service_unit,
                          "s-0", "u-0")

    def test_add_service_unit_to_different_service(self):
        """
        Adding the same unit to two different services must not
        be possible.
        """
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service("s-1", "mysql")
        self.topology.add_service_unit("s-0", "u-0")
        self.assertRaises(InternalTopologyError,
                          self.topology.add_service_unit,
                          "s-1", "u-0")

    def test_get_service_units(self):
        """
        Getting units registered from a service should return a
        list of these.
        """
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service("s-1", "mysql")
        self.assertEquals(self.topology.get_service_units("s-0"), [])
        self.topology.add_service_unit("s-0", "u-0")
        self.topology.add_service_unit("s-0", "u-1")
        self.topology.add_service_unit("s-1", "u-2")
        self.assertEquals(sorted(self.topology.get_service_units("s-0")),
                          ["u-0", "u-1"])
        self.assertEquals(sorted(self.topology.get_service_units("s-1")),
                          ["u-2"])

    def test_get_service_units_with_non_existing_service(self):
        """
        Getting service units from a non-existing service should
        raise an error.
        """
        self.assertRaises(InternalTopologyError,
                          self.topology.get_service_units, "s-0")

    def test_has_service_units(self):
        """
        Testing if a service unit exists in a service should be
        possible.
        """
        self.topology.add_service("s-0", "wordpress")
        self.assertFalse(self.topology.has_service_unit("s-0", "u-0"))
        self.topology.add_service_unit("s-0", "u-0")
        self.assertTrue(self.topology.has_service_unit("s-0", "u-0"))
        self.assertFalse(self.topology.has_service_unit("s-0", "u-1"))

    def test_has_service_units_with_non_existing_service(self):
        """
        Testing if a service unit exists should only work if a
        sensible service was provided.
        """
        self.assertRaises(InternalTopologyError,
                          self.topology.has_service_unit, "s-1", "u-0")

    def test_get_service_unit_service(self):
        """
        The reverse operation is also feasible: given a service unit,
        return the service id for the service containing the unit.
        """
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service("s-1", "mysql")
        self.topology.add_service_unit("s-0", "u-0")
        self.topology.add_service_unit("s-0", "u-1")
        self.topology.add_service_unit("s-1", "u-2")
        self.assertEquals(self.topology.get_service_unit_service("u-0"), "s-0")
        self.assertEquals(self.topology.get_service_unit_service("u-1"), "s-0")
        self.assertEquals(self.topology.get_service_unit_service("u-2"), "s-1")

    def test_get_unit_service_with_non_existing_unit(self):
        """
        If the unit provided to get_service_unit_service() doesn't exist,
        raise an error.
        """
        # Without any services.
        self.assertRaises(InternalTopologyError,
                          self.topology.get_service_unit_service, "u-1")

        # With a service without units.
        self.topology.add_service("s-0", "wordpress")
        self.assertRaises(InternalTopologyError,
                          self.topology.get_service_unit_service, "u-1")

        # With a service with a different unit.
        self.topology.add_service_unit("s-0", "u-0")
        self.assertRaises(InternalTopologyError,
                          self.topology.get_service_unit_service, "u-1")

    def test_get_service_unit_name(self):
        """
        Service units are named with the service name and the sequence
        number joined by a slash, such as wordpress/3.  This makes it
        convenient to use from higher layers.
        """
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service("s-1", "mysql")
        self.topology.add_service_unit("s-0", "u-0")
        self.topology.add_service_unit("s-0", "u-1")
        self.topology.add_service_unit("s-1", "u-2")
        self.assertEquals(self.topology.get_service_unit_name("s-0", "u-0"),
                          "wordpress/0")
        self.assertEquals(self.topology.get_service_unit_name("s-0", "u-1"),
                          "wordpress/1")
        self.assertEquals(self.topology.get_service_unit_name("s-1", "u-2"),
                          "mysql/0")

    def test_get_service_unit_name_from_id(self):
        """
        Service units are named with the service name and the sequence
        number joined by a slash, such as wordpress/3.  This makes it
        convenient to use from higher layers. Those layers ocassionally
        need to resolve the id to a name. This is mostly a simple convience
        wrapper around get_service_unit_name
        """
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service_unit("s-0", "u-0")
        self.topology.add_service_unit("s-0", "u-1")

        self.assertEqual(
            self.topology.get_service_unit_name_from_id("u-0"),
            "wordpress/0")
        self.assertEqual(
            self.topology.get_service_unit_name_from_id("u-1"),
            "wordpress/1")
        self.assertRaises(InternalTopologyError,
                          self.topology.get_service_unit_name_from_id,
                          "u-2")

    def test_get_unit_service_id_from_name(self):
        """Retrieve the unit id from the user oriented unit name."""
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service_unit("s-0", "u-0")
        self.topology.add_service_unit("s-0", "u-1")

        self.assertEqual(
            "u-0",
            self.topology.get_service_unit_id_from_name("wordpress/0"))

        self.assertEqual(
            "u-1",
            self.topology.get_service_unit_id_from_name("wordpress/1"))

    def test_get_unit_service_with_non_existing_service_or_unit(self):
        """
        If the unit provided to get_service_unit_service() doesn't exist,
        raise an error.
        """
        # Without any services.
        self.assertRaises(InternalTopologyError,
                          self.topology.get_service_unit_name,
                          "s-0", "u-1")

        # With a service without units.
        self.topology.add_service("s-0", "wordpress")
        self.assertRaises(InternalTopologyError,
                          self.topology.get_service_unit_name,
                          "s-0", "u-1")

        # With a service with a different unit.
        self.topology.add_service_unit("s-0", "u-0")
        self.assertRaises(InternalTopologyError,
                          self.topology.get_service_unit_name,
                          "s-0", "u-1")

    def test_remove_service_unit(self):
        """
        It should be possible to remove a service unit from an
        existing service.
        """
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service_unit("s-0", "m-0")
        self.topology.add_service_unit("s-0", "m-1")
        self.topology.remove_service_unit("s-0", "m-0")
        self.assertFalse(self.topology.has_service_unit("s-0", "m-0"))
        self.assertTrue(self.topology.has_service_unit("s-0", "m-1"))

    def test_remove_non_existent_service_unit(self):
        """
        Attempting to remove a non-existing service unit or a unit
        in a non-existing service should raise a local error.
        """
        self.assertRaises(InternalTopologyError,
                          self.topology.remove_service_unit, "s-0", "m-0")
        self.topology.add_service("s-0", "wordpress")
        self.assertRaises(InternalTopologyError,
                          self.topology.remove_service_unit, "s-0", "m-0")

    def test_service_unit_sequencing(self):
        """
        Even if service units are unregistered, the sequence number
        should not be reused.
        """
        self.topology.add_service("s-0", "wordpress")
        self.assertEquals(self.topology.add_service_unit("s-0", "u-05"), 0)
        self.assertEquals(self.topology.add_service_unit("s-0", "u-12"), 1)
        self.topology.remove_service_unit("s-0", "u-05")
        self.topology.remove_service_unit("s-0", "u-12")
        self.assertEquals(self.topology.add_service_unit("s-0", "u-14"), 2)
        self.assertEquals(self.topology.add_service_unit("s-0", "u-17"), 3)

        self.assertEquals(
            self.topology.get_service_unit_sequence("s-0", "u-14"), 2)
        self.assertEquals(
            self.topology.get_service_unit_sequence("s-0", "u-17"), 3)
        self.assertRaises(
            InternalTopologyError,
            self.topology.get_service_unit_sequence, "s-0", "u-05")

    def test_find_service_unit_with_sequence(self):
        """
        Given a service name and a sequence number, the function
        find_service_unit_with_sequence() should return the unit_id,
        or None if the sequence number is not found.
        """
        self.topology.add_service("s-1", "mysql")
        self.topology.add_service_unit("s-1", "u-05")
        self.topology.add_service_unit("s-1", "u-12")
        self.assertEquals(
            self.topology.find_service_unit_with_sequence("s-1", 0),
            "u-05")
        self.assertEquals(
            self.topology.find_service_unit_with_sequence("s-1", 1),
            "u-12")
        self.assertEquals(
            self.topology.find_service_unit_with_sequence("s-1", 2),
            None)

    def test_find_service_unit_with_sequence_using_non_existing_service(self):
        """
        If the service_id provided to find_service_unit_with_sequence
        does not exist, an error should be raised.
        """
        self.assertRaises(
            InternalTopologyError,
            self.topology.find_service_unit_with_sequence, "s-0", 0)

    def test_assign_service_unit_to_machine(self):
        """
        Assigning a service unit to a machine should work.
        """
        self.topology.add_machine("m-0")
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service_unit("s-0", "u-0")
        self.topology.assign_service_unit_to_machine("s-0", "u-0", "m-0")
        machine_id = self.topology.get_service_unit_machine("s-0", "u-0")
        self.assertEquals(machine_id, "m-0")

    def test_assign_service_unit_machine_with_non_existing_service(self):
        """
        If the service_id provided when assigning a unit to a machine
        doesn't exist, an error must be raised.
        """
        self.topology.add_machine("m-0")
        self.assertRaises(InternalTopologyError,
                          self.topology.assign_service_unit_to_machine,
                          "s-0", "u-0", "m-0")

    def test_assign_service_unit_machine_with_non_existing_service_unit(self):
        """
        If the unit_id provided when assigning a unit to a machine
        doesn't exist, an error must be raised.
        """
        self.topology.add_machine("m-0")
        self.topology.add_service("s-0", "wordpress")
        self.assertRaises(InternalTopologyError,
                          self.topology.assign_service_unit_to_machine,
                          "s-0", "u-0", "m-0")

    def test_assign_service_unit_machine_with_non_existing_machine(self):
        """
        If the machine_id provided when assigning a unit to a machine
        doesn't exist, an error must be raised.
        """
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service_unit("s-0", "u-0")
        self.assertRaises(InternalTopologyError,
                          self.topology.assign_service_unit_to_machine,
                          "s-0", "u-0", "m-0")

    def test_assign_service_unit_machine_twice(self):
        """
        If the service unit was previously assigned to a machine_id,
        attempting to assign it again should raise an error, even if
        the machine_id is exactly the same.
        """
        self.topology.add_machine("m-0")
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service_unit("s-0", "u-0")
        self.topology.assign_service_unit_to_machine("s-0", "u-0", "m-0")
        self.assertRaises(InternalTopologyError,
                          self.topology.assign_service_unit_to_machine,
                          "s-0", "u-0", "m-0")

    def test_get_service_unit_machine(self):
        """
        get_service_unit_machine() should return the current
        machine the unit is assigned to, or None if it wasn't yet
        assigned to any machine.
        """
        self.topology.add_machine("m-0")
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service_unit("s-0", "u-0")
        self.assertEquals(
            self.topology.get_service_unit_machine("s-0", "u-0"),
            None)
        self.topology.assign_service_unit_to_machine("s-0", "u-0", "m-0")
        self.assertEquals(
            self.topology.get_service_unit_machine("s-0", "u-0"),
            "m-0")

    def test_get_service_unit_machine_with_non_existing_service(self):
        """
        If the service_id provided when attempting to retrieve
        a service unit's machine does not exist, an error must
        be raised.
        """
        self.assertRaises(
            InternalTopologyError,
            self.topology.get_service_unit_machine, "s-0", "u-0")

    def test_get_service_unit_machine_with_non_existing_service_unit(self):
        """
        If the unit_id provided when attempting to retrieve
        a service unit's machine does not exist, an error must
        be raised.
        """
        # Without any units:
        self.topology.add_service("s-0", "wordpress")
        self.assertRaises(
            InternalTopologyError,
            self.topology.get_service_unit_machine, "s-0", "u-0")

        # With a different unit in place:
        self.topology.add_service_unit("s-0", "u-0")
        self.assertRaises(
            InternalTopologyError,
            self.topology.get_service_unit_machine, "s-0", "u-1")

    def test_unassign_service_unit_from_machine(self):
        """
        It should be possible to unassign a service unit from a machine,
        as long as it has been previously assigned to some machine.
        """
        self.topology.add_machine("m-0")
        self.topology.add_machine("m-1")
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service_unit("s-0", "u-0")
        self.topology.add_service_unit("s-0", "u-1")
        self.topology.assign_service_unit_to_machine("s-0", "u-0", "m-0")
        self.topology.assign_service_unit_to_machine("s-0", "u-1", "m-1")
        self.topology.unassign_service_unit_from_machine("s-0", "u-0")
        self.assertEquals(
            self.topology.get_service_unit_machine("s-0", "u-0"),
            None)
        self.assertEquals(
            self.topology.get_service_unit_machine("s-0", "u-1"),
            "m-1")

    def test_unassign_service_unit_from_machine_when_not_assigned(self):
        """
        Can't unassign a unit from a machine if it wasn't previously
        assigned.
        """
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service_unit("s-0", "u-0")
        self.assertRaises(
            InternalTopologyError,
            self.topology.unassign_service_unit_from_machine, "s-0", "u-0")

    def test_unassign_service_unit_with_non_existing_service(self):
        """
        If the service_id used when attempting to unassign the
        service unit from a machine does not exist, an error must
        be raised.
        """
        self.assertRaises(
            InternalTopologyError,
            self.topology.unassign_service_unit_from_machine, "s-0", "u-0")

    def test_unassign_service_unit_with_non_existing_unit(self):
        """
        If the unit_id used when attempting to unassign the
        service unit from a machine does not exist, an error must
        be raised.
        """
        # Without any units:
        self.topology.add_service("s-0", "wordpress")
        self.assertRaises(
            InternalTopologyError,
            self.topology.unassign_service_unit_from_machine, "s-0", "u-0")

        # Without a different unit in place:
        self.topology.add_service_unit("s-0", "u-0")
        self.assertRaises(
            InternalTopologyError,
            self.topology.unassign_service_unit_from_machine, "s-0", "u-1")

    def test_get_service_units_in_machine(self):
        """
        We must be able to get all service units in a given machine
        as well.
        """
        self.topology.add_machine("m-0")
        self.topology.add_machine("m-1")

        # Shouldn't break before services are added.
        self.assertEquals(self.topology.get_service_units_in_machine("m-0"),
                          [])

        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service("s-1", "mysql")

        # Shouldn't break before units are added either.
        self.assertEquals(self.topology.get_service_units_in_machine("m-0"),
                          [])

        self.topology.add_service_unit("s-0", "u-0")
        self.topology.add_service_unit("s-0", "u-1")
        self.topology.add_service_unit("s-1", "u-2")
        self.topology.add_service_unit("s-1", "u-3")

        # Shouldn't break with units which aren't assigned.
        self.topology.add_service_unit("s-1", "u-4")

        self.topology.assign_service_unit_to_machine("s-0", "u-0", "m-0")
        self.topology.assign_service_unit_to_machine("s-0", "u-1", "m-1")
        self.topology.assign_service_unit_to_machine("s-1", "u-2", "m-1")
        self.topology.assign_service_unit_to_machine("s-1", "u-3", "m-0")

        unit_ids0 = self.topology.get_service_units_in_machine("m-0")
        unit_ids1 = self.topology.get_service_units_in_machine("m-1")

        self.assertEquals(sorted(unit_ids0), ["u-0", "u-3"])
        self.assertEquals(sorted(unit_ids1), ["u-1", "u-2"])

    def test_get_service_units_in_machine_with_non_existing_machine(self):
        """
        If the machine passed to get_service_units_in_machine() doesn't
        exist, it should bail out gracefully.
        """
        # Shouldn't break before services are added.
        self.assertRaises(InternalTopologyError,
                          self.topology.get_service_units_in_machine, "m-0")

    def test_dump_and_parse(self):
        """
        dump() and parse() are opposite operations which enable the
        state of a topology to be persisted as a string, and then
        loaded back.
        """
        empty_data = self.topology.dump()
        self.assertEquals(yaml.load(empty_data), {"version": VERSION})
        self.topology.add_machine("m-0")
        machine_data = self.topology.dump()
        self.topology.parse(empty_data)
        self.assertFalse(self.topology.has_machine("m-0"))
        self.topology.parse(machine_data)
        self.assertTrue(self.topology.has_machine("m-0"))

    def test_incompatible_version(self):
        """Verify `IncompatibleVersion` raised if using old topology."""
        empty_data = self.topology.dump()
        self.assertEquals(yaml.load(empty_data), {"version": VERSION})
        self.topology.add_machine("m-0")
        machine_data = self.topology.dump()
        self.topology.parse(machine_data)
        self.assertTrue(self.topology.has_machine("m-0"))

        # Pretend to bump the versioning by one
        actual_version = VERSION
        import juju
        self.patch(juju.state.topology, "VERSION", actual_version + 1)

        # With this change to juju.state.topology.VERSION, verify
        # topology ops will now raise an incompatibility exception
        ex = self.assertRaises(IncompatibleVersion,
                               self.topology.parse, machine_data)
        self.assertEqual(
            str(ex),
            "Incompatible juju protocol versions (found %d, want %d)" % (
                    actual_version, juju.state.topology.VERSION))

    def test_reset(self):
        """
        Resetting a topology should put it back in the state it
        was initialized with.
        """
        empty_data = self.topology.dump()
        self.topology.add_machine("m-0")
        self.topology.reset()
        self.assertEquals(self.topology.dump(), empty_data)
        self.assertEquals(self.topology._state["version"], VERSION)

    def test_has_relation(self):
        """Testing if a relation exists should be possible.
        """
        self.topology.add_service("s-0", "wordpress")
        self.assertFalse(self.topology.has_relation("r-1"))
        self.topology.add_relation("r-1", "type")
        self.assertTrue(self.topology.has_relation("r-1"))

    def test_add_relation(self):
        """Add a relation between the given service ids.
        """
        self.assertFalse(self.topology.has_relation("r-1"))

        # Verify add relation works correctly.
        self.topology.add_relation("r-1", "type")
        self.assertTrue(self.topology.has_relation("r-1"))

        # Attempting to add again raises an exception
        self.assertRaises(
            InternalTopologyError,
            self.topology.add_relation, "r-1", "type")

    def test_assign_service_to_relation(self):
        """A service can be associated to a relation.
        """
        # Both service and relation must be valid.
        self.assertRaises(InternalTopologyError,
                          self.topology.assign_service_to_relation,
                          "r-1",
                          "s-0",
                          "name",
                          "role")
        self.topology.add_relation("r-1", "type")
        self.assertRaises(InternalTopologyError,
                          self.topology.assign_service_to_relation,
                          "r-1",
                          "s-0",
                          "name",
                          "role")
        self.topology.add_service("s-0", "wordpress")

        # The relation can be assigned.
        self.assertFalse(self.topology.relation_has_service("r-1", "s-0"))
        self.topology.assign_service_to_relation("r-1", "s-0", "name", "role")
        self.assertEqual(self.topology.get_relations_for_service("s-0"),
                         [("r-1", "type", {"name": "name", "role": "role"})])

        # Adding it again raises an error, even with a different name/role
        self.assertRaises(InternalTopologyError,
                          self.topology.assign_service_to_relation,
                          "r-1",
                          "s-0",
                          "name2",
                          "role2")

        # Another service can't provide the same role within a relation.
        self.topology.add_service("s-1", "database")
        self.assertRaises(InternalTopologyError,
                          self.topology.assign_service_to_relation,
                          "r-1",
                          "s-1",
                          "name",
                          "role")

    def test_unassign_service_from_relation(self):
        """A service can be disassociated from a relation.
        """
        # Both service and relation must be valid.
        self.assertRaises(InternalTopologyError,
                          self.topology.unassign_service_from_relation,
                          "r-1",
                          "s-0")
        self.topology.add_relation("r-1", "type")
        self.assertRaises(InternalTopologyError,
                          self.topology.unassign_service_from_relation,
                          "r-1",
                          "s-0")
        self.topology.add_service("s-0", "wordpress")

        # If the service is not assigned to the relation, raises an error.
        self.assertRaises(InternalTopologyError,
                          self.topology.unassign_service_from_relation,
                          "r-1",
                          "s-0")

        self.topology.assign_service_to_relation("r-1", "s-0", "name", "role")
        self.assertEqual(self.topology.get_relations_for_service("s-0"),
                         [("r-1", "type", {"name": "name", "role": "role"})])

        self.topology.unassign_service_from_relation("r-1", "s-0")
        self.assertFalse(self.topology.get_relations_for_service("s-0"))

    def test_relation_has_service(self):
        """We can test to see if a service is associated to a relation."""
        self.assertFalse(self.topology.relation_has_service("r-1", "s-0"))
        self.topology.add_relation("r-1", "type")
        self.topology.add_service("s-0", "wordpress")
        self.topology.assign_service_to_relation("r-1", "s-0", "name", "role")
        self.assertTrue(self.topology.relation_has_service("r-1", "s-0"))

    def test_get_relation_service(self):
        """We can fetch the setting of a service within a relation."""
        # Invalid relations cause an exception.
        self.assertRaises(InternalTopologyError,
                          self.topology.get_relation_service,
                          "r-1",
                          "s-0")
        self.topology.add_relation("r-1", "rel-type")

        # Invalid services cause an exception.
        self.assertRaises(InternalTopologyError,
                          self.topology.get_relation_service,
                          "r-1",
                          "s-0")
        self.topology.add_service("s-0", "wordpress")

        # Fetching info for services not assigned to a relation cause an error.
        self.assertRaises(InternalTopologyError,
                          self.topology.get_relation_service,
                          "r-1",
                          "s-0")
        self.topology.assign_service_to_relation("r-1", "s-0", "name", "role")
        relation_type, info = self.topology.get_relation_service("r-1", "s-0")

        self.assertEqual(info["name"], "name")
        self.assertEqual(info["role"], "role")
        self.assertEqual(relation_type, "rel-type")

    def test_get_relation_type(self):
        """The type of a relation can be instrospected.
        """
        self.assertRaises(InternalTopologyError,
                          self.topology.get_relation_type,
                          "r-1")

        self.topology.add_relation("r-1", "rel-type")
        self.assertEqual(self.topology.get_relation_type("r-1"),
                         "rel-type")

    def test_get_relations(self):
        names = self.topology.get_relations()
        self.assertEqual(names, [])

        self.topology.add_relation("r-1", "type")
        names = self.topology.get_relations()
        self.assertEqual(names, ["r-1"])

        self.topology.add_relation("r-2", "type")
        names = self.topology.get_relations()
        self.assertEqual(set(names), set(["r-1", "r-2"]))

    def test_get_services_for_relations(self):
        """The services for a given relation can be retrieved."""
        self.topology.add_relation("r-1", "type")
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service("s-1", "database")
        self.topology.assign_service_to_relation("r-1", "s-0", "name", "role")
        self.topology.assign_service_to_relation("r-1", "s-1", "name", "role2")
        self.assertEqual(
            self.topology.get_relation_services("r-1"),
            {'s-1': {'role': 'role2', 'name': 'name'},
             's-0': {'role': 'role', 'name': 'name'}})

    def test_get_relations_for_service(self):
        """The relations for a given service can be retrieved.
        """
        # Getting relations for unknown service raises a topologyerror.
        self.assertRaises(InternalTopologyError,
                          self.topology.get_relations_for_service,
                          "s-0")

        # A new service has no relations.
        self.topology.add_service("s-0", "wordpress")
        self.assertFalse(self.topology.get_relations_for_service("s-0"))

        # Add a relation and fetch it.
        self.topology.add_relation("r-1", "type")
        self.topology.assign_service_to_relation("r-1", "s-0", "name", "role")
        self.topology.add_relation("r-2", "type")
        self.topology.assign_service_to_relation("r-2", "s-0", "name", "role")

        self.assertEqual(
            sorted(self.topology.get_relations_for_service("s-0")),
            [("r-1", "type", {"name": "name", "role": "role"}),
             ("r-2", "type", {"name": "name", "role": "role"})])

        self.topology.unassign_service_from_relation("r-2", "s-0")

    def test_remove_relation(self):
        """A relation can be removed.
        """
        # Attempting to remove unknown relation raises a topologyerror
        self.assertRaises(InternalTopologyError,
                          self.topology.remove_relation,
                          "r-1")

        # Adding a relation with associated service, and remove it.
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_relation("r-1", "type")
        self.topology.assign_service_to_relation("r-1", "s-0", "name", "role")
        self.assertTrue(self.topology.has_relation("r-1"))
        self.topology.remove_relation("r-1")
        self.assertFalse(self.topology.has_relation("r-1"))

    def test_remove_service_with_relations(self):
        """
        Attempting to remove a service that's assigned to relations
        raises an InternalTopologyError.
        """
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_relation("r-1", "type")
        self.topology.assign_service_to_relation("r-1", "s-0", "name", "role")

        self.assertRaises(InternalTopologyError,
                          self.topology.remove_service,
                          "s-0")

    def test_has_relation_between_dyadic_endpoints(self):
        mysql_ep = RelationEndpoint("mysqldb", "mysql", "db", "server")
        blog_ep = RelationEndpoint("wordpress", "mysql", "mysql", "client")
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service("s-1", "mysqldb")
        self.topology.add_relation("r-0", "mysql")
        self.topology.assign_service_to_relation(
            "r-0", "s-0", "mysql", "client")
        self.topology.assign_service_to_relation(
            "r-0", "s-1", "db", "server")
        self.assertTrue(self.topology.has_relation_between_endpoints([
            mysql_ep, blog_ep]))
        self.assertTrue(self.topology.has_relation_between_endpoints([
            blog_ep, mysql_ep]))

    def test_has_relation_between_dyadic_endpoints_missing_assignment(self):
        mysql_ep = RelationEndpoint("mysqldb", "mysql", "db", "server")
        blog_ep = RelationEndpoint("wordpress", "mysql", "mysql", "client")
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service("s-1", "mysqldb")
        self.topology.add_relation("r-0", "mysql")
        self.topology.assign_service_to_relation(
            "r-0", "s-1", "db", "server")
        self.assertFalse(self.topology.has_relation_between_endpoints([
            mysql_ep, blog_ep]))
        self.assertFalse(self.topology.has_relation_between_endpoints([
            blog_ep, mysql_ep]))

    def test_has_relation_between_dyadic_endpoints_wrong_relation_name(self):
        mysql_ep = RelationEndpoint("mysqldb", "mysql", "wrong-name", "server")
        blog_ep = RelationEndpoint("wordpress", "mysql", "mysql", "client")
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service("s-1", "mysqldb")
        self.topology.add_relation("r-0", "mysql")
        self.topology.assign_service_to_relation(
            "r-0", "s-0", "mysql", "client")
        self.topology.assign_service_to_relation(
            "r-0", "s-1", "db", "server")
        self.assertFalse(self.topology.has_relation_between_endpoints([
            mysql_ep, blog_ep]))
        self.assertFalse(self.topology.has_relation_between_endpoints([
            blog_ep, mysql_ep]))

    def test_has_relation_between_monadic_endpoints(self):
        riak_ep = RelationEndpoint("riak", "riak", "riak", "peer")
        self.topology.add_service("s-0", "riak")
        self.topology.add_relation("r-0", "riak")
        self.topology.assign_service_to_relation("r-0", "s-0", "riak", "peer")
        self.assertTrue(self.topology.has_relation_between_endpoints(
            [riak_ep]))

    def test_get_relation_between_dyadic_endpoints(self):
        mysql_ep = RelationEndpoint("mysqldb", "mysql", "db", "server")
        blog_ep = RelationEndpoint("wordpress", "mysql", "mysql", "client")
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service("s-1", "mysqldb")
        self.topology.add_relation("r-0", "mysql")
        self.topology.assign_service_to_relation(
            "r-0", "s-0", "mysql", "client")
        self.topology.assign_service_to_relation(
            "r-0", "s-1", "db", "server")
        self.assertEqual(self.topology.get_relation_between_endpoints([
            mysql_ep, blog_ep]), "r-0")
        self.assertEqual(self.topology.get_relation_between_endpoints([
            blog_ep, mysql_ep]), "r-0")

    def test_get_relation_between_dyadic_endpoints_missing_assignment(self):
        mysql_ep = RelationEndpoint("mysqldb", "mysql", "db", "server")
        blog_ep = RelationEndpoint("wordpress", "mysql", "mysql", "client")
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service("s-1", "mysqldb")
        self.topology.add_relation("r-0", "mysql")
        self.topology.assign_service_to_relation(
            "r-0", "s-1", "db", "server")
        self.assertEqual(self.topology.get_relation_between_endpoints([
            mysql_ep, blog_ep]), None)
        self.assertEqual(self.topology.get_relation_between_endpoints([
            blog_ep, mysql_ep]), None)

    def test_get_relation_between_dyadic_endpoints_wrong_relation_name(self):
        mysql_ep = RelationEndpoint("mysqldb", "mysql", "wrong-name", "server")
        blog_ep = RelationEndpoint("wordpress", "mysql", "mysql", "client")
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service("s-1", "mysqldb")
        self.topology.add_relation("r-0", "mysql")
        self.topology.assign_service_to_relation(
            "r-0", "s-0", "mysql", "client")
        self.topology.assign_service_to_relation(
            "r-0", "s-1", "db", "server")
        self.assertEqual(self.topology.get_relation_between_endpoints([
            mysql_ep, blog_ep]), None)
        self.assertEqual(self.topology.get_relation_between_endpoints([
            blog_ep, mysql_ep]), None)

    def test_get_relation_between_monadic_endpoints(self):
        riak_ep = RelationEndpoint("riak", "riak", "riak", "peer")
        self.topology.add_service("s-0", "riak")
        self.topology.add_relation("r-0", "riak")
        self.topology.assign_service_to_relation("r-0", "s-0", "riak", "peer")
        self.assertEqual(self.topology.get_relation_between_endpoints(
            [riak_ep]), "r-0")
Beispiel #28
0
class InternalTopologyMapTest(TestCase):
    def setUp(self):
        self.topology = InternalTopology()

    def test_add_machine(self):
        """
        The topology map is stored as YAML at the moment, so it
        should be able to read it.
        """
        self.topology.add_machine("m-0")
        self.topology.add_machine("m-1")
        self.assertEquals(sorted(self.topology.get_machines()), ["m-0", "m-1"])

    def test_add_duplicated_machine(self):
        """
        Adding a machine which is already registered should fail.
        """
        self.topology.add_machine("m-0")
        self.assertRaises(InternalTopologyError, self.topology.add_machine,
                          "m-0")

    def test_has_machine(self):
        """
        Testing if a machine is registered should be possible.
        """
        self.assertFalse(self.topology.has_machine("m-0"))
        self.topology.add_machine("m-0")
        self.assertTrue(self.topology.has_machine("m-0"))
        self.assertFalse(self.topology.has_machine("m-1"))

    def test_get_machines(self):
        """
        get_machines() must return a list of machine ids
        previously registered.
        """
        self.assertEquals(self.topology.get_machines(), [])
        self.topology.add_machine("m-0")
        self.topology.add_machine("m-1")
        self.assertEquals(sorted(self.topology.get_machines()), ["m-0", "m-1"])

    def test_remove_machine(self):
        """
        Removing machines should take them off the state.
        """
        self.topology.add_machine("m-0")
        self.topology.add_machine("m-1")

        # Add a non-assigned unit, to test that the logic of
        # checking for assigned units validates this.
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service_unit("s-0", "u-0")

        self.topology.remove_machine("m-0")
        self.assertFalse(self.topology.has_machine("m-0"))
        self.assertTrue(self.topology.has_machine("m-1"))

    def test_remove_non_existent_machine(self):
        """
        Removing non-existing machines should raise an error.
        """
        self.assertRaises(InternalTopologyError, self.topology.remove_machine,
                          "m-0")

    def test_remove_machine_with_assigned_units(self):
        """
        A machine can't be removed when it has assigned units.
        """
        self.topology.add_machine("m-0")
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service_unit("s-0", "u-0")
        self.topology.add_service_unit("s-0", "u-1")
        self.topology.assign_service_unit_to_machine("s-0", "u-1", "m-0")
        self.assertRaises(InternalTopologyError, self.topology.remove_machine,
                          "m-0")

    def test_machine_has_units(self):
        """Test various ways a machine might or might not be assigned."""
        self.topology.add_machine("m-0")
        self.topology.add_machine("m-1")
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service_unit("s-0", "u-0")
        self.topology.add_service_unit("s-0", "u-1")
        self.topology.assign_service_unit_to_machine("s-0", "u-1", "m-0")
        self.assertTrue(self.topology.machine_has_units("m-0"))
        self.assertFalse(self.topology.machine_has_units("m-1"))
        self.assertRaises(InternalTopologyError,
                          self.topology.machine_has_units, "m-nonesuch")

    def test_add_service(self):
        """
        The topology map is stored as YAML at the moment, so it
        should be able to read it.
        """
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service("s-1", "mysql")
        self.assertEquals(sorted(self.topology.get_services()), ["s-0", "s-1"])

    def test_add_duplicated_service(self):
        """
        Adding a service which is already registered should fail.
        """
        self.topology.add_service("s-0", "wordpress")
        self.assertRaises(InternalTopologyError, self.topology.add_service,
                          "s-0", "wp")

    def test_add_services_with_duplicated_names(self):
        """
        Adding a service which is already registered should fail.
        """
        self.topology.add_service("s-0", "wordpress")
        self.assertRaises(InternalTopologyError, self.topology.add_service,
                          "s-1", "wordpress")

    def test_has_service(self):
        """
        Testing if a service is registered should be possible.
        """
        self.assertFalse(self.topology.has_service("s-0"))
        self.topology.add_service("s-0", "wordpress")
        self.assertTrue(self.topology.has_service("s-0"))
        self.assertFalse(self.topology.has_service("s-1"))

    def test_find_service_with_name(self):
        """
        find_service_with_name() must return the service_id for
        the service with the given name, or None if no service
        is found with that name.
        """
        self.assertEquals(self.topology.find_service_with_name("wordpress"),
                          None)
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service("s-1", "mysql")
        self.assertEquals(self.topology.find_service_with_name("wordpress"),
                          "s-0")
        self.assertEquals(self.topology.find_service_with_name("mysql"), "s-1")

    def test_get_service_name(self):
        """
        get_service_name() should return the service name for the
        given service_id.
        """
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service("s-1", "mysql")
        self.assertEquals(self.topology.get_service_name("s-0"), "wordpress")
        self.assertEquals(self.topology.get_service_name("s-1"), "mysql")

    def test_get_service_name_with_non_existing_service(self):
        """
        get_service_name() should raise an error if the service
        does not exist.
        """
        # Without any state:
        self.assertRaises(InternalTopologyError,
                          self.topology.get_service_name, "s-0")

        self.topology.add_service("s-0", "wordpress")

        # With some state:
        self.assertRaises(InternalTopologyError,
                          self.topology.get_service_name, "s-1")

    def test_get_services(self):
        """
        Retrieving a list of available services must be possible.
        """
        self.assertEquals(self.topology.get_services(), [])
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service("s-1", "mysql")
        self.assertEquals(sorted(self.topology.get_services()), ["s-0", "s-1"])

    def test_remove_service(self):
        """
        Removing a service should work properly, so that the service
        isn't available anymore after it happens (duh!).
        """
        self.topology.add_service("m-0", "wordpress")
        self.topology.add_service("m-1", "mysql")
        self.topology.remove_service("m-0")
        self.assertFalse(self.topology.has_service("m-0"))
        self.assertTrue(self.topology.has_service("m-1"))

    def test_remove_non_existent_service(self):
        """
        Attempting to remove a non-existing service should be an
        error.
        """
        self.assertRaises(InternalTopologyError, self.topology.remove_service,
                          "m-0")

    def test_add_service_unit(self):
        """
        add_service_unit() should register a new service unit for a
        given service, and should return a sequence number for the
        unit.  The sequence number increases monotonically for each
        service, and is helpful to provide nice unit names.
        """
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service("s-1", "mysql")
        self.assertEquals(self.topology.add_service_unit("s-0", "u-05"), 0)
        self.assertEquals(self.topology.add_service_unit("s-0", "u-12"), 1)
        self.assertEquals(self.topology.add_service_unit("s-1", "u-07"), 0)
        self.assertEquals(sorted(self.topology.get_service_units("s-0")),
                          ["u-05", "u-12"])
        self.assertEquals(self.topology.get_service_units("s-1"), ["u-07"])

    def test_global_unique_service_names(self):
        """Service unit names are unique.

        Even if the underlying service is destroyed and a new
        service with the same name is created, we'll never
        get a duplicate service unit name.
        """
        self.topology.add_service("s-0", "wordpress")
        sequence = self.topology.add_service_unit("s-0", "u-0")
        self.assertEqual(sequence, 0)
        sequence = self.topology.add_service_unit("s-0", "u-1")
        self.assertEqual(sequence, 1)
        self.topology.remove_service("s-0")
        self.topology.add_service("s-0", "wordpress")
        sequence = self.topology.add_service_unit("s-0", "u-1")
        self.assertEqual(sequence, 2)
        self.assertEqual(self.topology.get_service_unit_name("s-0", "u-1"),
                         "wordpress/2")

    def test_add_duplicated_service_unit(self):
        """
        Adding the same unit to the same service must not be
        possible.
        """
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service_unit("s-0", "u-0")
        self.assertRaises(InternalTopologyError,
                          self.topology.add_service_unit, "s-0", "u-0")

    def test_add_service_unit_to_non_existing_service(self):
        """
        Adding a service unit requires the service to have been
        previously created.
        """
        self.assertRaises(InternalTopologyError,
                          self.topology.add_service_unit, "s-0", "u-0")

    def test_add_service_unit_to_different_service(self):
        """
        Adding the same unit to two different services must not
        be possible.
        """
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service("s-1", "mysql")
        self.topology.add_service_unit("s-0", "u-0")
        self.assertRaises(InternalTopologyError,
                          self.topology.add_service_unit, "s-1", "u-0")

    def test_get_service_units(self):
        """
        Getting units registered from a service should return a
        list of these.
        """
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service("s-1", "mysql")
        self.assertEquals(self.topology.get_service_units("s-0"), [])
        self.topology.add_service_unit("s-0", "u-0")
        self.topology.add_service_unit("s-0", "u-1")
        self.topology.add_service_unit("s-1", "u-2")
        self.assertEquals(sorted(self.topology.get_service_units("s-0")),
                          ["u-0", "u-1"])
        self.assertEquals(sorted(self.topology.get_service_units("s-1")),
                          ["u-2"])

    def test_get_service_units_with_non_existing_service(self):
        """
        Getting service units from a non-existing service should
        raise an error.
        """
        self.assertRaises(InternalTopologyError,
                          self.topology.get_service_units, "s-0")

    def test_has_service_units(self):
        """
        Testing if a service unit exists in a service should be
        possible.
        """
        self.topology.add_service("s-0", "wordpress")
        self.assertFalse(self.topology.has_service_unit("s-0", "u-0"))
        self.topology.add_service_unit("s-0", "u-0")
        self.assertTrue(self.topology.has_service_unit("s-0", "u-0"))
        self.assertFalse(self.topology.has_service_unit("s-0", "u-1"))

    def test_has_service_units_with_non_existing_service(self):
        """
        Testing if a service unit exists should only work if a
        sensible service was provided.
        """
        self.assertRaises(InternalTopologyError,
                          self.topology.has_service_unit, "s-1", "u-0")

    def test_get_service_unit_service(self):
        """
        The reverse operation is also feasible: given a service unit,
        return the service id for the service containing the unit.
        """
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service("s-1", "mysql")
        self.topology.add_service_unit("s-0", "u-0")
        self.topology.add_service_unit("s-0", "u-1")
        self.topology.add_service_unit("s-1", "u-2")
        self.assertEquals(self.topology.get_service_unit_service("u-0"), "s-0")
        self.assertEquals(self.topology.get_service_unit_service("u-1"), "s-0")
        self.assertEquals(self.topology.get_service_unit_service("u-2"), "s-1")

    def test_get_unit_service_with_non_existing_unit(self):
        """
        If the unit provided to get_service_unit_service() doesn't exist,
        raise an error.
        """
        # Without any services.
        self.assertRaises(InternalTopologyError,
                          self.topology.get_service_unit_service, "u-1")

        # With a service without units.
        self.topology.add_service("s-0", "wordpress")
        self.assertRaises(InternalTopologyError,
                          self.topology.get_service_unit_service, "u-1")

        # With a service with a different unit.
        self.topology.add_service_unit("s-0", "u-0")
        self.assertRaises(InternalTopologyError,
                          self.topology.get_service_unit_service, "u-1")

    def test_get_service_unit_name(self):
        """
        Service units are named with the service name and the sequence
        number joined by a slash, such as wordpress/3.  This makes it
        convenient to use from higher layers.
        """
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service("s-1", "mysql")
        self.topology.add_service_unit("s-0", "u-0")
        self.topology.add_service_unit("s-0", "u-1")
        self.topology.add_service_unit("s-1", "u-2")
        self.assertEquals(self.topology.get_service_unit_name("s-0", "u-0"),
                          "wordpress/0")
        self.assertEquals(self.topology.get_service_unit_name("s-0", "u-1"),
                          "wordpress/1")
        self.assertEquals(self.topology.get_service_unit_name("s-1", "u-2"),
                          "mysql/0")

    def test_get_service_unit_name_from_id(self):
        """
        Service units are named with the service name and the sequence
        number joined by a slash, such as wordpress/3.  This makes it
        convenient to use from higher layers. Those layers ocassionally
        need to resolve the id to a name. This is mostly a simple convience
        wrapper around get_service_unit_name
        """
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service_unit("s-0", "u-0")
        self.topology.add_service_unit("s-0", "u-1")

        self.assertEqual(self.topology.get_service_unit_name_from_id("u-0"),
                         "wordpress/0")
        self.assertEqual(self.topology.get_service_unit_name_from_id("u-1"),
                         "wordpress/1")
        self.assertRaises(InternalTopologyError,
                          self.topology.get_service_unit_name_from_id, "u-2")

    def test_get_unit_service_id_from_name(self):
        """Retrieve the unit id from the user oriented unit name."""
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service_unit("s-0", "u-0")
        self.topology.add_service_unit("s-0", "u-1")

        self.assertEqual(
            "u-0", self.topology.get_service_unit_id_from_name("wordpress/0"))

        self.assertEqual(
            "u-1", self.topology.get_service_unit_id_from_name("wordpress/1"))

    def test_get_unit_service_with_non_existing_service_or_unit(self):
        """
        If the unit provided to get_service_unit_service() doesn't exist,
        raise an error.
        """
        # Without any services.
        self.assertRaises(InternalTopologyError,
                          self.topology.get_service_unit_name, "s-0", "u-1")

        # With a service without units.
        self.topology.add_service("s-0", "wordpress")
        self.assertRaises(InternalTopologyError,
                          self.topology.get_service_unit_name, "s-0", "u-1")

        # With a service with a different unit.
        self.topology.add_service_unit("s-0", "u-0")
        self.assertRaises(InternalTopologyError,
                          self.topology.get_service_unit_name, "s-0", "u-1")

    def test_remove_service_unit(self):
        """
        It should be possible to remove a service unit from an
        existing service.
        """
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service_unit("s-0", "m-0")
        self.topology.add_service_unit("s-0", "m-1")
        self.topology.remove_service_unit("s-0", "m-0")
        self.assertFalse(self.topology.has_service_unit("s-0", "m-0"))
        self.assertTrue(self.topology.has_service_unit("s-0", "m-1"))

    def test_remove_non_existent_service_unit(self):
        """
        Attempting to remove a non-existing service unit or a unit
        in a non-existing service should raise a local error.
        """
        self.assertRaises(InternalTopologyError,
                          self.topology.remove_service_unit, "s-0", "m-0")
        self.topology.add_service("s-0", "wordpress")
        self.assertRaises(InternalTopologyError,
                          self.topology.remove_service_unit, "s-0", "m-0")

    def test_service_unit_sequencing(self):
        """
        Even if service units are unregistered, the sequence number
        should not be reused.
        """
        self.topology.add_service("s-0", "wordpress")
        self.assertEquals(self.topology.add_service_unit("s-0", "u-05"), 0)
        self.assertEquals(self.topology.add_service_unit("s-0", "u-12"), 1)
        self.topology.remove_service_unit("s-0", "u-05")
        self.topology.remove_service_unit("s-0", "u-12")
        self.assertEquals(self.topology.add_service_unit("s-0", "u-14"), 2)
        self.assertEquals(self.topology.add_service_unit("s-0", "u-17"), 3)

        self.assertEquals(
            self.topology.get_service_unit_sequence("s-0", "u-14"), 2)
        self.assertEquals(
            self.topology.get_service_unit_sequence("s-0", "u-17"), 3)
        self.assertRaises(InternalTopologyError,
                          self.topology.get_service_unit_sequence, "s-0",
                          "u-05")

    def test_find_service_unit_with_sequence(self):
        """
        Given a service name and a sequence number, the function
        find_service_unit_with_sequence() should return the unit_id,
        or None if the sequence number is not found.
        """
        self.topology.add_service("s-1", "mysql")
        self.topology.add_service_unit("s-1", "u-05")
        self.topology.add_service_unit("s-1", "u-12")
        self.assertEquals(
            self.topology.find_service_unit_with_sequence("s-1", 0), "u-05")
        self.assertEquals(
            self.topology.find_service_unit_with_sequence("s-1", 1), "u-12")
        self.assertEquals(
            self.topology.find_service_unit_with_sequence("s-1", 2), None)

    def test_find_service_unit_with_sequence_using_non_existing_service(self):
        """
        If the service_id provided to find_service_unit_with_sequence
        does not exist, an error should be raised.
        """
        self.assertRaises(InternalTopologyError,
                          self.topology.find_service_unit_with_sequence, "s-0",
                          0)

    def test_assign_service_unit_to_machine(self):
        """
        Assigning a service unit to a machine should work.
        """
        self.topology.add_machine("m-0")
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service_unit("s-0", "u-0")
        self.topology.assign_service_unit_to_machine("s-0", "u-0", "m-0")
        machine_id = self.topology.get_service_unit_machine("s-0", "u-0")
        self.assertEquals(machine_id, "m-0")

    def test_assign_service_unit_machine_with_non_existing_service(self):
        """
        If the service_id provided when assigning a unit to a machine
        doesn't exist, an error must be raised.
        """
        self.topology.add_machine("m-0")
        self.assertRaises(InternalTopologyError,
                          self.topology.assign_service_unit_to_machine, "s-0",
                          "u-0", "m-0")

    def test_assign_service_unit_machine_with_non_existing_service_unit(self):
        """
        If the unit_id provided when assigning a unit to a machine
        doesn't exist, an error must be raised.
        """
        self.topology.add_machine("m-0")
        self.topology.add_service("s-0", "wordpress")
        self.assertRaises(InternalTopologyError,
                          self.topology.assign_service_unit_to_machine, "s-0",
                          "u-0", "m-0")

    def test_assign_service_unit_machine_with_non_existing_machine(self):
        """
        If the machine_id provided when assigning a unit to a machine
        doesn't exist, an error must be raised.
        """
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service_unit("s-0", "u-0")
        self.assertRaises(InternalTopologyError,
                          self.topology.assign_service_unit_to_machine, "s-0",
                          "u-0", "m-0")

    def test_assign_service_unit_machine_twice(self):
        """
        If the service unit was previously assigned to a machine_id,
        attempting to assign it again should raise an error, even if
        the machine_id is exactly the same.
        """
        self.topology.add_machine("m-0")
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service_unit("s-0", "u-0")
        self.topology.assign_service_unit_to_machine("s-0", "u-0", "m-0")
        self.assertRaises(InternalTopologyError,
                          self.topology.assign_service_unit_to_machine, "s-0",
                          "u-0", "m-0")

    def test_get_service_unit_machine(self):
        """
        get_service_unit_machine() should return the current
        machine the unit is assigned to, or None if it wasn't yet
        assigned to any machine.
        """
        self.topology.add_machine("m-0")
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service_unit("s-0", "u-0")
        self.assertEquals(self.topology.get_service_unit_machine("s-0", "u-0"),
                          None)
        self.topology.assign_service_unit_to_machine("s-0", "u-0", "m-0")
        self.assertEquals(self.topology.get_service_unit_machine("s-0", "u-0"),
                          "m-0")

    def test_get_service_unit_machine_with_non_existing_service(self):
        """
        If the service_id provided when attempting to retrieve
        a service unit's machine does not exist, an error must
        be raised.
        """
        self.assertRaises(InternalTopologyError,
                          self.topology.get_service_unit_machine, "s-0", "u-0")

    def test_get_service_unit_machine_with_non_existing_service_unit(self):
        """
        If the unit_id provided when attempting to retrieve
        a service unit's machine does not exist, an error must
        be raised.
        """
        # Without any units:
        self.topology.add_service("s-0", "wordpress")
        self.assertRaises(InternalTopologyError,
                          self.topology.get_service_unit_machine, "s-0", "u-0")

        # With a different unit in place:
        self.topology.add_service_unit("s-0", "u-0")
        self.assertRaises(InternalTopologyError,
                          self.topology.get_service_unit_machine, "s-0", "u-1")

    def test_unassign_service_unit_from_machine(self):
        """
        It should be possible to unassign a service unit from a machine,
        as long as it has been previously assigned to some machine.
        """
        self.topology.add_machine("m-0")
        self.topology.add_machine("m-1")
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service_unit("s-0", "u-0")
        self.topology.add_service_unit("s-0", "u-1")
        self.topology.assign_service_unit_to_machine("s-0", "u-0", "m-0")
        self.topology.assign_service_unit_to_machine("s-0", "u-1", "m-1")
        self.topology.unassign_service_unit_from_machine("s-0", "u-0")
        self.assertEquals(self.topology.get_service_unit_machine("s-0", "u-0"),
                          None)
        self.assertEquals(self.topology.get_service_unit_machine("s-0", "u-1"),
                          "m-1")

    def test_unassign_service_unit_from_machine_when_not_assigned(self):
        """
        Can't unassign a unit from a machine if it wasn't previously
        assigned.
        """
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service_unit("s-0", "u-0")
        self.assertRaises(InternalTopologyError,
                          self.topology.unassign_service_unit_from_machine,
                          "s-0", "u-0")

    def test_unassign_service_unit_with_non_existing_service(self):
        """
        If the service_id used when attempting to unassign the
        service unit from a machine does not exist, an error must
        be raised.
        """
        self.assertRaises(InternalTopologyError,
                          self.topology.unassign_service_unit_from_machine,
                          "s-0", "u-0")

    def test_unassign_service_unit_with_non_existing_unit(self):
        """
        If the unit_id used when attempting to unassign the
        service unit from a machine does not exist, an error must
        be raised.
        """
        # Without any units:
        self.topology.add_service("s-0", "wordpress")
        self.assertRaises(InternalTopologyError,
                          self.topology.unassign_service_unit_from_machine,
                          "s-0", "u-0")

        # Without a different unit in place:
        self.topology.add_service_unit("s-0", "u-0")
        self.assertRaises(InternalTopologyError,
                          self.topology.unassign_service_unit_from_machine,
                          "s-0", "u-1")

    def test_get_service_units_in_machine(self):
        """
        We must be able to get all service units in a given machine
        as well.
        """
        self.topology.add_machine("m-0")
        self.topology.add_machine("m-1")

        # Shouldn't break before services are added.
        self.assertEquals(self.topology.get_service_units_in_machine("m-0"),
                          [])

        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service("s-1", "mysql")

        # Shouldn't break before units are added either.
        self.assertEquals(self.topology.get_service_units_in_machine("m-0"),
                          [])

        self.topology.add_service_unit("s-0", "u-0")
        self.topology.add_service_unit("s-0", "u-1")
        self.topology.add_service_unit("s-1", "u-2")
        self.topology.add_service_unit("s-1", "u-3")

        # Shouldn't break with units which aren't assigned.
        self.topology.add_service_unit("s-1", "u-4")

        self.topology.assign_service_unit_to_machine("s-0", "u-0", "m-0")
        self.topology.assign_service_unit_to_machine("s-0", "u-1", "m-1")
        self.topology.assign_service_unit_to_machine("s-1", "u-2", "m-1")
        self.topology.assign_service_unit_to_machine("s-1", "u-3", "m-0")

        unit_ids0 = self.topology.get_service_units_in_machine("m-0")
        unit_ids1 = self.topology.get_service_units_in_machine("m-1")

        self.assertEquals(sorted(unit_ids0), ["u-0", "u-3"])
        self.assertEquals(sorted(unit_ids1), ["u-1", "u-2"])

    def test_get_service_units_in_machine_with_non_existing_machine(self):
        """
        If the machine passed to get_service_units_in_machine() doesn't
        exist, it should bail out gracefully.
        """
        # Shouldn't break before services are added.
        self.assertRaises(InternalTopologyError,
                          self.topology.get_service_units_in_machine, "m-0")

    def test_dump_and_parse(self):
        """
        dump() and parse() are opposite operations which enable the
        state of a topology to be persisted as a string, and then
        loaded back.
        """
        empty_data = self.topology.dump()
        self.assertEquals(yaml.load(empty_data), {"version": VERSION})
        self.topology.add_machine("m-0")
        machine_data = self.topology.dump()
        self.topology.parse(empty_data)
        self.assertFalse(self.topology.has_machine("m-0"))
        self.topology.parse(machine_data)
        self.assertTrue(self.topology.has_machine("m-0"))

    def test_incompatible_version(self):
        """Verify `IncompatibleVersion` raised if using old topology."""
        empty_data = self.topology.dump()
        self.assertEquals(yaml.load(empty_data), {"version": VERSION})
        self.topology.add_machine("m-0")
        machine_data = self.topology.dump()
        self.topology.parse(machine_data)
        self.assertTrue(self.topology.has_machine("m-0"))

        # Pretend to bump the versioning by one
        actual_version = VERSION
        import juju
        self.patch(juju.state.topology, "VERSION", actual_version + 1)

        # With this change to juju.state.topology.VERSION, verify
        # topology ops will now raise an incompatibility exception
        ex = self.assertRaises(IncompatibleVersion, self.topology.parse,
                               machine_data)
        self.assertEqual(
            str(ex),
            "Incompatible juju protocol versions (found %d, want %d)" %
            (actual_version, juju.state.topology.VERSION))

    def test_reset(self):
        """
        Resetting a topology should put it back in the state it
        was initialized with.
        """
        empty_data = self.topology.dump()
        self.topology.add_machine("m-0")
        self.topology.reset()
        self.assertEquals(self.topology.dump(), empty_data)
        self.assertEquals(self.topology._state["version"], VERSION)

    def test_has_relation(self):
        """Testing if a relation exists should be possible.
        """
        self.topology.add_service("s-0", "wordpress")
        self.assertFalse(self.topology.has_relation("r-1"))
        self.topology.add_relation("r-1", "type")
        self.assertTrue(self.topology.has_relation("r-1"))

    def test_add_relation(self):
        """Add a relation between the given service ids.
        """
        self.assertFalse(self.topology.has_relation("r-1"))

        # Verify add relation works correctly.
        self.topology.add_relation("r-1", "type")
        self.assertTrue(self.topology.has_relation("r-1"))

        # Attempting to add again raises an exception
        self.assertRaises(InternalTopologyError, self.topology.add_relation,
                          "r-1", "type")

    def test_assign_service_to_relation(self):
        """A service can be associated to a relation.
        """
        # Both service and relation must be valid.
        self.assertRaises(InternalTopologyError,
                          self.topology.assign_service_to_relation, "r-1",
                          "s-0", "name", "role")
        self.topology.add_relation("r-1", "type")
        self.assertRaises(InternalTopologyError,
                          self.topology.assign_service_to_relation, "r-1",
                          "s-0", "name", "role")
        self.topology.add_service("s-0", "wordpress")

        # The relation can be assigned.
        self.assertFalse(self.topology.relation_has_service("r-1", "s-0"))
        self.topology.assign_service_to_relation("r-1", "s-0", "name", "role")
        self.assertEqual(self.topology.get_relations_for_service("s-0"),
                         [("r-1", "type", {
                             "name": "name",
                             "role": "role"
                         })])

        # Adding it again raises an error, even with a different name/role
        self.assertRaises(InternalTopologyError,
                          self.topology.assign_service_to_relation, "r-1",
                          "s-0", "name2", "role2")

        # Another service can't provide the same role within a relation.
        self.topology.add_service("s-1", "database")
        self.assertRaises(InternalTopologyError,
                          self.topology.assign_service_to_relation, "r-1",
                          "s-1", "name", "role")

    def test_unassign_service_from_relation(self):
        """A service can be disassociated from a relation.
        """
        # Both service and relation must be valid.
        self.assertRaises(InternalTopologyError,
                          self.topology.unassign_service_from_relation, "r-1",
                          "s-0")
        self.topology.add_relation("r-1", "type")
        self.assertRaises(InternalTopologyError,
                          self.topology.unassign_service_from_relation, "r-1",
                          "s-0")
        self.topology.add_service("s-0", "wordpress")

        # If the service is not assigned to the relation, raises an error.
        self.assertRaises(InternalTopologyError,
                          self.topology.unassign_service_from_relation, "r-1",
                          "s-0")

        self.topology.assign_service_to_relation("r-1", "s-0", "name", "role")
        self.assertEqual(self.topology.get_relations_for_service("s-0"),
                         [("r-1", "type", {
                             "name": "name",
                             "role": "role"
                         })])

        self.topology.unassign_service_from_relation("r-1", "s-0")
        self.assertFalse(self.topology.get_relations_for_service("s-0"))

    def test_relation_has_service(self):
        """We can test to see if a service is associated to a relation."""
        self.assertFalse(self.topology.relation_has_service("r-1", "s-0"))
        self.topology.add_relation("r-1", "type")
        self.topology.add_service("s-0", "wordpress")
        self.topology.assign_service_to_relation("r-1", "s-0", "name", "role")
        self.assertTrue(self.topology.relation_has_service("r-1", "s-0"))

    def test_get_relation_service(self):
        """We can fetch the setting of a service within a relation."""
        # Invalid relations cause an exception.
        self.assertRaises(InternalTopologyError,
                          self.topology.get_relation_service, "r-1", "s-0")
        self.topology.add_relation("r-1", "rel-type")

        # Invalid services cause an exception.
        self.assertRaises(InternalTopologyError,
                          self.topology.get_relation_service, "r-1", "s-0")
        self.topology.add_service("s-0", "wordpress")

        # Fetching info for services not assigned to a relation cause an error.
        self.assertRaises(InternalTopologyError,
                          self.topology.get_relation_service, "r-1", "s-0")
        self.topology.assign_service_to_relation("r-1", "s-0", "name", "role")
        relation_type, info = self.topology.get_relation_service("r-1", "s-0")

        self.assertEqual(info["name"], "name")
        self.assertEqual(info["role"], "role")
        self.assertEqual(relation_type, "rel-type")

    def test_get_relation_type(self):
        """The type of a relation can be instrospected.
        """
        self.assertRaises(InternalTopologyError,
                          self.topology.get_relation_type, "r-1")

        self.topology.add_relation("r-1", "rel-type")
        self.assertEqual(self.topology.get_relation_type("r-1"), "rel-type")

    def test_get_relations(self):
        names = self.topology.get_relations()
        self.assertEqual(names, [])

        self.topology.add_relation("r-1", "type")
        names = self.topology.get_relations()
        self.assertEqual(names, ["r-1"])

        self.topology.add_relation("r-2", "type")
        names = self.topology.get_relations()
        self.assertEqual(set(names), set(["r-1", "r-2"]))

    def test_get_services_for_relations(self):
        """The services for a given relation can be retrieved."""
        self.topology.add_relation("r-1", "type")
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service("s-1", "database")
        self.topology.assign_service_to_relation("r-1", "s-0", "name", "role")
        self.topology.assign_service_to_relation("r-1", "s-1", "name", "role2")
        self.assertEqual(
            self.topology.get_relation_services("r-1"), {
                's-1': {
                    'role': 'role2',
                    'name': 'name'
                },
                's-0': {
                    'role': 'role',
                    'name': 'name'
                }
            })

    def test_get_relations_for_service(self):
        """The relations for a given service can be retrieved.
        """
        # Getting relations for unknown service raises a topologyerror.
        self.assertRaises(InternalTopologyError,
                          self.topology.get_relations_for_service, "s-0")

        # A new service has no relations.
        self.topology.add_service("s-0", "wordpress")
        self.assertFalse(self.topology.get_relations_for_service("s-0"))

        # Add a relation and fetch it.
        self.topology.add_relation("r-1", "type")
        self.topology.assign_service_to_relation("r-1", "s-0", "name", "role")
        self.topology.add_relation("r-2", "type")
        self.topology.assign_service_to_relation("r-2", "s-0", "name", "role")

        self.assertEqual(
            sorted(self.topology.get_relations_for_service("s-0")),
            [("r-1", "type", {
                "name": "name",
                "role": "role"
            }), ("r-2", "type", {
                "name": "name",
                "role": "role"
            })])

        self.topology.unassign_service_from_relation("r-2", "s-0")

    def test_remove_relation(self):
        """A relation can be removed.
        """
        # Attempting to remove unknown relation raises a topologyerror
        self.assertRaises(InternalTopologyError, self.topology.remove_relation,
                          "r-1")

        # Adding a relation with associated service, and remove it.
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_relation("r-1", "type")
        self.topology.assign_service_to_relation("r-1", "s-0", "name", "role")
        self.assertTrue(self.topology.has_relation("r-1"))
        self.topology.remove_relation("r-1")
        self.assertFalse(self.topology.has_relation("r-1"))

    def test_remove_service_with_relations(self):
        """
        Attempting to remove a service that's assigned to relations
        raises an InternalTopologyError.
        """
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_relation("r-1", "type")
        self.topology.assign_service_to_relation("r-1", "s-0", "name", "role")

        self.assertRaises(InternalTopologyError, self.topology.remove_service,
                          "s-0")

    def test_has_relation_between_dyadic_endpoints(self):
        mysql_ep = RelationEndpoint("mysqldb", "mysql", "db", "server")
        blog_ep = RelationEndpoint("wordpress", "mysql", "mysql", "client")
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service("s-1", "mysqldb")
        self.topology.add_relation("r-0", "mysql")
        self.topology.assign_service_to_relation("r-0", "s-0", "mysql",
                                                 "client")
        self.topology.assign_service_to_relation("r-0", "s-1", "db", "server")
        self.assertTrue(
            self.topology.has_relation_between_endpoints([mysql_ep, blog_ep]))
        self.assertTrue(
            self.topology.has_relation_between_endpoints([blog_ep, mysql_ep]))

    def test_has_relation_between_dyadic_endpoints_missing_assignment(self):
        mysql_ep = RelationEndpoint("mysqldb", "mysql", "db", "server")
        blog_ep = RelationEndpoint("wordpress", "mysql", "mysql", "client")
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service("s-1", "mysqldb")
        self.topology.add_relation("r-0", "mysql")
        self.topology.assign_service_to_relation("r-0", "s-1", "db", "server")
        self.assertFalse(
            self.topology.has_relation_between_endpoints([mysql_ep, blog_ep]))
        self.assertFalse(
            self.topology.has_relation_between_endpoints([blog_ep, mysql_ep]))

    def test_has_relation_between_dyadic_endpoints_wrong_relation_name(self):
        mysql_ep = RelationEndpoint("mysqldb", "mysql", "wrong-name", "server")
        blog_ep = RelationEndpoint("wordpress", "mysql", "mysql", "client")
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service("s-1", "mysqldb")
        self.topology.add_relation("r-0", "mysql")
        self.topology.assign_service_to_relation("r-0", "s-0", "mysql",
                                                 "client")
        self.topology.assign_service_to_relation("r-0", "s-1", "db", "server")
        self.assertFalse(
            self.topology.has_relation_between_endpoints([mysql_ep, blog_ep]))
        self.assertFalse(
            self.topology.has_relation_between_endpoints([blog_ep, mysql_ep]))

    def test_has_relation_between_monadic_endpoints(self):
        riak_ep = RelationEndpoint("riak", "riak", "riak", "peer")
        self.topology.add_service("s-0", "riak")
        self.topology.add_relation("r-0", "riak")
        self.topology.assign_service_to_relation("r-0", "s-0", "riak", "peer")
        self.assertTrue(self.topology.has_relation_between_endpoints([riak_ep
                                                                      ]))

    def test_get_relation_between_dyadic_endpoints(self):
        mysql_ep = RelationEndpoint("mysqldb", "mysql", "db", "server")
        blog_ep = RelationEndpoint("wordpress", "mysql", "mysql", "client")
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service("s-1", "mysqldb")
        self.topology.add_relation("r-0", "mysql")
        self.topology.assign_service_to_relation("r-0", "s-0", "mysql",
                                                 "client")
        self.topology.assign_service_to_relation("r-0", "s-1", "db", "server")
        self.assertEqual(
            self.topology.get_relation_between_endpoints([mysql_ep, blog_ep]),
            "r-0")
        self.assertEqual(
            self.topology.get_relation_between_endpoints([blog_ep, mysql_ep]),
            "r-0")

    def test_get_relation_between_dyadic_endpoints_missing_assignment(self):
        mysql_ep = RelationEndpoint("mysqldb", "mysql", "db", "server")
        blog_ep = RelationEndpoint("wordpress", "mysql", "mysql", "client")
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service("s-1", "mysqldb")
        self.topology.add_relation("r-0", "mysql")
        self.topology.assign_service_to_relation("r-0", "s-1", "db", "server")
        self.assertEqual(
            self.topology.get_relation_between_endpoints([mysql_ep, blog_ep]),
            None)
        self.assertEqual(
            self.topology.get_relation_between_endpoints([blog_ep, mysql_ep]),
            None)

    def test_get_relation_between_dyadic_endpoints_wrong_relation_name(self):
        mysql_ep = RelationEndpoint("mysqldb", "mysql", "wrong-name", "server")
        blog_ep = RelationEndpoint("wordpress", "mysql", "mysql", "client")
        self.topology.add_service("s-0", "wordpress")
        self.topology.add_service("s-1", "mysqldb")
        self.topology.add_relation("r-0", "mysql")
        self.topology.assign_service_to_relation("r-0", "s-0", "mysql",
                                                 "client")
        self.topology.assign_service_to_relation("r-0", "s-1", "db", "server")
        self.assertEqual(
            self.topology.get_relation_between_endpoints([mysql_ep, blog_ep]),
            None)
        self.assertEqual(
            self.topology.get_relation_between_endpoints([blog_ep, mysql_ep]),
            None)

    def test_get_relation_between_monadic_endpoints(self):
        riak_ep = RelationEndpoint("riak", "riak", "riak", "peer")
        self.topology.add_service("s-0", "riak")
        self.topology.add_relation("r-0", "riak")
        self.topology.assign_service_to_relation("r-0", "s-0", "riak", "peer")
        self.assertEqual(
            self.topology.get_relation_between_endpoints([riak_ep]), "r-0")
Beispiel #29
0
 def setUp(self):
     self.topology = InternalTopology()
Beispiel #30
0
 def get_topology(self):
     """Read /topology and return InternalTopology instance with it."""
     content, stat = yield self.client.get("/topology")
     topology = InternalTopology()
     topology.parse(content)
     returnValue(topology)
Beispiel #31
0
 def get_topology(self):
     """Read /topology and return InternalTopology instance with it."""
     content, stat = yield self.client.get("/topology")
     topology = InternalTopology()
     topology.parse(content)
     returnValue(topology)
Beispiel #32
0
 def parse_topology(self, content):
     topology = InternalTopology()
     topology.parse(content)
     return topology
Beispiel #33
0
 def change_content_function(content, stat):
     topology = InternalTopology()
     if content:
         topology.parse(content)
     yield change_topology_function(topology)
     returnValue(topology.dump())
Beispiel #34
0
 def parse_topology(self, content):
     topology = InternalTopology()
     topology.parse(content)
     return topology