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