feat(oci): support custom Compose workers and migration jobs

This commit is contained in:
Jonatan
2026-10-28 08:41:41 +01:00
parent b383fb9311
commit 3a0a7eaf14
44 changed files with 3267 additions and 110 deletions
@@ -29,6 +29,9 @@ TRANSACTION = ROOT / 'oci/remote/oci_stack_transaction.py'
def extracted(path, name, scope):
if name == 'remove':
# Operational cache cleanup is exercised by the engine tests; keep wording tests inert.
scope.setdefault('compose_jobs', SimpleNamespace(forget_completed=lambda *args: None))
node = next(n for n in ast.parse(path.read_text()).body
if isinstance(n, ast.FunctionDef) and n.name == name)
exec(compile(ast.Module(body=[node], type_ignores=[]), str(path), 'exec'), scope)
@@ -35,6 +35,9 @@ KEYS = (PREVIEW, CONFIRM, RESULT, BRIDGE, BRIDGE_FAILURE, SUCCESS, FIREWALL, POR
def extract(path, name, scope):
if name == 'remove':
# Operational cache cleanup is exercised by the engine tests; keep wording tests inert.
scope.setdefault('compose_jobs', SimpleNamespace(forget_completed=lambda *args: None))
node = next(n for n in ast.parse(path.read_text()).body if isinstance(n, ast.FunctionDef) and n.name == name)
exec(compile(ast.Module(body=[node], type_ignores=[]), str(path), 'exec'), scope)
return scope[name]
@@ -1,9 +1,13 @@
"""The watchdog of an OCI application is reachable from the Monitor and worded in every language."""
import ast
import copy
import json
from pathlib import Path
import re
from unittest import TestCase
from unittest.mock import Mock
from types import SimpleNamespace
from typing import Any
ROOT = Path(__file__).resolve().parents[3]
LOCALES = ('en', 'es', 'de', 'fr', 'it', 'pt', 'sk', 'sv')
@@ -65,9 +69,45 @@ class OciWatchdogInstaller(TestCase):
self.assertLess(install.index('deployment = build_deployment(candidate, wizard, mode)'), asked)
self.assertLess(asked, install.index('approved = wizard.review('))
self.assertLess(install.index('watchdog = bool(deployment.pop("watchdog", False))'), install.index('run_remote_install('))
self.assertIn('set_watchdog(PROJECT_ROOT, sorted(vmids), True)', install)
self._assert_jobs_are_excluded_from_installed_watchdog()
self.assertIn('row(translate("Watchdog"), _yes_no(plan["watchdog"]))', self.CLI)
def _assert_jobs_are_excluded_from_installed_watchdog(self):
node = next(item for item in ast.parse(self.CLI).body
if isinstance(item, ast.FunctionDef) and item.name == 'install_template')
class InertImports(ast.NodeTransformer):
def visit_ImportFrom(self, item):
return None
node = InertImports().visit(node)
deployment = {'services': [
{'name': 'migration', 'deployment': {'execution_mode': 'one-shot'}},
{'name': 'database', 'deployment': {}},
{'name': 'worker', 'deployment': {}}]}
result = {'vmid': 4800, 'stack_vmids': {'web': 4800, 'migration': 4801,
'database': 4802, 'worker': 4803}}
set_watchdog = Mock(return_value=True)
class UI:
def confirm(self, *args, **kwargs): return True
def review(self, *args, **kwargs): return True
def close(self): pass
def install(project, template, plan, target):
self.assertNotIn('watchdog', plan)
return result
scope = {'Any': Any, 'copy': copy, 'BacktrackUI': lambda ui: ui,
'RestartWizard': RuntimeError, 'InstallError': ValueError,
'build_deployment': lambda *args: copy.deepcopy(deployment),
'watchdog_default': lambda *args: False,
'translate': lambda value: value, '_deployment_summary_text': lambda *args: '',
'PROJECT_ROOT': Path('/inert'), 'source_text': lambda value: 'test', 'APP_TITLE': 'test',
'console': SimpleNamespace(**{name: lambda *args: None for name in
('show_logo', 'msg_title', 'msg_error', 'msg_ok', 'msg_warn', 'wait_for_enter')}),
'run_remote_install': install, 'carry_records': lambda *args, **kwargs: None,
'images': SimpleNamespace(offer_removal=lambda *args: (False, None)),
'_print_installation_summary': lambda *args: None, 'set_watchdog': set_watchdog}
exec(compile(ast.fix_missing_locations(ast.Module(body=[node], type_ignores=[])), 'cli.py', 'exec'), scope)
scope['install_template'](UI(), {'catalog_ui': {'title': {'en_US': 'test'}}}, 'test', 'advanced')
set_watchdog.assert_called_once_with(Path('/inert'), [4800, 4802, 4803], True)
def test_both_management_menus_offer_it(self):
self.assertEqual(self.MENU.count("('watchdog', "), 2)
self.assertEqual(self.MENU.count("if action == 'watchdog':"), 2)
+4 -1
View File
@@ -85,7 +85,10 @@ class OciWordingTests(TestCase):
template = function(CUSTOM, 'template_from_compose',
_services=lambda doc: doc['services'], _template_from_stack=stack)
self.assertIs(template(json.dumps(document)), converted)
stack.assert_called_once_with(document, None)
stack.assert_called_once()
self.assertEqual(stack.call_args.args, (document, None))
self.assertFalse(stack.call_args.kwargs['interpolated'])
self.assertIsNone(stack.call_args.kwargs.get('image_entrypoints'))
def test_empty_paste_is_specific_and_nonempty_unchanged(self):
for title, expected in ((None, 'No Compose file was given'),