Skip to content

Commit

Permalink
Merge pull request #706 from cderici/fix-facade-issues-1
Browse files Browse the repository at this point in the history
#706

#### Description

This change attempts to fix the `Unit.run_actions` issue where the resulting object is different based on the used Facade (which is based on whether the `2.9` or `3.0` juju client is being used). With this change both ways will return the same type of object as a result of running the action on the unit. It's also a continuation/possible-regression-fix of #698.

Also adding the `EnvironUpgrader` facade that's introduced in `Juju 3.0`, so we shouldn't get the "unexpected facade" warning anymore.

Also updating the clients with the latest schema from juju 3.0. In particuılar the Application facade version is bumped up `13` -> `14`.

With this #705 should be fixed too. 

#### QA Steps

QA steps for this is sort of a simulation of one of the real world libjuju uses in a function on [mongodb-operator](https://github.com/canonical/mongodb-operator/blob/8796be02e10b84f990da0426ad1cff5922dc506a/tests/integration/helpers.py#L39).

Get a controller on a k8s cloud (I did it on `microk8s`). The following then should work fine.
Full QA should be done with 2 separate controllers (at least that's what I did): one bootstrapped with `juju 2.9` and another with `juju 3.0`.

```python
async def _get_password():
 model = Model()
 await model.connect()
 await model.deploy('zinc-k8s')
 await model.wait_for_idle(status="active")

 unit = model.applications['zinc-k8s'].units[0]
 action = await unit.run_action("get-admin-password")
 action = await action.wait()
 print(action.results["admin-password"])
 await model.disconnect()
```

#### Notes & Discussion

This is also related (and most likely a fix) to:
* openstack-charmers/zaza#545
* openstack-charmers/zaza#546
* https://github.com/canonical/mongodb-operator/runs/7761747331?check_suite_focus=true
  • Loading branch information
jujubot committed Aug 11, 2022
2 parents 1dd9cbf + 5a88c66 commit d6e9ca5
Show file tree
Hide file tree
Showing 20 changed files with 66,725 additions and 76,467 deletions.
8 changes: 7 additions & 1 deletion juju/action.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@


class Action(model.ModelEntity):

def __init__(self, entity_id, model, history_index=-1, connected=True):
super().__init__(entity_id, model, history_index, connected)
self.results = {}

@property
def status(self):
return self.data['status']

async def wait(self):
return await self.model.get_action_output(self.id)
self.results = await self.model.get_action_output(self.id)
return self
37 changes: 7 additions & 30 deletions juju/client/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,21 @@
from juju.client._definitions import *


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


CLIENTS = {
"2": _client2,
"7": _client7,
"1": _client1,
"3": _client3,
"2": _client2,
"14": _client14,
"4": _client4,
"5": _client5,
"8": _client8,
"7": _client7,
"9": _client9,
"10": _client10,
"6": _client6,
"12": _client12,
"5": _client5,
"11": _client11,
"13": _client13,
"15": _client15,
"16": _client16,
"17": _client17,
"18": _client18,
"14": _client14
"9": _client9,
"18": _client18
}


Expand Down Expand Up @@ -127,10 +120,6 @@ class ApplicationOffersFacade(TypeFactory):
pass


class ApplicationRelationsWatcherFacade(TypeFactory):
pass


class ApplicationScalerFacade(TypeFactory):
pass

Expand Down Expand Up @@ -247,10 +236,6 @@ class DeployerFacade(TypeFactory):
pass


class DiscoverSpacesFacade(TypeFactory):
pass


class DiskManagerFacade(TypeFactory):
pass

Expand Down Expand Up @@ -455,10 +440,6 @@ class RelationUnitsWatcherFacade(TypeFactory):
pass


class RemoteApplicationWatcherFacade(TypeFactory):
pass


class RemoteRelationWatcherFacade(TypeFactory):
pass

Expand All @@ -467,10 +448,6 @@ class RemoteRelationsFacade(TypeFactory):
pass


class RemoteRelationsWatcherFacade(TypeFactory):
pass


class ResourcesFacade(TypeFactory):
pass

Expand Down
21,331 changes: 7,031 additions & 14,300 deletions juju/client/_client1.py

Large diffs are not rendered by default.

1,548 changes: 0 additions & 1,548 deletions juju/client/_client11.py

Large diffs are not rendered by default.

131 changes: 4 additions & 127 deletions juju/client/_client14.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,15 +216,6 @@ class ApplicationFacade(Type):
'remote',
'life'],
'type': 'object'},
'ApplicationSet': {'additionalProperties': False,
'properties': {'application': {'type': 'string'},
'branch': {'type': 'string'},
'options': {'patternProperties': {'.*': {'type': 'string'}},
'type': 'object'}},
'required': ['application',
'branch',
'options'],
'type': 'object'},
'ApplicationSetCharm': {'additionalProperties': False,
'properties': {'application': {'type': 'string'},
'channel': {'type': 'string'},
Expand Down Expand Up @@ -799,12 +790,6 @@ class ApplicationFacade(Type):
'properties': {'Params': {'$ref': '#/definitions/ApplicationGet'},
'Result': {'$ref': '#/definitions/ApplicationGetResults'}},
'type': 'object'},
'GetCharmURL': {'description': 'GetCharmURL returns the charm '
'URL the given application is\n'
'running at present.',
'properties': {'Params': {'$ref': '#/definitions/ApplicationGet'},
'Result': {'$ref': '#/definitions/StringResult'}},
'type': 'object'},
'GetCharmURLOrigin': {'description': 'GetCharmURLOrigin '
'returns the charm URL '
'and charm origin the '
Expand Down Expand Up @@ -853,20 +838,13 @@ class ApplicationFacade(Type):
'properties': {'Params': {'$ref': '#/definitions/ScaleApplicationsParams'},
'Result': {'$ref': '#/definitions/ScaleApplicationResults'}},
'type': 'object'},
'Set': {'description': 'Set implements the server side of '
'Application.Set.\n'
'It does not unset values that are set '
'to an empty string.\n'
'Unset should be used for that.',
'properties': {'Params': {'$ref': '#/definitions/ApplicationSet'}},
'type': 'object'},
'SetCharm': {'description': 'SetCharm sets the charm for a '
'given for the application.',
'properties': {'Params': {'$ref': '#/definitions/ApplicationSetCharm'}},
'type': 'object'},
'SetConfigs': {'description': 'SetConfig implements the server '
'side of Application.SetConfig. '
'Both\n'
'SetConfigs': {'description': 'SetConfigs implements the '
'server side of '
'Application.SetConfig. Both\n'
'application and charm config '
'are set. It does not unset '
'values in\n'
Expand Down Expand Up @@ -908,10 +886,6 @@ class ApplicationFacade(Type):
'properties': {'Params': {'$ref': '#/definitions/Entities'},
'Result': {'$ref': '#/definitions/UnitInfoResults'}},
'type': 'object'},
'Unset': {'description': 'Unset implements the server side of '
'Client.Unset.',
'properties': {'Params': {'$ref': '#/definitions/ApplicationUnset'}},
'type': 'object'},
'UnsetApplicationsConfig': {'description': 'UnsetApplicationsConfig '
'implements the '
'server side of '
Expand Down Expand Up @@ -1350,35 +1324,6 @@ async def Get(self, application=None, branch=None):



@ReturnMapping(StringResult)
async def GetCharmURL(self, application=None, branch=None):
'''
GetCharmURL returns the charm URL the given application is
running at present.
application : str
branch : str
Returns -> StringResult
'''
if application is not None and not isinstance(application, (bytes, str)):
raise Exception("Expected application to be a str, received: {}".format(type(application)))

if branch is not None and not isinstance(branch, (bytes, str)):
raise Exception("Expected branch to be a str, received: {}".format(type(branch)))

# map input types to rpc msg
_params = dict()
msg = dict(type='Application',
request='GetCharmURL',
version=14,
params=_params)
_params['application'] = application
_params['branch'] = branch
reply = await self.rpc(msg)
return reply



@ReturnMapping(CharmURLOriginResult)
async def GetCharmURLOrigin(self, application=None, branch=None):
'''
Expand Down Expand Up @@ -1557,41 +1502,6 @@ async def ScaleApplications(self, applications=None):



@ReturnMapping(None)
async def Set(self, application=None, branch=None, options=None):
'''
Set implements the server side of Application.Set.
It does not unset values that are set to an empty string.
Unset should be used for that.
application : str
branch : str
options : typing.Mapping[str, str]
Returns -> None
'''
if application is not None and not isinstance(application, (bytes, str)):
raise Exception("Expected application to be a str, received: {}".format(type(application)))

if branch is not None and not isinstance(branch, (bytes, str)):
raise Exception("Expected branch to be a str, received: {}".format(type(branch)))

if options is not None and not isinstance(options, dict):
raise Exception("Expected options to be a Mapping, received: {}".format(type(options)))

# map input types to rpc msg
_params = dict()
msg = dict(type='Application',
request='Set',
version=14,
params=_params)
_params['application'] = application
_params['branch'] = branch
_params['options'] = options
reply = await self.rpc(msg)
return reply



@ReturnMapping(None)
async def SetCharm(self, application=None, channel=None, charm_origin=None, charm_url=None, config_settings=None, config_settings_yaml=None, endpoint_bindings=None, force=None, force_series=None, force_units=None, generation=None, resource_ids=None, storage_constraints=None):
'''
Expand Down Expand Up @@ -1678,7 +1588,7 @@ async def SetCharm(self, application=None, channel=None, charm_origin=None, char
@ReturnMapping(ErrorResults)
async def SetConfigs(self, args=None):
'''
SetConfig implements the server side of Application.SetConfig. Both
SetConfigs implements the server side of Application.SetConfig. Both
application and charm config are set. It does not unset values in
Config map that are set to an empty string. Unset should be used for that.
Expand Down Expand Up @@ -1827,39 +1737,6 @@ async def UnitsInfo(self, entities=None):



@ReturnMapping(None)
async def Unset(self, application=None, branch=None, options=None):
'''
Unset implements the server side of Client.Unset.
application : str
branch : str
options : typing.Sequence[str]
Returns -> None
'''
if application is not None and not isinstance(application, (bytes, str)):
raise Exception("Expected application to be a str, received: {}".format(type(application)))

if branch is not None and not isinstance(branch, (bytes, str)):
raise Exception("Expected branch to be a str, received: {}".format(type(branch)))

if options is not None and not isinstance(options, (bytes, str, list)):
raise Exception("Expected options to be a Sequence, received: {}".format(type(options)))

# map input types to rpc msg
_params = dict()
msg = dict(type='Application',
request='Unset',
version=14,
params=_params)
_params['application'] = application
_params['branch'] = branch
_params['options'] = options
reply = await self.rpc(msg)
return reply



@ReturnMapping(ErrorResults)
async def UnsetApplicationsConfig(self, args=None):
'''
Expand Down
Loading

0 comments on commit d6e9ca5

Please sign in to comment.