Files
ProxMenux/oci/tests/test_menu_performance.py

110 lines
6.0 KiB
Python

"""Menu speedups avoid unused probes, retaining confirmation and capacity checks."""
from pathlib import Path
import sys
import tempfile
import unittest
from unittest.mock import Mock,patch
ROOT=Path(__file__).resolve().parents[1]
sys.path[:0]=[str(ROOT/'src'),str(ROOT/'remote')]
from proxmenux_oci import management as menu
import oci_instances as instances
import oci_instance_reconcile as reconcile
import oci_remove as remover
import oci_work_backup as backup
class UpdateEntryTests(unittest.TestCase):
def run_update(self,approved,ready):
events=[]
record={'status':'installed','vmid':110,'deployment':{},'template':{}}
row={'vmid':110,'reason':'matched','stack':False,'pending':False,'status':'installed'}
ui=Mock();ui.unattended=True
ui.review.side_effect=lambda *a,**kw:events.append('review') or approved
with patch.object(menu,'carry_records',return_value={}) as carry, \
patch.object(menu,'check_selected',return_value=row), \
patch.object(menu,'_selected_configuration',return_value=b'current config'), \
patch.object(instances,'read',return_value=record),patch.object(reconcile,'propose',return_value=None), \
patch.object(menu,'_work_backup_ready',side_effect=lambda *a:events.append('backup') or ready), \
patch.object(menu,'_run_lifecycle',side_effect=lambda *a:events.append('lifecycle') or True):
result=menu.manage_instance(ROOT,ui,row,action='update')
carry.assert_called_once_with(ROOT,vmids=[110],verify=True)
return result,events
def test_declining_update_does_not_probe_disks_or_storages(self):
result,events=self.run_update(False,False)
self.assertFalse(result);self.assertEqual(events,['review'])
def test_accepted_update_checks_backup_space_before_starting(self):
result,events=self.run_update(True,True)
self.assertTrue(result);self.assertEqual(events,['review','backup','lifecycle'])
def test_insufficient_space_still_prevents_the_update(self):
result,events=self.run_update(True,False)
self.assertFalse(result);self.assertEqual(events,['review','backup'])
class BackupDestinationTests(unittest.TestCase):
def status(self,local_free,saved_free=None,only_needed=True):
with tempfile.TemporaryDirectory() as directory:
root=Path(directory);saved= root/'nas';saved.mkdir()
record={'deployment':{backup.KEY:'nas'}}
with patch.object(backup,'application',return_value=(110,[110,111])), \
patch.object(instances,'read',return_value=record), \
patch.object(backup,'required',return_value=100) as required, \
patch.object(backup,'available',side_effect=lambda p:saved_free if Path(p)==saved else local_free), \
patch.object(backup,'storage_path',return_value=saved if saved_free is not None else None) as path, \
patch.object(backup,'file_storages',return_value=[{'storage':'enough','free':200},{'storage':'small','free':50}]) as alternatives:
result=backup.status(root,110,only_needed=only_needed)
required.assert_called_once_with([110,111])
return result,path.call_count,alternatives.call_count
def test_local_room_avoids_saved_storage_and_alternative_queries(self):
result,saved,alternatives=self.status(200,saved_free=1000)
self.assertTrue(result['fits']);self.assertEqual((saved,alternatives),(0,0))
def test_saved_destination_that_fits_avoids_alternative_queries(self):
result,saved,alternatives=self.status(50,saved_free=200)
self.assertTrue(result['saved_fits']);self.assertEqual((saved,alternatives),(1,0))
def test_when_neither_fits_all_available_destinations_are_checked(self):
result,saved,alternatives=self.status(50,saved_free=40)
self.assertFalse(result['fits']);self.assertFalse(result['saved_fits'])
self.assertEqual(result['candidates'],[{'storage':'enough','free':200}])
self.assertEqual(alternatives,1)
def test_missing_saved_storage_still_offers_alternatives(self):
result,saved,alternatives=self.status(50)
self.assertEqual(result['candidates'],[{'storage':'enough','free':200}])
self.assertEqual(alternatives,1)
def test_complete_status_keeps_legacy_fields_and_all_candidates(self):
result,saved,alternatives=self.status(200,saved_free=300,only_needed=False)
self.assertTrue(result['fits']);self.assertTrue(result['saved_fits'])
self.assertEqual((saved,alternatives),(1,1))
self.assertEqual(result['candidates'],[{'storage':'enough','free':200}])
class RemovalPreviewTests(unittest.TestCase):
def preview(self,reader):
record={'vmid':110,'deployment':{},'template':{'catalog_ui':{'title':'Example'}}}
with patch.object(remover,'members_of',return_value=(110,record,[110])), \
patch.object(instances,'read',return_value=record), \
patch.object(menu,'_selected_configuration',side_effect=reader), \
patch.object(remover,'guest_config',side_effect=AssertionError('Duplicate pct process')), \
patch.object(remover,'host_directories',return_value=[]), \
patch.object(remover,'private_bridge',return_value=None):
return menu._removal_summary(ROOT,110)
def test_preview_reuses_verified_config_and_keeps_volume_and_host_path_details(self):
config=b'hostname: example\nrootfs: local:subvol-110-disk-0,size=8G\nmp0: /mnt/media,mp=/media,backup=0\n'
text=self.preview(lambda v:config)
self.assertIn('8G',text);self.assertIn('/mnt/media',text)
self.assertIn('not targeted for removal',text)
def test_missing_guest_can_still_be_reviewed_without_a_second_probe(self):
text=self.preview(lambda v:(_ for _ in ()).throw(RuntimeError('guest missing')))
self.assertIn('CT 110',text);self.assertIn('rootfs -',text)
if __name__=='__main__':unittest.main()