Skip to content

Commit c21cb93

Browse files
authored
Merge branch 'master' into optimize-teardown
2 parents e06d80f + 4349a4a commit c21cb93

3 files changed

Lines changed: 59 additions & 35 deletions

File tree

juju/model.py

Lines changed: 52 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1746,6 +1746,8 @@ async def deploy(
17461746
if base:
17471747
charm_origin.base = utils.parse_base_arg(base)
17481748

1749+
server_side_deploy = False
1750+
17491751
if res.is_bundle:
17501752
handler = BundleHandler(self, trusted=trust, forced=force)
17511753
await handler.fetch_plan(url, charm_origin, overlays=overlays)
@@ -1777,9 +1779,20 @@ async def deploy(
17771779
else:
17781780
charm_origin = add_charm_res.charm_origin
17791781
if Schema.CHARM_HUB.matches(url.schema):
1780-
resources = await self._add_charmhub_resources(res.app_name,
1781-
identifier,
1782-
add_charm_res.charm_origin)
1782+
1783+
if client.ApplicationFacade.best_facade_version(self.connection()) >= 19:
1784+
server_side_deploy = True
1785+
else:
1786+
# TODO (cderici): this is an awkward workaround for basically not calling
1787+
# the AddPendingResources in case this is a server side deploy.
1788+
# If that's the case, then the store resources (and revisioned local
1789+
# resources) are handled at the server side if this is a server side deploy
1790+
# (local uploads are handled right after we get the pendingIDs returned
1791+
# from the facade call).
1792+
resources = await self._add_charmhub_resources(res.app_name,
1793+
identifier,
1794+
add_charm_res.charm_origin)
1795+
17831796
is_sub = await self.charmhub.is_subordinate(url.name)
17841797
if is_sub:
17851798
if num_units > 1:
@@ -1832,6 +1845,7 @@ async def deploy(
18321845
charm_origin=charm_origin,
18331846
attach_storage=attach_storage,
18341847
force=force,
1848+
server_side_deploy=server_side_deploy,
18351849
)
18361850

18371851
async def _add_charm(self, charm_url, origin):
@@ -2032,42 +2046,42 @@ async def add_local_resources(self, application, entity_url, metadata, resources
20322046
'username': '',
20332047
'password': '',
20342048
}
2035-
20362049
data = yaml.dump(docker_image_details)
2050+
else:
2051+
p = Path(path)
2052+
data = p.read_text() if p.exists() else ''
20372053

2038-
hash_alg = hashlib.sha3_384
2039-
2040-
charmresource['fingerprint'] = hash_alg(bytes(data, 'utf-8')).digest()
2054+
self._upload(data, path, application, name, resource_type, pending_id)
20412055

2042-
conn, headers, path_prefix = self.connection().https_connection()
2056+
return resource_map
20432057

2044-
query = "?pendingid={}".format(pending_id)
2045-
url = "{}/applications/{}/resources/{}{}".format(
2046-
path_prefix, application, name, query)
2047-
if resource_type == "oci-image":
2048-
disp = "multipart/form-data; filename=\"{}\"".format(path)
2049-
else:
2050-
disp = "form-data; filename=\"{}\"".format(path)
2058+
def _upload(self, data, path, app_name, res_name, res_type, pending_id):
2059+
conn, headers, path_prefix = self.connection().https_connection()
20512060

2052-
headers['Content-Type'] = 'application/octet-stream'
2053-
headers['Content-Length'] = len(data)
2054-
headers['Content-Sha384'] = charmresource['fingerprint'].hex()
2055-
headers['Content-Disposition'] = disp
2061+
query = "?pendingid={}".format(pending_id)
2062+
url = "{}/applications/{}/resources/{}{}".format(path_prefix, app_name, res_name, query)
2063+
if res_type == "oci-image":
2064+
disp = "multipart/form-data; filename=\"{}\"".format(path)
2065+
else:
2066+
disp = "form-data; filename=\"{}\"".format(path)
20562067

2057-
conn.request('PUT', url, data, headers)
2068+
headers['Content-Type'] = 'application/octet-stream'
2069+
headers['Content-Length'] = len(data)
2070+
headers['Content-Sha384'] = hashlib.sha384(bytes(data, 'utf-8')).hexdigest()
2071+
headers['Content-Disposition'] = disp
20582072

2059-
response = conn.getresponse()
2060-
result = response.read().decode()
2061-
if not response.status == 200:
2062-
raise JujuError(result)
2073+
conn.request('PUT', url, data, headers)
20632074

2064-
return resource_map
2075+
response = conn.getresponse()
2076+
result = response.read().decode()
2077+
if not response.status == 200:
2078+
raise JujuError(result)
20652079

20662080
async def _deploy(self, charm_url, application, series, config,
20672081
constraints, endpoint_bindings, resources, storage,
20682082
channel=None, num_units=None, placement=None,
20692083
devices=None, charm_origin=None, attach_storage=[],
2070-
force=False):
2084+
force=False, server_side_deploy=False):
20712085
"""Logic shared between `Model.deploy` and `BundleHandler.deploy`.
20722086
"""
20732087
log.info('Deploying %s', charm_url)
@@ -2080,7 +2094,7 @@ async def _deploy(self, charm_url, application, series, config,
20802094

20812095
app_facade = client.ApplicationFacade.from_connection(self.connection())
20822096

2083-
if client.ApplicationFacade.best_facade_version(self.connection()) >= 19:
2097+
if server_side_deploy:
20842098
# Call DeployFromRepository
20852099
app = client.DeployFromRepositoryArg(
20862100
applicationname=application,
@@ -2102,10 +2116,18 @@ async def _deploy(self, charm_url, application, series, config,
21022116
revision=charm_origin.revision,
21032117
)
21042118
result = await app_facade.DeployFromRepository([app])
2119+
# Collect the errors
21052120
errors = []
21062121
for r in result.results:
21072122
if r.errors:
21082123
errors.extend([e.message for e in r.errors])
2124+
# Upload pending local resources if any
2125+
for _result in result.results:
2126+
for pending_upload_resource in getattr(_result, 'pendingresourceuploads', []):
2127+
_path = pending_upload_resource.filename
2128+
p = Path(_path)
2129+
data = p.read_text() if p.exists() else ''
2130+
self._upload(data, _path, application, pending_upload_resource.name, 'file', '')
21092131
else:
21102132
app = client.ApplicationDeploy(
21112133
charm_url=charm_url,
@@ -2128,6 +2150,7 @@ async def _deploy(self, charm_url, application, series, config,
21282150
errors = [r.error.message for r in result.results if r.error]
21292151
if errors:
21302152
raise JujuError('\n'.join(errors))
2153+
21312154
return await self._wait_for_new('application', application)
21322155

21332156
async def destroy_unit(self, unit_id, destroy_storage=False, dry_run=False, force=False, max_wait=None):
@@ -2732,10 +2755,10 @@ def _raise_for_status(entities, status):
27322755
# errors to raise at the end
27332756
break
27342757
for unit in app.units:
2735-
if unit.machine is not None and unit.machine.status == "error":
2758+
if raise_on_error and unit.machine is not None and unit.machine.status == "error":
27362759
errors.setdefault("Machine", []).append(unit.machine.id)
27372760
continue
2738-
if unit.agent_status == "error":
2761+
if raise_on_error and unit.agent_status == "error":
27392762
errors.setdefault("Agent", []).append(unit.name)
27402763
continue
27412764
if raise_on_error and unit.workload_status == "error":

tests/integration/test_application.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
logger = logging.getLogger(__name__)
1616

17+
from ..utils import INTEGRATION_TEST_DIR
18+
1719

1820
@base.bootstrapped
1921
async def test_action(event_loop):
@@ -191,17 +193,16 @@ async def test_upgrade_local_charm(event_loop):
191193
@base.bootstrapped
192194
async def test_upgrade_local_charm_resource(event_loop):
193195
async with base.CleanModel() as model:
194-
tests_dir = Path(__file__).absolute().parent
195-
charm_path = tests_dir / 'file-resource-charm'
196+
charm_path = INTEGRATION_TEST_DIR / 'file-resource-charm'
196197
resources = {"file-res": "test.file"}
197198

198199
app = await model.deploy(str(charm_path), resources=resources)
199200
assert 'file-resource-charm' in model.applications
200-
await model.wait_for_idle()
201+
await model.wait_for_idle(raise_on_error=False)
201202
assert app.units[0].agent_status == 'idle'
202203

203204
await app.upgrade_charm(path=charm_path, resources=resources)
204-
await model.wait_for_idle()
205+
await model.wait_for_idle(raise_on_error=False)
205206
ress = await app.get_resources()
206207
assert 'file-res' in ress
207208
assert ress['file-res']

tests/integration/test_model.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -700,7 +700,7 @@ async def test_local_file_resource_charm(event_loop):
700700
app = await model.deploy(str(charm_path), resources=resources)
701701
assert 'file-resource-charm' in model.applications
702702

703-
await model.wait_for_idle()
703+
await model.wait_for_idle(raise_on_error=False)
704704
assert app.units[0].agent_status == 'idle'
705705

706706
ress = await app.get_resources()
@@ -716,7 +716,7 @@ async def test_attach_resource(event_loop):
716716
app = await model.deploy(str(charm_path), resources=resources)
717717
assert 'file-resource-charm' in model.applications
718718

719-
await model.wait_for_idle()
719+
await model.wait_for_idle(raise_on_error=False)
720720
assert app.units[0].agent_status == 'idle'
721721

722722
with open(str(charm_path / 'test.file')) as f:

0 commit comments

Comments
 (0)