Skip to content

Commit 08fb01c

Browse files
authored
Merge pull request #691 from cderici/more-test-fixes-for-3.0-compat
#691 #### Description This PR completes the fixes for 3.0 compatibility. With this, `pylibjuju` should be able to work with `juju 3.0`. Includes 8 commits. The summary of the changes is: * `ClientFacade` -> `CharmsFacade` for upgrades * Correct facade functions for setting and unsetting application config. * Added a call to charmhub api to check for subordinate * Changes to use the correct facade functions with correct input/result parameters * Small fixes for tests #### QA Steps All the integration tests should pass on a `juju 3.0` controller. So bootstrap from juju's `develop` branch (or `snap install juju --channel=latest/beta`) and run the following in libjuju: ```sh make test ``` #### Notes & Discussion * I observe in my local runs that some tests get stuck, running them individually makes them pass, there might be intermittent issues, but this is not related to 3.0 compatibility, a further investigation about that might be done later on. * Again, this targets the `juju-3.0-compatibility` branch. Next step on that is to make sure that the `pylibjuju` can work with both `2.9` and `3.0` facades at the same time.
2 parents 8b7e7b8 + 274e90e commit 08fb01c

11 files changed

Lines changed: 50 additions & 34 deletions

File tree

juju/application.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -431,10 +431,9 @@ async def get_actions(self, schema=False):
431431
:return dict: The charms actions, empty dict if none are defined.
432432
"""
433433
actions = {}
434-
entity = [{"tag": self.tag}]
434+
entity = {"tag": self.tag}
435435
action_facade = client.ActionFacade.from_connection(self.connection)
436-
results = (
437-
await action_facade.ApplicationsCharmsActions(entities=entity)).results
436+
results = (await action_facade.ApplicationsCharmsActions(entities=[entity])).results
438437
for result in results:
439438
if result.application_tag == self.tag and result.actions:
440439
actions = result.actions
@@ -563,7 +562,10 @@ async def set_config(self, config):
563562
else:
564563
raise JujuApplicationConfigError(config, [k, v])
565564

566-
await app_facade.Set(application=self.name, options=str_config)
565+
return await app_facade.SetConfigs(args=[{
566+
"application": self.name,
567+
"config": str_config,
568+
}])
567569

568570
async def reset_config(self, to_default):
569571
"""
@@ -577,7 +579,10 @@ async def reset_config(self, to_default):
577579
log.debug(
578580
'Restoring default config for %s: %s', self.name, to_default)
579581

580-
return await app_facade.Unset(application=self.name, options=to_default)
582+
return await app_facade.UnsetApplicationsConfig(args=[{
583+
"application": self.name,
584+
"options": to_default,
585+
}])
581586

582587
async def set_constraints(self, constraints):
583588
"""Set machine constraints for this application.
@@ -619,7 +624,7 @@ async def refresh(
619624
if switch is not None and revision is not None:
620625
raise ValueError("switch and revision are mutually exclusive")
621626

622-
client_facade = client.ClientFacade.from_connection(self.connection)
627+
charms_facade = client.CharmsFacade.from_connection(self.connection)
623628
resources_facade = client.ResourcesFacade.from_connection(
624629
self.connection)
625630
app_facade = self._facade()
@@ -644,11 +649,15 @@ async def refresh(
644649
if charm_url == self.data['charm-url']:
645650
raise JujuError('already running charm "%s"' % charm_url)
646651

652+
# TODO (caner) : this needs to be revisited and updated with the charmhub stuff
653+
origin = client.CharmOrigin(source="charm-store",
654+
risk=channel,
655+
)
647656
# Update charm
648-
await client_facade.AddCharm(
657+
await charms_facade.AddCharm(
649658
url=charm_url,
650659
force=force,
651-
channel=channel
660+
charm_origin=origin,
652661
)
653662

654663
# Update resources

juju/bundle.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -619,7 +619,7 @@ async def run(self, context):
619619
self.application, charm, overrides=self.resources)
620620
elif Schema.CHARM_HUB.matches(url.schema):
621621
c_hub = charmhub.CharmHub(context.model)
622-
id_ = c_hub.get_charm_id(url.name)
622+
id_, _ = c_hub.get_charm_id(url.name)
623623
origin.id_ = id_
624624
resources = await context.model._add_charmhub_resources(
625625
self.application, charm, origin, overrides=self.resources)

juju/charmhub.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,20 @@ def get_charm_id(self, charm_name):
1717
if not _response.status_code == 200:
1818
raise JujuError("Got {} from {}".format(_response.status_code, url))
1919
response = json.loads(_response.text)
20-
return response['id']
20+
return response['id'], response['name']
21+
22+
def is_subordinate(self, charm_name):
23+
conn, headers, path_prefix = self.model.connection().https_connection()
24+
25+
url = "http://api.snapcraft.io/v2/charms/info/{}?fields=default-release.revision.subordinate".format(charm_name)
26+
_response = requests.get(url)
27+
if not _response.status_code == 200:
28+
raise JujuError("Got {} from {}".format(_response.status_code, url))
29+
response = json.loads(_response.text)
30+
return 'subordinate' in response['default-release']['revision']
31+
32+
# TODO (caner) : we should be able to recreate the channel-map through the
33+
# api call without needing the CharmHub facade
2134

2235
async def info(self, name, channel=None):
2336
"""info displays detailed information about a CharmHub charm. The charm

