55import ipaddress
66import random
77import string
8+ import subprocess
89from dataclasses import dataclass , field
910from pathlib import Path
1011
@@ -67,9 +68,14 @@ def __init__(self, netns, ssh_key: Path, host, user, *, on_error=None):
6768
6869 self ._on_error = on_error
6970
71+ @property
72+ def user_host (self ):
73+ """remote address for in SSH format <user>@<IP>"""
74+ return f"{ self .user } @{ self .host } "
75+
7076 def remote_path (self , path ):
7177 """Convert a path to remote"""
72- return f"{ self .user } @ { self . host } :{ path } "
78+ return f"{ self .user_host } :{ path } "
7379
7480 def _scp (self , path1 , path2 , options ):
7581 """Copy files to/from the VM using scp."""
@@ -111,21 +117,12 @@ def run(self, cmd_string, timeout=None, *, check=False, debug=False):
111117
112118 If `debug` is set, pass `-vvv` to `ssh`. Note that this will clobber stderr.
113119 """
114- command = [
115- "ssh" ,
116- * self .options ,
117- f"{ self .user } @{ self .host } " ,
118- cmd_string ,
119- ]
120+ command = ["ssh" , * self .options , self .user_host , cmd_string ]
120121
121122 if debug :
122123 command .insert (1 , "-vvv" )
123124
124- return self ._exec (
125- command ,
126- timeout ,
127- check = check ,
128- )
125+ return self ._exec (command , timeout , check = check )
129126
130127 def check_output (self , cmd_string , timeout = None , * , debug = False ):
131128 """Same as `run`, but raises an exception on non-zero return code of remote command"""
@@ -144,6 +141,27 @@ def _exec(self, cmd, timeout=None, check=False):
144141
145142 raise
146143
144+ # pylint:disable=invalid-name
145+ def Popen (
146+ self ,
147+ cmd : str ,
148+ stdin = subprocess .DEVNULL ,
149+ stdout = subprocess .PIPE ,
150+ stderr = subprocess .PIPE ,
151+ ** kwargs ,
152+ ) -> subprocess .Popen :
153+ """Execute the command in the guest and return a Popen object.
154+
155+ pop = uvm.ssh.Popen("while true; do echo $(date -Is) $RANDOM; sleep 1; done")
156+ pop.stdout.read(16)
157+ """
158+ cmd = ["ssh" , * self .options , self .user_host , cmd ]
159+ if self .netns is not None :
160+ cmd = ["ip" , "netns" , "exec" , self .netns ] + cmd
161+ return subprocess .Popen (
162+ cmd , stdin = stdin , stdout = stdout , stderr = stderr , ** kwargs
163+ )
164+
147165
148166def mac_from_ip (ip_address ):
149167 """Create a MAC address based on the provided IP.
0 commit comments