Skip to content

Commit 9349e45

Browse files
authored
Merge pull request #692 from cderici/merge-3.0-compatibility
#692 #### Description This PR introduces the mechanism to have both the old and the new clients generated from `2.9` and `3.0` schema respectively. Based on the `server_version'` information within the connection, correct facade is picked from the correct client among the correct client group. With this, `pylibjuju` should be able to work with both `juju 2.9` and `juju 3.0`. This PR also merges the `juju-3.0-compatibility` branch onto the `master`. Most important changes include: - we have both the clients generated from `juju 3.0` schema, and from the `juju 2.9` schema. The `facade` code is updated to generate the `_client` code to have both sets of clients and facades inside, and whenever a facade is needed, to dynamically pick the correct facade from the correct client based on the version set in the underlying connection with juju. - `client.py` module is no longer a regular Python module, it's a class instance that acts like a module within Python runtime. Based on the juju version, it pulls all the bindings (`_client`s and the `_definition`s) dynamically from respective correct modules, which are named `_client` and `_definitions` for `juju 3.0`, and `_2_9_client` and `_2_9_definitions` for `juju 2.9`. So whenever in the code we pull a binding from the client, like `client.CharmOrigin`, the `CharmOrigin` object we get comes from the correct `_definitions` module. #### QA Steps All the integration tests should pass here on CI, and also 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 - Everything on the CI tests should pass except the intermittent stuff we already know about, and also some charmhub stuff that we observed before I started working on this. - There will be some tests failing for sure, I couldn't check all the tests yet, so I'm labeling this as `do not merge` until we see all green (or almost all green).
2 parents fbbaf78 + e516285 commit 9349e45

91 files changed

Lines changed: 256064 additions & 2719 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

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: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ def __init__(self, model, trusted=False, forced=False):
5252
model.connection())
5353
self.ann_facade = client.AnnotationsFacade.from_connection(
5454
model.connection())
55+
self.machine_manager_facade = client.MachineManagerFacade.from_connection(
56+
model.connection())
5557

5658
# Feature detect if we have the new charms facade, otherwise fallback
5759
# to the client facade, when making calls.
@@ -617,10 +619,8 @@ async def run(self, context):
617619
self.application, charm, overrides=self.resources)
618620
elif Schema.CHARM_HUB.matches(url.schema):
619621
c_hub = charmhub.CharmHub(context.model)
620-
info = await c_hub.info(url.name, channel=self.channel)
621-
if info.errors.error_list.code:
622-
raise JujuError("unable to resolve the charm {} with channel {}".format(url.name, channel))
623-
origin.id_ = info.result.id_
622+
id_, _ = c_hub.get_charm_id(url.name)
623+
origin.id_ = id_
624624
resources = await context.model._add_charmhub_resources(
625625
self.application, charm, origin, overrides=self.resources)
626626
else:
@@ -726,7 +726,7 @@ async def run(self, context):
726726
if Schema.CHARM_STORE.matches(url.schema):
727727
entity_id = await context.charmstore.entityId(self.charm, channel=self.channel)
728728
log.debug('Adding %s', entity_id)
729-
await context.client_facade.AddCharm(channel=self.channel, url=entity_id, force=False)
729+
await context.charms_facade.AddCharm(channel=self.channel, url=entity_id, force=False)
730730
identifier = entity_id
731731
origin = client.CharmOrigin(source="charm-store", risk="stable")
732732

@@ -837,7 +837,7 @@ async def run(self, context):
837837

838838
# Submit the request.
839839
params = client.AddMachineParams(**params)
840-
results = await context.client_facade.AddMachines(params=[params])
840+
results = await context.machine_manager_facade.AddMachines(params=[params])
841841
error = results.machines[0].error
842842
if error:
843843
raise ValueError("Error adding machine: %s" % error.message)

juju/charmhub.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,42 @@
11
from .client import client
22
from .errors import JujuError
3+
from juju import jasyncio
4+
5+
import requests
6+
import json
37

48

59
class CharmHub:
610
def __init__(self, model):
711
self.model = model
812

13+
def request_charmhub_with_retry(self, url, retries):
14+
for attempt in range(retries):
15+
_response = requests.get(url)
16+
if _response.status_code == 200:
17+
return _response
18+
jasyncio.sleep(5)
19+
raise JujuError("Got {} from {}".format(_response.status_code, url))
20+
21+
def get_charm_id(self, charm_name):
22+
conn, headers, path_prefix = self.model.connection().https_connection()
23+
24+
url = "http://api.snapcraft.io/v2/charms/info/{}".format(charm_name)
25+
_response = self.request_charmhub_with_retry(url, 5)
26+
response = json.loads(_response.text)
27+
return response['id'], response['name']
28+
29+
def is_subordinate(self, charm_name):
30+
conn, headers, path_prefix = self.model.connection().https_connection()
31+
32+
url = "http://api.snapcraft.io/v2/charms/info/{}?fields=default-release.revision.subordinate".format(charm_name)
33+
_response = self.request_charmhub_with_retry(url, 5)
34+
response = json.loads(_response.text)
35+
return 'subordinate' in response['default-release']['revision']
36+
37+
# TODO (caner) : we should be able to recreate the channel-map through the
38+
# api call without needing the CharmHub facade
39+
940
async def info(self, name, channel=None):
1041
"""info displays detailed information about a CharmHub charm. The charm
1142
can be specified by the exact name.

juju/client/_client.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33

44
from juju.client._definitions import *
55

6-
from juju.client import _client2, _client1, _client3, _client4, _client5, _client8, _client7, _client9, _client10, _client6, _client12, _client11, _client13, _client15, _client16, _client17, _client18
6+
7+
from juju.client import _client2, _client1, _client3, _client4, _client5, _client8, _client7, _client9, _client10, _client6, _client12, _client11, _client13, _client15, _client16, _client17, _client18, _client14
78

89

910
CLIENTS = {
@@ -23,11 +24,11 @@
2324
"15": _client15,
2425
"16": _client16,
2526
"17": _client17,
26-
"18": _client18
27+
"18": _client18,
28+
"14": _client14
2729
}
2830

2931

30-
3132
def lookup_facade(name, version):
3233
"""
3334
Given a facade name and version, attempt to pull that facade out
@@ -45,7 +46,6 @@ def lookup_facade(name, version):
4546
"{}".format(name))
4647

4748

48-
4949
class TypeFactory:
5050
@classmethod
5151
def from_connection(cls, connection):
@@ -259,6 +259,10 @@ class EntityWatcherFacade(TypeFactory):
259259
pass
260260

261261

262+
class EnvironUpgraderFacade(TypeFactory):
263+
pass
264+
265+
262266
class ExternalControllerUpdaterFacade(TypeFactory):
263267
pass
264268

0 commit comments

Comments
 (0)