juju/controller.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -807,7 +807,7 @@ async def get_consume_details(self, endpoint):
807807
model to consume the specified offers represented by the urls.
808808
"""
809809
facade = client.ApplicationOffersFacade.from_connection(self.connection())
810-
offers = await facade.GetConsumeDetails(offer_urls=[endpoint])
810+
offers = await facade.GetConsumeDetails(offer_urls=client.OfferURLs(offer_urls=[endpoint]))
811811
if len(offers.results) != 1:
812812
raise JujuAPIError("expected to find one result")
813813
result = offers.results[0]

juju/model.py

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1636,14 +1636,7 @@ async def deploy(
16361636
resources = await self._add_charmhub_resources(res.app_name,
16371637
identifier,
16381638
add_charm_res.charm_origin)
1639-
# TODO (cderici) : temporarily disable subordinate check for now
1640-
# charm_info = await self.charmhub.info(url.name)
1641-
is_subordinate = False
1642-
# try:
1643-
# is_subordinate = charm_info.result.charm.subordinate
1644-
# except AttributeError:
1645-
# log.warning('CharmHub.Info : unable to retrieve the subordinate information')
1646-
if is_subordinate:
1639+
if self.charmhub.is_subordinate(url.name):
16471640
if num_units > 1:
16481641
raise JujuError("cannot use num_units with subordinate application")
16491642
num_units = 0

juju/unit.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ async def run(self, command, timeout=None):
145145
timeout=timeout,
146146
units=[self.name],
147147
)
148-
return await self.model.wait_for_action(res.results[0].action.tag)
148+
return await self.model.wait_for_action(res.actions[0].action.tag)
149149

150150
async def run_action(self, action_name, **params):
151151
"""Run an action on this unit.

tests/integration/test_application.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import pytest
44

55
from .. import base
6+
from juju import jasyncio
67

78
MB = 1
89

@@ -40,6 +41,8 @@ async def test_action(event_loop):
4041
constraints = await ubuntu_app.get_constraints()
4142
assert constraints['mem'] == 512 * MB
4243

44+
await jasyncio.sleep(5)
45+
4346
# check action definitions
4447
actions = await ubuntu_app.get_actions()
4548
assert 'backup' in actions.keys()

tests/integration/test_charmhub.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@
99
@pytest.mark.asyncio
1010
async def test_info(event_loop):
1111
async with base.CleanModel() as model:
12-
result = await model.charmhub.info("hello-juju")
13-
14-
assert result.result.name == "hello-juju"
12+
_, name = model.charmhub.get_charm_id("hello-juju")
13+
assert name == "hello-juju"
1514

1615

1716
@base.bootstrapped
1817
@pytest.mark.asyncio
18+
@pytest.mark.skip('CharmHub facade no longer exists')
1919
async def test_info_with_channel(event_loop):
2020
async with base.CleanModel() as model:
2121
result = await model.charmhub.info("hello-juju", "latest/stable")
@@ -26,6 +26,7 @@ async def test_info_with_channel(event_loop):
2626

2727
@base.bootstrapped
2828
@pytest.mark.asyncio
29+
@pytest.mark.skip('CharmHub facade no longer exists')
2930
async def test_info_not_found(event_loop):
3031
async with base.CleanModel() as model:
3132
try:
@@ -38,6 +39,7 @@ async def test_info_not_found(event_loop):
3839

3940
@base.bootstrapped
4041
@pytest.mark.asyncio
42+
@pytest.mark.skip('CharmHub facade no longer exists')
4143
async def test_find(event_loop):
4244
async with base.CleanModel() as model:
4345
result = await model.charmhub.find("kube")
@@ -50,6 +52,7 @@ async def test_find(event_loop):
5052

5153
@base.bootstrapped
5254
@pytest.mark.asyncio
55+
@pytest.mark.skip('CharmHub facade no longer exists')
5356
async def test_find_bundles(event_loop):
5457
async with base.CleanModel() as model:
5558
result = await model.charmhub.find("kube", charm_type="bundle")
@@ -62,6 +65,7 @@ async def test_find_bundles(event_loop):
6265

6366
@base.bootstrapped
6467
@pytest.mark.asyncio
68+
@pytest.mark.skip('CharmHub facade no longer exists')
6569
async def test_find_all(event_loop):
6670
async with base.CleanModel() as model:
6771
result = await model.charmhub.find("")

tests/integration/test_errors.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ async def test_juju_error_in_results_list(event_loop):
5454
@pytest.mark.asyncio
5555
async def test_juju_error_in_result(event_loop):
5656
'''
57-
Verify that we raise a JujuError when appropraite when we are
57+
Verify that we raise a JujuError when appropriate when we are
5858
looking at a single result coming back.
5959
6060
'''
@@ -65,4 +65,4 @@ async def test_juju_error_in_result(event_loop):
6565
app_facade = client.ApplicationFacade.from_connection(model.connection())
6666

6767
with pytest.raises(JujuError):
68-
return await app_facade.GetCharmURL(application='foo')
68+
return await app_facade.GetCharmURLOrigin(application='foo')

tests/integration/test_unit.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ async def test_run(event_loop):
8181
for unit in app.units:
8282
action = await unit.run('unit-get public-address')
8383
assert isinstance(action, Action)
84-
assert 'Stdout' in action.results
84+
assert action.status == 'completed'
8585
break
8686

8787
for unit in app.units:

0 commit comments

Comments
 (0)