test: verify rollback restores originals on partial deployment failure
Rebuild & Deploy / rebuild (push) Failing after 15s
Rebuild & Deploy / rebuild (push) Failing after 15s
This commit is contained in:
1 parent
df25c7336c
commit
9cc6147c6f
3 files changed
+60
-1
No files matched your search
@@ -64,6 +64,9 @@ also passes `model_options.reasoning.enabled: false` and forwards content only.
|
|||||||
narrowly trusted proxy identity/failed-login throttling regressions.
|
narrowly trusted proxy identity/failed-login throttling regressions.
|
||||||
- Provisioner tests: **9 passed**.
|
- Provisioner tests: **9 passed**.
|
||||||
- Authenticated bootstrap tests: **2 passed**.
|
- Authenticated bootstrap tests: **2 passed**.
|
||||||
|
- Offline deployment rollback tests: **3 passed**; injected second-service stop,
|
||||||
|
rename and replacement-run failures restore both original immutable IDs and
|
||||||
|
running names, without removing originals.
|
||||||
- LiveKit Hermes stream tests: **16 passed** (no reasoning fallback or replay,
|
- LiveKit Hermes stream tests: **16 passed** (no reasoning fallback or replay,
|
||||||
approvals fail safely, total timeout, cancellation, visible timer progress,
|
approvals fail safely, total timeout, cancellation, visible timer progress,
|
||||||
out-of-band notice/first-answer callback contracts).
|
out-of-band notice/first-answer callback contracts).
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@
|
|||||||
```sh
|
```sh
|
||||||
python -m venv .venv-tests
|
python -m venv .venv-tests
|
||||||
.venv-tests/bin/python -m pip install pytest fastapi httpx
|
.venv-tests/bin/python -m pip install pytest fastapi httpx
|
||||||
.venv-tests/bin/python -m pytest --noconftest tests/unit/test_accounts.py tests/unit/test_provisioner.py tests/unit/test_token_bootstrap.py -q
|
.venv-tests/bin/python -m pytest --noconftest tests/unit/test_accounts.py tests/unit/test_provisioner.py tests/unit/test_token_bootstrap.py tests/unit/test_deploy_rollback.py -q
|
||||||
agent/.venv/bin/python -m unittest discover -s tests/unit -p test_hermes_llm.py -v
|
agent/.venv/bin/python -m unittest discover -s tests/unit -p test_hermes_llm.py -v
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
"""Offline destructive-step failures restore both original container IDs."""
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
FAKE = r'''#!/usr/bin/env python3
|
||||||
|
import json,os,sys
|
||||||
|
p=os.environ['STATE'];s=json.load(open(p));a=sys.argv[1:]
|
||||||
|
def save():open(p,'w').write(json.dumps(s))
|
||||||
|
def item(n):return next((x for x in s['containers'] if x['name']==n or x['id']==n),None)
|
||||||
|
cmd=a[0];n=a[-1];c=item(n)
|
||||||
|
if cmd=='inspect':
|
||||||
|
if not c:sys.exit(1)
|
||||||
|
if '--format' in a:
|
||||||
|
fmt=a[a.index('--format')+1]
|
||||||
|
print(c['id'] if '.Id' in fmt else '/'+c['name'] if '.Name' in fmt else 'healthy')
|
||||||
|
else:print(json.dumps([c]))
|
||||||
|
elif cmd=='stop':
|
||||||
|
c['running']=False;save()
|
||||||
|
if n=='hope-control' and s['failure']=='stop':sys.exit(7)
|
||||||
|
elif cmd=='rename':
|
||||||
|
c=item(a[1])
|
||||||
|
if a[1]=='hope-control' and s['failure']=='rename':sys.exit(8)
|
||||||
|
c['name']=a[2];save()
|
||||||
|
elif cmd=='start':c['running']=True;save()
|
||||||
|
elif cmd=='rm':
|
||||||
|
assert c['id'] not in ['original-voice','original-control']
|
||||||
|
s['containers'].remove(c);save()
|
||||||
|
elif cmd=='run':
|
||||||
|
name=a[a.index('--name')+1]
|
||||||
|
if name=='voice' and s['failure']=='run':sys.exit(9)
|
||||||
|
s['containers'].append({'name':name,'id':'new-'+name,'running':True});save()
|
||||||
|
else:sys.exit(2)
|
||||||
|
'''
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('failure',['stop','rename','run'])
|
||||||
|
def test_partial_deployment_restores_originals(tmp_path,failure):
|
||||||
|
root=tmp_path/'state';root.mkdir()
|
||||||
|
for n in ['runtime.env','livekit.yaml','voice.env']:(root/n).touch()
|
||||||
|
for n in ['accounts','hermes','certs']:(root/n).mkdir()
|
||||||
|
state=root/'fake.json'
|
||||||
|
state.write_text(json.dumps({'failure':failure,'containers':[{'name':'voice','id':'original-voice','running':True},{'name':'hope-control','id':'original-control','running':True}]}))
|
||||||
|
bindir=tmp_path/'bin';bindir.mkdir()
|
||||||
|
for name,text in [('docker',FAKE),('curl','#!/bin/sh\nexit 0\n')]:
|
||||||
|
p=bindir/name;p.write_text(text);p.chmod(0o755)
|
||||||
|
source=(Path(__file__).parents[2]/'scripts/deploy-unraid.sh').read_text()
|
||||||
|
source=source.replace('ROOT=/mnt/user/appdata/hope-voice',f'ROOT={root}').replace('/mnt/user/dev/hope-voice-api/certs',str(root/'certs'))
|
||||||
|
script=tmp_path/'deploy.sh';script.write_text(source)
|
||||||
|
env=dict(os.environ,STATE=str(state),PATH=str(bindir)+os.pathsep+os.environ['PATH'])
|
||||||
|
result=subprocess.run(['bash',str(script)],env=env,capture_output=True,text=True)
|
||||||
|
assert result.returncode!=0,result.stdout+result.stderr
|
||||||
|
data=json.loads(state.read_text())
|
||||||
|
assert sorted((x['name'],x['id'],x['running']) for x in data['containers'])==[('hope-control','original-control',True),('voice','original-voice',True)]
|
||||||
Reference in new issue
Block a user