mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-10-11 00:33:45 +08:00
Reconnect persistent event assets after checkout replacement
This commit is contained in:
@@ -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=""
|
||||
|
||||
@@ -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()
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user