55import  ipaddress 
66import  random 
77import  string 
8+ import  subprocess 
89from  dataclasses  import  dataclass , field 
910from  pathlib  import  Path 
1011
@@ -54,9 +55,13 @@ def __init__(self, netns, ssh_key: Path, host, user):
5455
5556        self ._init_connection ()
5657
58+     @property  
59+     def  user_host (self ):
60+         return  f"{ self .user } { self .host }  
61+ 
5762    def  remote_path (self , path ):
5863        """Convert a path to remote""" 
59-         return  f"{ self .user } @ { self . host } { path }  
64+         return  f"{ self .user_host } { path }  
6065
6166    def  _scp (self , path1 , path2 , options ):
6267        """Copy files to/from the VM using scp.""" 
@@ -98,21 +103,12 @@ def run(self, cmd_string, timeout=None, *, check=False, debug=False):
98103
99104        If `debug` is set, pass `-vvv` to `ssh`. Note that this will clobber stderr. 
100105        """ 
101-         command  =  [
102-             "ssh" ,
103-             * self .options ,
104-             f"{ self .user } { self .host }  ,
105-             cmd_string ,
106-         ]
106+         command  =  ["ssh" , * self .options , self .user_host , cmd_string ]
107107
108108        if  debug :
109109            command .insert (1 , "-vvv" )
110110
111-         return  self ._exec (
112-             command ,
113-             timeout ,
114-             check = check ,
115-         )
111+         return  self ._exec (command , timeout , check = check )
116112
117113    def  check_output (self , cmd_string , timeout = None , * , debug = False ):
118114        """Same as `run`, but raises an exception on non-zero return code of remote command""" 
@@ -125,6 +121,26 @@ def _exec(self, cmd, timeout=None, check=False):
125121
126122        return  utils .run_cmd (cmd , check = check , timeout = timeout )
127123
124+     def  Popen (
125+         self ,
126+         cmd : str ,
127+         stdin = subprocess .DEVNULL ,
128+         stdout = subprocess .PIPE ,
129+         stderr = subprocess .PIPE ,
130+         ** kwargs ,
131+     ) ->  subprocess .Popen :
132+         """Execute the command in the guest and return a Popen object. 
133+ 
134+         pop = uvm.ssh.Popen("while true; do echo $(date -Is) $RANDOM; sleep 1; done") 
135+         pop.stdout.read(16) 
136+         """ 
137+         cmd  =  ["ssh" , * self .options , self .user_host , cmd ]
138+         if  self .netns  is  not None :
139+             cmd  =  ["ip" , "netns" , "exec" , self .netns ] +  cmd 
140+         return  subprocess .Popen (
141+             cmd , stdin = stdin , stdout = stdout , stderr = stderr , ** kwargs 
142+         )
143+ 
128144
129145def  mac_from_ip (ip_address ):
130146    """Create a MAC address based on the provided IP. 
0 commit comments