55import  ipaddress 
66import  random 
77import  string 
8+ import  subprocess 
89from  dataclasses  import  dataclass , field 
910from  pathlib  import  Path 
1011
@@ -54,9 +55,14 @@ def __init__(self, netns, ssh_key: Path, host, user):
5455
5556        self ._init_connection ()
5657
58+     @property  
59+     def  user_host (self ):
60+         """remote address for in SSH format <user>@<IP>""" 
61+         return  f"{ self .user }  @{ self .host }  " 
62+ 
5763    def  remote_path (self , path ):
5864        """Convert a path to remote""" 
59-         return  f"{ self .user } @ { self . host }  :{ path }  " 
65+         return  f"{ self .user_host }  :{ path }  " 
6066
6167    def  _scp (self , path1 , path2 , options ):
6268        """Copy files to/from the VM using scp.""" 
@@ -98,21 +104,12 @@ def run(self, cmd_string, timeout=None, *, check=False, debug=False):
98104
99105        If `debug` is set, pass `-vvv` to `ssh`. Note that this will clobber stderr. 
100106        """ 
101-         command  =  [
102-             "ssh" ,
103-             * self .options ,
104-             f"{ self .user }  @{ self .host }  " ,
105-             cmd_string ,
106-         ]
107+         command  =  ["ssh" , * self .options , self .user_host , cmd_string ]
107108
108109        if  debug :
109110            command .insert (1 , "-vvv" )
110111
111-         return  self ._exec (
112-             command ,
113-             timeout ,
114-             check = check ,
115-         )
112+         return  self ._exec (command , timeout , check = check )
116113
117114    def  check_output (self , cmd_string , timeout = None , * , debug = False ):
118115        """Same as `run`, but raises an exception on non-zero return code of remote command""" 
@@ -125,6 +122,27 @@ def _exec(self, cmd, timeout=None, check=False):
125122
126123        return  utils .run_cmd (cmd , check = check , timeout = timeout )
127124
125+     # pylint:disable=invalid-name 
126+     def  Popen (
127+         self ,
128+         cmd : str ,
129+         stdin = subprocess .DEVNULL ,
130+         stdout = subprocess .PIPE ,
131+         stderr = subprocess .PIPE ,
132+         ** kwargs ,
133+     ) ->  subprocess .Popen :
134+         """Execute the command in the guest and return a Popen object. 
135+ 
136+         pop = uvm.ssh.Popen("while true; do echo $(date -Is) $RANDOM; sleep 1; done") 
137+         pop.stdout.read(16) 
138+         """ 
139+         cmd  =  ["ssh" , * self .options , self .user_host , cmd ]
140+         if  self .netns  is  not   None :
141+             cmd  =  ["ip" , "netns" , "exec" , self .netns ] +  cmd 
142+         return  subprocess .Popen (
143+             cmd , stdin = stdin , stdout = stdout , stderr = stderr , ** kwargs 
144+         )
145+ 
128146
129147def  mac_from_ip (ip_address ):
130148    """Create a MAC address based on the provided IP. 
0 commit comments