Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NAS-130995 / 25.04 / Allow retrieving logs of apps in crashed state #14419

Merged
merged 1 commit into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/middlewared/middlewared/plugins/apps/crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from .ix_apps.path import get_installed_app_path, get_installed_app_version_path
from .ix_apps.query import list_apps
from .ix_apps.setup import setup_install_app_dir
from .ix_apps.utils import AppState
from .version_utils import get_latest_version_from_app_versions


Expand All @@ -33,7 +34,7 @@ class Config:
'app_entry',
Str('name'),
Str('id'),
Str('state', enum=['STOPPED', 'DEPLOYING', 'RUNNING']),
Str('state', enum=[state.value for state in AppState]),
Bool('upgrade_available'),
Str('human_version'),
Str('version'),
Expand Down
25 changes: 17 additions & 8 deletions src/middlewared/middlewared/plugins/apps/ix_apps/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from .metadata import get_collective_config, get_collective_metadata
from .lifecycle import get_current_app_config
from .path import get_app_parent_config_path
from .utils import get_app_name_from_project_name, normalize_reference, PROJECT_PREFIX
from .utils import AppState, ContainerState, get_app_name_from_project_name, normalize_reference, PROJECT_PREFIX


COMPOSE_SERVICE_KEY: str = 'com.docker.compose.service'
Expand Down Expand Up @@ -86,12 +86,18 @@ def list_apps(
# When we stop docker service and start it again - the containers can be in exited
# state which means we need to account for this.
state = 'STOPPED'
exited_containers = 0
for container in workloads['container_details']:
if container['state'] == 'starting':
state = 'DEPLOYING'
if container['state'] == ContainerState.STARTING.value:
state = AppState.DEPLOYING.value
break
elif container['state'] == 'running':
state = 'RUNNING'
elif container['state'] == ContainerState.RUNNING.value:
state = AppState.RUNNING.value
elif container['state'] == ContainerState.EXITED.value:
exited_containers += 1
else:
if exited_containers != 0 and exited_containers == len(workloads['container_details']):
state = AppState.CRASHED.value

app_metadata = metadata[app_name]
active_workloads = get_default_workload_values() if state == 'STOPPED' else workloads
Expand Down Expand Up @@ -127,7 +133,7 @@ def list_apps(
'name': entry.name,
'id': entry.name,
'active_workloads': get_default_workload_values(),
'state': 'STOPPED',
'state': AppState.STOPPED.value,
'upgrade_available': upgrade_available_for_app(train_to_apps_version_mapping, app_metadata),
'image_updates_available': False,
**app_metadata | {'portals': normalize_portal_uris(app_metadata['portals'], host_ip)}
Expand Down Expand Up @@ -187,9 +193,12 @@ def translate_resources_to_desired_workflow(app_resources: dict) -> dict:

if container['State']['Status'].lower() == 'running':
if health_config := container['State'].get('Health'):
state = 'running' if health_config['Status'] == 'healthy' else 'starting'
if health_config['Status'] == 'healthy':
state = ContainerState.RUNNING.value
else:
state = ContainerState.STARTING.value
else:
state = 'running'
state = ContainerState.RUNNING.value
else:
state = 'exited'

Expand Down
15 changes: 15 additions & 0 deletions src/middlewared/middlewared/plugins/apps/ix_apps/utils.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,23 @@
import enum

from catalog_reader.library import RE_VERSION # noqa
from middlewared.plugins.apps_images.utils import normalize_reference # noqa
from middlewared.plugins.apps.schema_utils import CONTEXT_KEY_NAME # noqa
from middlewared.plugins.apps.utils import IX_APPS_MOUNT_PATH, PROJECT_PREFIX, run # noqa


class AppState(enum.Enum):
CRASHED = 'CRASHED'
DEPLOYING = 'DEPLOYING'
RUNNING = 'RUNNING'
STOPPED = 'STOPPED'


class ContainerState(enum.Enum):
EXITED = 'exited'
RUNNING = 'running'
STARTING = 'starting'


def get_app_name_from_project_name(project_name: str) -> str:
return project_name[len(PROJECT_PREFIX):]
5 changes: 3 additions & 2 deletions src/middlewared/middlewared/plugins/apps/logs.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from middlewared.service import CallError
from middlewared.validators import Range

from .ix_apps.utils import AppState
from .ix_apps.docker.utils import get_docker_client


Expand Down Expand Up @@ -38,8 +39,8 @@ def __init__(self, *args, **kwargs):

def validate_log_args(self, app_name, container_id):
app = self.middleware.call_sync('app.get_instance', app_name)
if app['state'] not in ('RUNNING', 'DEPLOYING'):
raise CallError(f'App "{app_name}" is not running')
if app['state'] not in (AppState.CRASHED.value, AppState.RUNNING.value, AppState.DEPLOYING.value):
raise CallError(f'Unable to retrieve logs of stopped {app_name!r} app')

if not any(c['id'] == container_id for c in app['active_workloads']['container_details']):
raise CallError(f'Container "{container_id}" not found in app "{app_name}"', errno=errno.ENOENT)
Expand Down
5 changes: 4 additions & 1 deletion src/middlewared/middlewared/plugins/apps/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from middlewared.utils.gpu import get_nvidia_gpus

from .ix_apps.utils import ContainerState
from .resources_utils import get_normalized_gpu_choices


Expand Down Expand Up @@ -43,7 +44,9 @@ async def container_ids(self, app_name, options):
'id': c['id'],
} for c in (
await self.middleware.call('app.get_instance', app_name)
)['active_workloads']['container_details'] if (options['alive_only'] is False or c['state'] == 'running')
)['active_workloads']['container_details'] if (
options['alive_only'] is False or ContainerState(c['state']) == ContainerState.RUNNING
)
}

@accepts(Str('app_name'), roles=['APPS_READ'])
Expand Down
Loading