okami.mom/albedo
git clone https://git.nkp.pet/okami.mom/albedo
61c43ca
main
1"""Sessions at remote locations: `[user@]host:/abs/path` workspaces. 2 3A remote location is a stored key the daemon parses and labels without 4connecting to the host. These cover what a client sees: the canonical 5spelling and parsed `location`, the host label's `user@` elision against 6`ssh -G`, rejected forms, and moving between local and remote. What needs 7the host itself (kernels, `~`, folders, project files) is 8remote_kernels_test.py, over a loopback ssh. 9""" 10 11import json 12import shutil 13import subprocess 14import unittest 15import urllib .error 16import urllib .parse 17 18from harness import Albedo ,exclusive ,operation_id 19 20HOST = "albedo-e2e-nowhere" 21 22 23def ssh_user (host ): 24"""The user ssh would pick for `host`, or None without ssh.""" 25if shutil .which ("ssh" )is None : 26return None 27output = subprocess .run ( 28 ["ssh" ,"-G" ,host ],capture_output = True ,text = True ,check = True 29 ).stdout 30return next ( 31line .split (" " ,1 )[1 ] 32for line in output .splitlines () 33if line .startswith ("user " ) 34 ) 35 36 37class LocationsTest (unittest .TestCase ): 38def setUp (self ): 39self .app = Albedo ().__enter__ () 40self .addCleanup (self .app .__exit__ ,None ,None ,None ) 41 42def api (self ,path ,body = None ,** options ): 43with self .app .api (path ,body ,** options )as response : 44return json .load (response ) 45 46def failure (self ,path ,body = None ,** options ): 47with self .assertRaises (urllib .error .HTTPError )as caught : 48self .api (path ,body ,** options ) 49return caught .exception .code ,json .load (caught .exception )["detail" ] 50 51def create (self ,workspace ): 52return self .api ( 53f"/sessions/ { operation_id () } " , 54 { 55"kind" :"new" , 56"workspace" :workspace , 57"provider_profile" :self .app .profile , 58 }, 59method = "PUT" , 60headers = {"If-None-Match" :"*" }, 61 ) 62 63def move (self ,session ,workspace ): 64snapshot = self .api (f"/sessions/ { session } ?tail=0" ) 65resource = snapshot ["configuration_resource" ] 66return self .api ( 67resource ["url" ], 68 {"workspace" :workspace ,"family_revision" :snapshot ["family_revision" ]}, 69method = "PATCH" , 70headers = {"If-Match" :resource ["etag" ]}, 71 )["session" ] 72 73def test_remote_workspace_is_stored_canonically_with_its_location (self ): 74created = self .create (f"mayer@ { HOST } :/home/mayer//proj/./albedo/" ) 75canonical = f"mayer@ { HOST } :/home/mayer/proj/albedo" 76self .assertEqual (created ["workspace" ],canonical ) 77location = created ["location" ] 78self .assertEqual ( 79 (location ["host" ],location ["user" ],location ["path" ]), 80 (HOST ,"mayer" ,"/home/mayer/proj/albedo" ), 81 ) 82query = urllib .parse .urlencode ({"workspace" :canonical }) 83listed = next ( 84s 85for s in self .api ("/sessions?" + query )["items" ] 86if s ["id" ]== created ["id" ] 87 ) 88self .assertEqual (listed ["workspace" ],canonical ) 89 90local = self .create (str (self .app .workspace )) 91self .assertEqual (local ["workspace" ],str (self .app .workspace )) 92self .assertEqual ( 93local ["location" ], 94 { 95"host" :None , 96"user" :None , 97"path" :str (self .app .workspace ), 98"label" :None , 99 }, 100 ) 101 102def test_host_label_drops_the_user_ssh_would_pick_anyway (self ): 103self .assertEqual (self .create (f" { HOST } :/srv" )["location" ]["label" ],HOST ) 104other = "someone-else" 105self .assertEqual ( 106self .create (f" { other } @ { HOST } :/srv" )["location" ]["label" ], 107f" { other } @ { HOST } " , 108 ) 109default = ssh_user (HOST ) 110if default is None : 111self .skipTest ("ssh is not installed: the label keeps any user" ) 112self .assertEqual ( 113self .create (f" { default } @ { HOST } :/srv" )["location" ]["label" ],HOST 114 ) 115 116# `albedo new` takes the active profile, which other tests on the shared 117# daemon rewrite; a daemon of its own keeps that profile this fixture's. 118# exclusive: CLI new depends on the active provider profile other fixtures rewrite 119@exclusive 120def test_the_cli_starts_a_remote_session_without_resolving_it_locally (self ): 121session = json .loads (self .app .cli ("new" ,f" { HOST } :/srv/cli" ))["session" ] 122listed = next (s for s in self .api ("/sessions" )["items" ]if s ["id" ]== session ) 123self .assertEqual (listed ["workspace" ],f" { HOST } :/srv/cli" ) 124 125def test_unusable_locations_are_rejected (self ): 126for workspace ,said in ( 127 (f" { HOST } :proj" ,"must be absolute" ), 128 (f" { HOST } :" ,"must be absolute" ), 129 ("-oProxyCommand=touch:/tmp" ,"not a valid host" ), 130 (f"-l@ { HOST } :/srv" ,"not a valid user" ), 131 ("relative/path" ,"absolute path or host:/absolute/path" ), 132 ("dir/with:colon" ,"absolute path or host:/absolute/path" ), 133 ): 134with self .subTest (workspace = workspace ): 135status ,message = self .failure ( 136f"/sessions/ { operation_id () } " , 137 { 138"kind" :"new" , 139"workspace" :workspace , 140"provider_profile" :self .app .profile , 141 }, 142method = "PUT" , 143headers = {"If-None-Match" :"*" }, 144 ) 145self .assertEqual (status ,400 ) 146self .assertIn (said ,message ) 147 148def test_a_session_moves_to_a_remote_location_and_back (self ): 149session = self .app .session () 150moved = self .move (session ,f" { HOST } :/srv//app/" ) 151self .assertEqual (moved ["workspace" ],f" { HOST } :/srv/app" ) 152self .assertEqual (moved ["location" ]["host" ],HOST ) 153with self .assertRaises (urllib .error .HTTPError )as invalid : 154self .move (session ,f" { HOST } :app" ) 155self .assertEqual (invalid .exception .code ,400 ) 156self .assertIn ("must be absolute" ,json .load (invalid .exception )["detail" ]) 157back = self .move (session ,str (self .app .workspace )) 158self .assertEqual (back ["workspace" ],str (self .app .workspace )) 159self .assertIsNone (back ["location" ]["host" ]) 160 161 162if __name__ == "__main__" : 163unittest .main ()