From e2a3f01e7bb983ac3cd8eb24117e0d4208b40cc9 Mon Sep 17 00:00:00 2001 From: Tim Van Steenburgh Date: Fri, 24 Feb 2017 09:39:08 -0500 Subject: [PATCH] Make Machine.destroy() block 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 | 7 ++++--- juju/machine.py | 6 +++++- juju/model.py | 6 ++++-- tests/base.py | 23 ++++++++++++++++++++ tests/client/test_client.py | 26 +++++++++++------------ tests/client/test_connection.py | 11 +++++----- tests/functional/test_model.py | 37 +++++++++------------------------ 7 files changed, 64 insertions(+), 52 deletions(-) diff --git a/examples/add_machine.py b/examples/add_machine.py index c5f9a6c..391df00 100755 --- a/examples/add_machine.py +++ b/examples/add_machine.py @@ -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() diff --git a/juju/machine.py b/juju/machine.py index 04abc3b..44560bf 100644 --- a/juju/machine.py +++ b/juju/machine.py @@ -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): diff --git a/juju/model.py b/juju/model.py index ef6bc85..1ab29b5 100644 --- a/juju/model.py +++ b/juju/model.py @@ -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. diff --git a/tests/base.py b/tests/base.py index 35003cb..382da43 100644 --- a/tests/base.py +++ b/tests/base.py @@ -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() diff --git a/tests/client/test_client.py b/tests/client/test_client.py index 653da5b..ca2637f 100644 --- a/tests/client/test_client.py +++ b/tests/client/test_client.py @@ -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) diff --git a/tests/client/test_connection.py b/tests/client/test_connection.py index 0a7402b..9c61759 100644 --- a/tests/client/test_connection.py +++ b/tests/client/test_connection.py @@ -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() diff --git a/tests/functional/test_model.py b/tests/functional/test_model.py index 52d8a03..9ae9204 100644 --- a/tests/functional/test_model.py +++ b/tests/functional/test_model.py @@ -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 -- 2.25.1