Make Machine.destroy() block
authorTim Van Steenburgh <tvansteenburgh@gmail.com>
Fri, 24 Feb 2017 14:39:08 +0000 (09:39 -0500)
committerTim Van Steenburgh <tvansteenburgh@gmail.com>
Fri, 24 Feb 2017 14:39:08 +0000 (09:39 -0500)
Block the current coro until the machine is actually removed
from the remote model. This follows the same pattern as other
operations, e.g. Model.deploy().

Also clean up tests, making each functional test run on its
own clean model.

examples/add_machine.py
juju/machine.py
juju/model.py
tests/base.py
tests/client/test_client.py
tests/client/test_connection.py
tests/functional/test_model.py

index c5f9a6c..391df00 100755 (executable)
@@ -54,9 +54,10 @@ async def main():
                         for unit in application.units))
 
         await application.remove()
-        await machine3.destroy()
-        await machine2.destroy()
-        await machine1.destroy()
+
+        await machine3.destroy(force=True)
+        await machine2.destroy(force=True)
+        await machine1.destroy(force=True)
     finally:
         await model.disconnect()
 
index 04abc3b..44560bf 100644 (file)
@@ -10,6 +10,8 @@ class Machine(model.ModelEntity):
     async def destroy(self, force=False):
         """Remove this machine from the model.
 
+        Blocks until the machine is actually removed.
+
         """
         facade = client.ClientFacade()
         facade.connect(self.connection)
@@ -17,7 +19,9 @@ class Machine(model.ModelEntity):
         log.debug(
             'Destroying machine %s', self.id)
 
-        return await facade.DestroyMachines(force, [self.id])
+        await facade.DestroyMachines(force, [self.id])
+        return await self.model._wait(
+            'machine', self.id, 'remove')
     remove = destroy
 
     def run(self, command, timeout=None):
index ef6bc85..1ab29b5 100644 (file)
@@ -671,7 +671,7 @@ class Model(object):
 
         :param entity_type: The entity's type.
         :param entity_id: The entity's id.
-        :param action: the type of action (e.g., 'add' or 'change')
+        :param action: the type of action (e.g., 'add', 'change', or 'remove')
         :param predicate: optional callable that must take as an
             argument a delta, and must return a boolean, indicating
             whether the delta contains the specific action we're looking
@@ -686,7 +686,9 @@ class Model(object):
 
         self.add_observer(callback, entity_type, action, entity_id, predicate)
         entity_id = await q.get()
-        return self.state._live_entity_map(entity_type)[entity_id]
+        # object might not be in the entity_map if we were waiting for a
+        # 'remove' action
+        return self.state._live_entity_map(entity_type).get(entity_id)
 
     async def _wait_for_new(self, entity_type, entity_id=None, predicate=None):
         """Wait for a new object to appear in the Model and return it.
index 35003cb..382da43 100644 (file)
@@ -1,7 +1,10 @@
+import uuid
 import subprocess
 
 import pytest
 
+from juju.controller import Controller
+
 
 def is_bootstrapped():
     result = subprocess.run(['juju', 'switch'], stdout=subprocess.PIPE)
@@ -12,3 +15,23 @@ def is_bootstrapped():
 bootstrapped = pytest.mark.skipif(
     not is_bootstrapped(),
     reason='bootstrapped Juju environment required')
+
+
+class CleanModel():
+    def __init__(self):
+        self.controller = None
+        self.model = None
+
+    async def __aenter__(self):
+        self.controller = Controller()
+        await self.controller.connect_current()
+
+        model_name = 'model-{}'.format(uuid.uuid4())
+        self.model = await self.controller.add_model(model_name)
+
+        return self.model
+
+    async def __aexit__(self, exc_type, exc, tb):
+        await self.model.disconnect()
+        await self.controller.destroy_model(self.model.info.uuid)
+        await self.controller.disconnect()
index 653da5b..ca2637f 100644 (file)
@@ -1,24 +1,22 @@
 import pytest
 
-from juju.client.connection import Connection
 from juju.client import client
 
-from ..base import bootstrapped
+from .. import base
 
 
-@bootstrapped
+@base.bootstrapped
 @pytest.mark.asyncio
 async def test_user_info(event_loop):
-    conn = await Connection.connect_current()
-    controller_conn = await conn.controller()
+    async with base.CleanModel() as model:
+        controller_conn = await model.connection.controller()
 
-    um = client.UserManagerFacade()
-    um.connect(controller_conn)
-    result = await um.UserInfo(
-        [client.Entity('user-admin')], True)
-    await conn.close()
-    await controller_conn.close()
+        um = client.UserManagerFacade()
+        um.connect(controller_conn)
+        result = await um.UserInfo(
+            [client.Entity('user-admin')], True)
+        await controller_conn.close()
 
-    assert isinstance(result, client.UserInfoResults)
-    for r in result.results:
-        assert isinstance(r, client.UserInfoResult)
+        assert isinstance(result, client.UserInfoResults)
+        for r in result.results:
+            assert isinstance(r, client.UserInfoResult)
index 0a7402b..9c61759 100644 (file)
@@ -1,13 +1,14 @@
 import pytest
 
 from juju.client.connection import Connection
-from ..base import bootstrapped
+from .. import base
 
 
-@bootstrapped
+@base.bootstrapped
 @pytest.mark.asyncio
 async def test_connect_current(event_loop):
-    conn = await Connection.connect_current()
+    async with base.CleanModel():
+        conn = await Connection.connect_current()
 
-    assert isinstance(conn, Connection)
-    await conn.close()
+        assert isinstance(conn, Connection)
+        await conn.close()
index 52d8a03..9ae9204 100644 (file)
@@ -1,41 +1,17 @@
-import uuid
-
 import pytest
 
-from juju.controller import Controller
-
-from ..base import bootstrapped
+from .. import base
 
 MB = 1
 GB = 1024
 
 
-class CleanModel():
-    def __init__(self):
-        self.controller = None
-        self.model = None
-
-    async def __aenter__(self):
-        self.controller = Controller()
-        await self.controller.connect_current()
-
-        model_name = 'model-{}'.format(uuid.uuid4())
-        self.model = await self.controller.add_model(model_name)
-
-        return self.model
-
-    async def __aexit__(self, exc_type, exc, tb):
-        await self.model.disconnect()
-        await self.controller.destroy_model(self.model.info.uuid)
-        await self.controller.disconnect()
-
-
-@bootstrapped
+@base.bootstrapped
 @pytest.mark.asyncio
 async def test_add_machine(event_loop):
     from juju.machine import Machine
 
-    async with CleanModel() as model:
+    async with base.CleanModel() as model:
         # add a new default machine
         machine1 = await model.add_machine()
 
@@ -60,3 +36,10 @@ async def test_add_machine(event_loop):
             assert isinstance(m, Machine)
 
         assert len(model.machines) == 3
+
+        await machine3.destroy(force=True)
+        await machine2.destroy(force=True)
+        res = await machine1.destroy(force=True)
+
+        assert res is None
+        assert len(model.machines) == 0