Skip to content

Commit

Permalink
Merge branch 'master' into one-track-to-rule-them-all
Browse files Browse the repository at this point in the history
  • Loading branch information
cderici authored Feb 5, 2024
2 parents 1109846 + 16149db commit 38cf031
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 26 deletions.
32 changes: 22 additions & 10 deletions juju/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,20 @@
import hashlib
import json
import logging
import typing
from pathlib import Path

from . import model, tag, utils, jasyncio
from .url import URL
from .status import derive_status
from . import jasyncio, model, tag, utils
from .annotationhelper import _get_annotations, _set_annotations
from .client import client
from .errors import JujuError, JujuApplicationConfigError
from .bundle import get_charm_series, is_local_charm
from .placement import parse as parse_placement
from .client import client
from .errors import JujuApplicationConfigError, JujuError
from .origin import Channel, Source
from .placement import parse as parse_placement
from .relation import Relation
from .status import derive_status
from .url import URL
from .utils import block_until

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -59,14 +62,14 @@ def subordinate_units(self):
return [u for u in self.units if u.is_subordinate]

@property
def relations(self):
def relations(self) -> typing.List[Relation]:
return [rel for rel in self.model.relations if rel.matches(self.name)]

def related_applications(self, endpoint_name=None):
apps = {}
for rel in self.relations:
if rel.is_peer:
local_ep, remote_ep = rel.endpoints[0]
local_ep, remote_ep = rel.endpoints
else:
def is_us(ep):
return ep.application.name == self.name
Expand Down Expand Up @@ -191,12 +194,13 @@ async def scale(self, scale=None, scale_change=None):
scale_change=scale_change)
])

async def destroy_relation(self, local_relation, remote_relation):
async def destroy_relation(self, local_relation, remote_relation, block_until_done: bool = False):
"""Remove a relation to another application.
:param str local_relation: Name of relation on this application
:param str remote_relation: Name of relation on the other
application in the form '<application>[:<relation_name>]'
:param bool block_until_done: Wait until the relation is completely removed.
"""
if ':' not in local_relation:
Expand All @@ -207,8 +211,16 @@ async def destroy_relation(self, local_relation, remote_relation):
log.debug(
'Destroying relation %s <-> %s', local_relation, remote_relation)

return await app_facade.DestroyRelation(endpoints=[
await app_facade.DestroyRelation(endpoints=[
local_relation, remote_relation])
if block_until_done:
await block_until(
lambda: not any(
relation.matches(local_relation, remote_relation)
for relation in self.relations
)
)

remove_relation = destroy_relation

async def destroy_unit(self, *unit_names):
Expand Down
10 changes: 0 additions & 10 deletions juju/client/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import json
import logging
import ssl
import signal
import urllib.request
import weakref
from http.client import HTTPSConnection
Expand Down Expand Up @@ -425,10 +424,6 @@ def _exit_tasks():
for task in jasyncio.all_tasks():
task.cancel()

loop = jasyncio.get_running_loop()
for sig in (signal.SIGINT, signal.SIGTERM):
loop.add_signal_handler(sig, _exit_tasks)

return (await websockets.connect(
url,
ssl=self._get_ssl(cacert),
Expand Down Expand Up @@ -473,11 +468,6 @@ async def close(self, to_reconnect=False):
if self.proxy is not None:
self.proxy.close()

# Remove signal handlers
loop = jasyncio.get_running_loop()
for sig in (signal.SIGINT, signal.SIGTERM):
loop.remove_signal_handler(sig)

async def _recv(self, request_id):
if not self.is_open:
raise websockets.exceptions.ConnectionClosed(0, 'websocket closed')
Expand Down
14 changes: 12 additions & 2 deletions juju/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -1000,7 +1000,15 @@ async def remove_storage(self, *storage_ids, force=False, destroy_storage=False)
if ret.results[0].error:
raise JujuError(ret.results[0].error.message)

async def remove_application(self, app_name, block_until_done=False, force=False, destroy_storage=False, no_wait=False):
async def remove_application(
self,
app_name,
block_until_done=False,
force=False,
destroy_storage=False,
no_wait=False,
timeout=None
):
"""Removes the given application from the model.
:param str app_name: Name of the application
Expand All @@ -1009,6 +1017,8 @@ async def remove_application(self, app_name, block_until_done=False, force=False
:param bool no_wait: Rush through application removal without waiting for each individual step to complete (=false)
:param bool block_until_done: Ensure the app is removed from the
model when returned
:param int timeout: Raise asyncio.exceptions.TimeoutError if the application is not removed
within the timeout period.
"""
if app_name not in self.applications:
raise JujuError("Given application doesn't seem to appear in the\
Expand All @@ -1019,7 +1029,7 @@ async def remove_application(self, app_name, block_until_done=False, force=False
no_wait=no_wait,
)
if block_until_done:
await self.block_until(lambda: app_name not in self.applications)
await self.block_until(lambda: app_name not in self.applications, timeout=timeout)

async def block_until(self, *conditions, timeout=None, wait_period=0.5):
"""Return only after all conditions are true.
Expand Down
32 changes: 28 additions & 4 deletions tests/integration/test_application.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
# Copyright 2023 Canonical Ltd.
# Licensed under the Apache V2, see LICENCE file for details.

import logging
from pathlib import Path
import asyncio

import pytest
import logging

from .. import base
from juju import jasyncio, errors
from juju.url import URL, Schema
from juju import errors, jasyncio
from juju.application import Application
from juju.client import client
from juju.url import URL, Schema

from .. import base

MB = 1

Expand Down Expand Up @@ -324,10 +327,31 @@ async def test_app_remove_wait_flag():
assert a_name not in model.applications


@base.bootstrapped
async def test_app_remove_timeout():
async with base.CleanModel() as model:
app = await model.deploy('juju-qa-test')
await model.wait_for_idle(status="active")

with pytest.raises(asyncio.TimeoutError):
await model.remove_application(app.name, block_until_done=True, timeout=1)


@base.bootstrapped
async def test_app_charm_name():
async with base.CleanModel() as model:
app = await model.deploy('ubuntu')
await model.wait_for_idle(status="active")
assert 'ubuntu' in app.charm_url
assert 'ubuntu' == app.charm_name


@base.bootstrapped
async def test_app_relation_destroy_block_until_done():
async with base.CleanModel() as model:
app: Application = await model.deploy('docker-registry')
rsa: Application = await model.deploy("easyrsa")
relation = await app.relate('cert-provider', rsa.name)
await model.wait_for_idle(status="active")
await app.destroy_relation('cert-provider', rsa.name, block_until_done=True)
assert relation not in app.relations

0 comments on commit 38cf031

Please sign in to comment.