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