Reconnect persistent event assets after checkout replacement

This commit is contained in:
firestar5683
2026-09-19 15:26:19 -07:00
parent a7551f9df4
commit f2a16ec721
4 changed files with 100 additions and 0 deletions
+1
View File
@@ -3,6 +3,7 @@ set -euo pipefail
cd /data/openpilot
env -u OPENPILOT_PREFIX -u PARAMS_ROOT /usr/local/venv/bin/python -c 'from openpilot.common.params import Params; assert not Params().get_bool("IsOnroad")'
cd /data/roadscore
/usr/local/venv/bin/python prototype/persistent_assets.py
exec 9>generated/native_session.lock
flock -n 9 || { echo "Another native RoadScore session owns this bench"; exit 1; }
power_pid=""
+39
View File
@@ -0,0 +1,39 @@
"""Reconnect a replaced checkout to previously provisioned event assets."""
import argparse
from pathlib import Path
def restore_links(root, data=Path('/data')):
root, data = Path(root), Path(data)
assets, state = data / 'roadscore-event-assets', data / 'roadscore-event-state'
if not assets.exists() and not state.exists():
return [] # Existing installations retain their original layout.
mapping = {name: state / name for name in ('generated', 'results', 'assets', 'runtime.json')}
mapping.update({f'experiments/ace_chestnut_20260916/{name}': assets / 'ace' / name
for name in ('weights', 'vae_weights', 'profiles')})
missing = []
for relative, target in mapping.items():
valid = target.is_file() if relative == 'runtime.json' else target.is_dir()
if not valid:
raise RuntimeError(f'Persistent RoadScore asset missing: {target}; restore it before launch')
link = root / relative
if link.is_symlink():
if link.resolve() != target.resolve():
raise RuntimeError(f'Refusing to replace another link: {link}')
elif link.exists():
raise RuntimeError(f'Refusing to overwrite checkout data: {link}')
else:
missing.append((link, target))
# Validate the entire map before changing anything. Never delete or move data.
for link, target in missing:
link.parent.mkdir(parents=True, exist_ok=True)
link.symlink_to(target, target_is_directory=target.is_dir())
return [str(link) for link, _ in missing]
if __name__ == '__main__':
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--root', type=Path, default=Path('/data/roadscore'))
args = parser.parse_args()
for path in restore_links(args.root):
print(f'Reconnected persistent RoadScore path: {path}')
@@ -0,0 +1,59 @@
import tempfile
import unittest
from pathlib import Path
from persistent_assets import restore_links
class PersistentAssetsTest(unittest.TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory()
self.addCleanup(self.temp.cleanup)
self.data = Path(self.temp.name)
self.root = self.data / 'checkout' / 'roadscore'
self.root.mkdir(parents=True)
def provision(self):
for name in ('generated', 'results', 'assets'):
(self.data / 'roadscore-event-state' / name).mkdir(parents=True)
(self.data / 'roadscore-event-state/runtime.json').write_text('{}')
for name in ('weights', 'vae_weights', 'profiles'):
(self.data / 'roadscore-event-assets/ace' / name).mkdir(parents=True)
def test_replaced_checkout_reconnects_without_copying_and_is_idempotent(self):
self.provision()
marker = self.data / 'roadscore-event-assets/ace/weights/model'
marker.write_bytes(b'preserved')
self.assertEqual(len(restore_links(self.root, self.data)), 7)
self.assertEqual(restore_links(self.root, self.data), [])
self.assertEqual((self.root / 'experiments/ace_chestnut_20260916/weights/model').read_bytes(), b'preserved')
def test_existing_data_is_never_overwritten_or_partially_modified(self):
self.provision()
(self.root / 'runtime.json').write_text('local settings')
with self.assertRaisesRegex(RuntimeError, 'overwrite'):
restore_links(self.root, self.data)
self.assertEqual((self.root / 'runtime.json').read_text(), 'local settings')
self.assertFalse((self.root / 'generated').exists())
def test_wrong_link_rejected_without_modifications(self):
self.provision()
(self.root / 'results').symlink_to(self.data / 'other')
with self.assertRaisesRegex(RuntimeError, 'another link'):
restore_links(self.root, self.data)
self.assertFalse((self.root / 'generated').exists())
def test_incomplete_persistent_installation_rejected_before_linking(self):
self.provision()
(self.data / 'roadscore-event-assets/ace/profiles').rmdir()
with self.assertRaisesRegex(RuntimeError, 'missing'):
restore_links(self.root, self.data)
self.assertFalse((self.root / 'generated').exists())
def test_unprovisioned_installation_unchanged(self):
self.assertEqual(restore_links(self.root, self.data), [])
self.assertEqual(list(self.root.iterdir()), [])
if __name__ == '__main__':
unittest.main()
+1
View File
@@ -10,6 +10,7 @@ if [ -e /data/roadscore ]; then
else
ln -s "$root" /data/roadscore
fi
/usr/local/venv/bin/python "$root/prototype/persistent_assets.py" --root "$root"
mkdir -p "$root"/{generated,results/event_night_one,routes,assets,.cache/uv,tmp} /data/roadscore-feasibility/{cache,tmp} /data/sa3-feasibility
# AGNOS /home has only a small overlay. Never use it for package downloads.
export UV_CACHE_DIR="$root/.cache/uv" TMPDIR="$root/tmp"