Implement Application.upgrade_charm method (#132)
authorCynerva <cynerva@gmail.com>
Fri, 2 Jun 2017 14:53:28 +0000 (09:53 -0500)
committerCory Johns <johnsca@gmail.com>
Fri, 2 Jun 2017 14:53:28 +0000 (10:53 -0400)
* Implement Application.upgrade_charm method

* Use model.block_until instead of a direct loop on asyncio.sleep

* Raise JujuError if upgrade_charm is a noop

Same behavior as Juju CLI here.

* Add integration tests for upgrade_charm

* Fix upgrade_charm hangs forever if switch arg does not start with 'cs:'

juju/application.py
tests/integration/test_application.py

index 2733a6c..8719a62 100644 (file)
@@ -3,6 +3,7 @@ import logging
 
 from . import model
 from .client import client
+from .errors import JujuError
 from .placement import parse as parse_placement
 
 log = logging.getLogger(__name__)
@@ -314,9 +315,9 @@ class Application(model.ModelEntity):
         """
         raise NotImplementedError()
 
-    def upgrade_charm(
+    async def upgrade_charm(
             self, channel=None, force_series=False, force_units=False,
-            path=None, resources=None, revision=-1, switch=None):
+            path=None, resources=None, revision=None, switch=None):
         """Upgrade the charm for this application.
 
         :param str channel: Channel to use when getting the charm from the
@@ -331,7 +332,55 @@ class Application(model.ModelEntity):
         :param str switch: Crossgrade charm url
 
         """
-        raise NotImplementedError()
+        # TODO: Support local upgrades
+        if path is not None:
+            raise NotImplementedError("path option is not implemented")
+        if resources is not None:
+            raise NotImplementedError("resources option is not implemented")
+
+        if switch is not None and revision is not None:
+            raise ValueError("switch and revision are mutually exclusive")
+
+        client_facade = client.ClientFacade.from_connection(self.connection)
+        app_facade = client.ApplicationFacade.from_connection(self.connection)
+
+        if switch is not None:
+            charm_url = switch
+            if not charm_url.startswith('cs:'):
+                charm_url = 'cs:' + charm_url
+        else:
+            charm_url = self.data['charm-url']
+            charm_url = charm_url.rpartition('-')[0]
+            if revision is not None:
+                charm_url = "%s-%d" % (charm_url, revision)
+            else:
+                charmstore = self.model.charmstore
+                entity = await charmstore.entity(charm_url, channel=channel)
+                charm_url = entity['Id']
+
+        if charm_url == self.data['charm-url']:
+            raise JujuError('already running charm "%s"' % charm_url)
+
+        await client_facade.AddCharm(
+            url=charm_url,
+            channel=channel
+        )
+
+        await app_facade.SetCharm(
+            application=self.entity_id,
+            channel=channel,
+            charm_url=charm_url,
+            config_settings=None,
+            config_settings_yaml=None,
+            force_series=force_series,
+            force_units=force_units,
+            resource_ids=None,
+            storage_constraints=None
+        )
+
+        await self.model.block_until(
+            lambda: self.data['charm-url'] == charm_url
+        )
 
     async def get_metrics(self):
         """Get metrics for this application's units.
index 1618a5a..1a4fcaa 100644 (file)
@@ -50,3 +50,45 @@ async def test_add_units(event_loop):
         assert len(units) == 2
         for unit in units:
             assert isinstance(unit, Unit)
+
+
+@base.bootstrapped
+@pytest.mark.asyncio
+async def test_upgrade_charm(event_loop):
+    async with base.CleanModel() as model:
+        app = await model.deploy('ubuntu-0')
+        assert app.data['charm-url'] == 'cs:ubuntu-0'
+        await app.upgrade_charm()
+        assert app.data['charm-url'].startswith('cs:ubuntu-')
+        assert app.data['charm-url'] != 'cs:ubuntu-0'
+
+
+@base.bootstrapped
+@pytest.mark.asyncio
+async def test_upgrade_charm_channel(event_loop):
+    async with base.CleanModel() as model:
+        app = await model.deploy('ubuntu-0')
+        assert app.data['charm-url'] == 'cs:ubuntu-0'
+        await app.upgrade_charm(channel='stable')
+        assert app.data['charm-url'].startswith('cs:ubuntu-')
+        assert app.data['charm-url'] != 'cs:ubuntu-0'
+
+
+@base.bootstrapped
+@pytest.mark.asyncio
+async def test_upgrade_charm_revision(event_loop):
+    async with base.CleanModel() as model:
+        app = await model.deploy('ubuntu-0')
+        assert app.data['charm-url'] == 'cs:ubuntu-0'
+        await app.upgrade_charm(revision=8)
+        assert app.data['charm-url'] == 'cs:ubuntu-8'
+
+
+@base.bootstrapped
+@pytest.mark.asyncio
+async def test_upgrade_charm_switch(event_loop):
+    async with base.CleanModel() as model:
+        app = await model.deploy('ubuntu-0')
+        assert app.data['charm-url'] == 'cs:ubuntu-0'
+        await app.upgrade_charm(switch='ubuntu-8')
+        assert app.data['charm-url'] == 'cs:ubuntu-8'