Skip to content

Commit 7841558

Browse files
committed
Merged refactoring from client/refactoring branch
2 parents 1442435 + 3918ec5 commit 7841558

7 files changed

Lines changed: 437 additions & 546 deletions

File tree

rcm/client/gui/rcm_main_window.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ def __init__(self):
3232
screen_height = QGuiApplication.primaryScreen().size().height()
3333
pack_info.add_client_screen_dimensions(screen_width, screen_height)
3434

35-
app_width = 0.6 * screen_width # 1200
36-
app_height = 0.375 * app_width # 450
35+
app_width = 0.6 * screen_width # 1200
36+
app_height = max(0.375 * app_width, 360) # 450
3737

3838
logger.debug("screen_width: " + str(screen_width))
3939
logger.debug("screen_height: " + str(screen_height))

rcm/client/gui/thread.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def __init__(self, session_widget, host, user, password, preload=''):
1919
def run(self):
2020
try:
2121
self.session_widget.remote_connection_manager.login_setup(host=self.host,
22-
remoteuser=self.user,
22+
user=self.user,
2323
password=self.password,
2424
preload=self.preload)
2525
self.session_widget.platform_config = self.session_widget.remote_connection_manager.get_config()

rcm/client/logic/manager.py

Lines changed: 56 additions & 235 deletions
Large diffs are not rendered by default.

rcm/client/logic/plugin.py

Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
# std lib
2+
import sys
3+
import os
4+
import json
5+
import pexpect
6+
import subprocess
7+
8+
# local includes
9+
import client.logic.rcm_utils as rcm_utils
10+
from client.miscellaneous.logger import logic_logger
11+
from client.utils.pyinstaller_utils import resource_path
12+
import client.logic.cipher as cipher
13+
from client.miscellaneous.config_parser import parser, defaults
14+
15+
16+
class Executable(object):
17+
"""Class representing a program that can be run on the command line."""
18+
19+
def __init__(self, name):
20+
self.exe = name.split(' ')
21+
self.default_env = {}
22+
self.returncode = None
23+
24+
# if not self.exe:
25+
# raise ProcessError("Cannot construct executable for '%s'" % name)
26+
27+
def add_default_arg(self, arg):
28+
"""Add a default argument to the command."""
29+
self.exe.append(arg)
30+
31+
def add_arg_value(self, arg, value):
32+
"""Add a default argument to the command."""
33+
self.exe.append(arg)
34+
self.exe.append(value)
35+
36+
def add_default_env(self, key, value):
37+
"""Set an environment variable when the command is run.
38+
39+
Parameters:
40+
key: The environment variable to set
41+
value: The value to set it to
42+
"""
43+
self.default_env[key] = value
44+
45+
@property
46+
def command(self):
47+
"""The command-line string.
48+
49+
Returns:
50+
str: The executable and default arguments
51+
"""
52+
return ' '.join(self.exe)
53+
54+
@property
55+
def name(self):
56+
"""The executable name.
57+
58+
Returns:
59+
str: The basename of the executable
60+
"""
61+
return os.path.basename(self.path)
62+
63+
@property
64+
def path(self):
65+
"""The path to the executable.
66+
67+
Returns:
68+
str: The path to the executable
69+
"""
70+
return self.exe[0]
71+
72+
73+
class TurboVNCExecutable(Executable):
74+
def __init__(self):
75+
76+
self.set_env()
77+
78+
if sys.platform.startswith('darwin'):
79+
exe = "open"
80+
else:
81+
exe = rcm_utils.which('vncviewer')
82+
if not exe :
83+
logic_logger.error("vncviewer not found! Check the PATH environment variable.")
84+
if sys.platform == 'win32':
85+
# if the executable path contains spaces, it has to be put inside apexes
86+
exe = "\"" + exe + "\""
87+
# self.exe = exe
88+
logic_logger.debug("vncviewer path: " + exe)
89+
90+
super(TurboVNCExecutable, self).__init__(exe)
91+
92+
def set_env(self):
93+
# set the environment
94+
if getattr(sys, 'frozen', False):
95+
logic_logger.debug("Running in a bundle")
96+
# if running in a bundle, we hardcode the path
97+
# of the built-in vnc viewer and plink (windows only)
98+
os.environ['JAVA_HOME'] = resource_path('turbovnc')
99+
if sys.platform == 'win32':
100+
# on windows 10, administration policies prevent execution of external programs
101+
# located in %TEMP% ... it seems that it cannot be loaded
102+
103+
home_path = os.path.expanduser('~')
104+
desktop_path = os.path.join(home_path, 'Desktop')
105+
exe_dir_path = os.path.dirname(sys.executable)
106+
if os.path.exists(desktop_path):
107+
rcm_unprotected_path = os.path.join(exe_dir_path, '.rcm', 'executables')
108+
os.makedirs(rcm_unprotected_path, exist_ok=True)
109+
dest_dir = os.path.join(rcm_unprotected_path, 'turbovnc')
110+
rcm_utils.copytree(resource_path('turbovnc'), dest_dir)
111+
os.environ['JAVA_HOME'] = dest_dir
112+
113+
os.environ['JDK_HOME'] = os.environ['JAVA_HOME']
114+
os.environ['JRE_HOME'] = os.path.join(os.environ['JAVA_HOME'], 'jre')
115+
os.environ['CLASSPATH'] = os.path.join(os.environ['JAVA_HOME'], 'lib') + \
116+
os.pathsep + os.path.join(os.environ['JRE_HOME'], 'lib')
117+
os.environ['PATH'] = os.path.join(os.environ['JAVA_HOME'], 'bin') + os.pathsep + os.environ['PATH']
118+
logic_logger.debug("JAVA_HOME: " + str(os.environ['JAVA_HOME']))
119+
logic_logger.debug("JRE_HOME: " + str(os.environ['JRE_HOME']))
120+
logic_logger.debug("JDK_HOME: " + str(os.environ['JDK_HOME']))
121+
logic_logger.debug("CLASSPATH: " + str(os.environ['CLASSPATH']))
122+
logic_logger.debug("PATH: " + str(os.environ['PATH']))
123+
124+
def build(self, session, local_portnumber):
125+
nodelogin = session.hash['nodelogin']
126+
# local_portnumber = rcm_utils.get_unused_portnumber()
127+
128+
tunnel = session.hash['tunnel']
129+
try:
130+
tunnelling_method = json.loads(parser.get('Settings', 'ssh_client'))
131+
except Exception:
132+
tunnelling_method = "internal"
133+
logic_logger.info("Using " + str(tunnelling_method) + " ssh tunnelling")
134+
135+
# Decrypt password
136+
vncpassword = session.hash.get('vncpassword', '')
137+
rcm_cipher = cipher.RCMCipher()
138+
vncpassword_decrypted = rcm_cipher.decrypt(vncpassword)
139+
140+
# Darwin
141+
if sys.platform.startswith('darwin'):
142+
self.add_arg_value("-W", "vnc://:" + vncpassword_decrypted + "@127.0.0.1:" + str(local_portnumber))
143+
144+
# Win64
145+
elif sys.platform == 'win32':
146+
self.add_default_arg("/nounixlogin")
147+
self.add_default_arg("/noreconnect")
148+
self.add_default_arg("/nonewconn")
149+
self.add_arg_value("/loglevel", str(rcm_utils.vnc_loglevel))
150+
self.add_arg_value("/password", vncpassword_decrypted)
151+
152+
# Linux
153+
else:
154+
self.add_arg_value("-quality", "80")
155+
self.add_arg_value("-password", vncpassword_decrypted)
156+
self.add_default_arg("-noreconnect")
157+
self.add_default_arg("-nonewconn")
158+
159+
if not sys.platform.startswith('darwin'):
160+
if tunnel == 'y':
161+
self.add_default_arg("127.0.0.1:" + str(local_portnumber))
162+
else:
163+
self.add_default_arg(nodelogin + ":" + session.hash['display'])
164+
165+
166+
class SSHExecutable(Executable):
167+
def __init__(self):
168+
169+
self.set_env()
170+
171+
# ssh executable
172+
if sys.platform == 'win32':
173+
exe = rcm_utils.which('PLINK')
174+
else:
175+
exe = rcm_utils.which('ssh')
176+
if not exe:
177+
if sys.platform == 'win32':
178+
logic_logger.error("plink.exe not found! Check the PATH environment variable.")
179+
else:
180+
logic_logger.error("ssh not found!")
181+
return
182+
if sys.platform == 'win32':
183+
# if the executable path contains spaces, it has to be put inside apexes
184+
exe = "\"" + exe + "\""
185+
186+
super(SSHExecutable, self).__init__(exe)
187+
188+
def set_env(self):
189+
return
190+
191+
def build(self, user, password, session, local_portnumber):
192+
node = session.hash['node']
193+
nodelogin = session.hash['nodelogin']
194+
195+
portstring = session.hash.get('port', '')
196+
if portstring:
197+
portnumber = int(portstring)
198+
else:
199+
portnumber = 5900 + int(session.hash['display'])
200+
201+
self.add_arg_value("-L", "127.0.0.1:" + str(local_portnumber) + ":" + node + ":" + str(portnumber) )
202+
self.add_default_arg(user + "@" + nodelogin)
203+
204+
if sys.platform == 'win32':
205+
self.add_default_arg("-ssh")
206+
self.add_arg_value("-pw", str(password))
207+
208+
209+
class NativeSSHTunnelForwarder(object):
210+
def __init__(self, tunnel_command, password):
211+
self.tunnel_command = tunnel_command
212+
self.tunnel_process = None
213+
self.password = password
214+
215+
216+
def __enter__(self):
217+
logic_logger.debug(self.tunnel_command)
218+
if sys.platform == 'win32':
219+
self.tunnel_process = subprocess.Popen(self.tunnel_command,
220+
bufsize=1,
221+
stdout=subprocess.PIPE,
222+
stderr=subprocess.PIPE,
223+
stdin=subprocess.PIPE,
224+
shell=False,
225+
universal_newlines=True,
226+
env=os.environ)
227+
while True:
228+
o = self.tunnel_process.stderr.readline()
229+
logic_logger.debug("tunnel process stderr: " + str(o.strip()))
230+
231+
if o.strip().split()[0] == 'Store':
232+
break
233+
if o.strip().split()[0] == 'Using':
234+
break
235+
if o.strip().split()[0] == 'connection.':
236+
self.tunnel_process.stdin.write("yes\r\n")
237+
continue
238+
239+
else:
240+
self.tunnel_process = pexpect.spawn(self.tunnel_command,
241+
timeout=50)
242+
243+
i = self.tunnel_process.expect(['continue connecting',
244+
'password',
245+
r'.+',
246+
pexpect.TIMEOUT,
247+
pexpect.EOF],
248+
timeout=5)
249+
250+
if i == 0:
251+
self.tunnel_process.sendline('yes')
252+
i = self.tunnel_process.expect(['continue connecting',
253+
'password',
254+
pexpect.TIMEOUT,
255+
pexpect.EOF],
256+
timeout=5)
257+
258+
if i == 1:
259+
self.tunnel_process.sendline(self.password)
260+
261+
def __exit__(self, exc_type, exc_value, tb):
262+
self.stop()
263+
264+
def stop(self):
265+
logic_logger.debug("Stopping ssh tunnelling")
266+
267+
if self.tunnel_process:
268+
if sys.platform == 'win32':
269+
logic_logger.debug("Killing ssh tunnel process " +
270+
str(self.tunnel_process.pid))
271+
self.tunnel_process.terminate()
272+
else:
273+
self.tunnel_process.close(force=True)

rcm/client/logic/rcm_utils.py

Lines changed: 24 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import sys
66
import paramiko
77
import socket
8+
import shutil
89
import queue
910
import hashlib
1011
import urllib.request
@@ -87,10 +88,32 @@ def client_folder():
8788
return os.path.join(os.path.expanduser('~'), '.rcm')
8889

8990

90-
def log_folder():
91+
def log_folder():
9192
return os.path.join(client_folder(), 'logs')
9293

9394

95+
def copytree(src, dst, symlinks=False, ignore=None):
96+
if not os.path.exists(dst):
97+
logic_logger.debug("Creating folder " + dst)
98+
os.makedirs(dst)
99+
for item in os.listdir(src):
100+
s = os.path.join(src, item)
101+
d = os.path.join(dst, item)
102+
if os.path.isdir(s):
103+
copytree(s, d, symlinks, ignore)
104+
else:
105+
if not os.path.exists(d):
106+
logic_logger.debug("Copy: " + s + " >> " + d)
107+
shutil.copy2(s, d)
108+
else:
109+
source_hash = compute_checksum(s)
110+
dest_hash = compute_checksum(d)
111+
if source_hash == dest_hash:
112+
logic_logger.debug("Found previous: " + d)
113+
else:
114+
logic_logger.warning("Update previous: " + s + " >> " + d)
115+
shutil.copy2(s, d)
116+
94117

95118
# this is the real class, hidden
96119
class _pack_info:
@@ -159,58 +182,3 @@ def get_threads_exceptions():
159182
logic_logger.error("one thread raised ->" + exc)
160183
if exc:
161184
raise Exception("ERROR: " + exc + " in thread")
162-
163-
164-
def get_server_command(host, user, passwd=''):
165-
"""
166-
It call bare ssh server to check if on login node, the user has defined a variable
167-
named RCM_SERVER_COMMAND, in tht case the content of that variable overrides the default
168-
rcm command string used for the remaining part of the server interaction
169-
"""
170-
171-
ssh = paramiko.SSHClient()
172-
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
173-
try:
174-
ssh.connect(host, username=user, password=passwd)
175-
except Exception as e:
176-
logic_logger.error("ERROR {0}: ".format(e) + "in ssh.connect to host " + host)
177-
raise e
178-
179-
chan = ssh.get_transport().open_session()
180-
chan.get_pty()
181-
stdin = chan.makefile('wb')
182-
stdout = chan.makefile('rb')
183-
184-
start_string = '_##start##_'
185-
end_string = '_##end##_'
186-
env_variable = '${RCM_SERVER_COMMAND}'
187-
get_rcm_server_command = 'echo ' + start_string + env_variable + end_string + '\n'
188-
chan.invoke_shell()
189-
chan.sendall(get_rcm_server_command)
190-
stdin.flush()
191-
192-
chan.settimeout(20)
193-
194-
loop = True
195-
output = ''
196-
rcm_server_command = ''
197-
198-
while loop:
199-
try:
200-
# python3
201-
if sys.version_info >= (3, 0):
202-
line = str(stdout.readline(), 'utf-8')
203-
# python2
204-
else:
205-
line = stdout.readline()
206-
logic_logger.debug("parsing output line: " + line)
207-
208-
if end_string in line and start_string in line and not 'echo' in line:
209-
rcm_server_command=line.split(end_string)[0].split(start_string)[1]
210-
loop = False
211-
output += line
212-
except socket.timeout:
213-
logic_logger.warning("WARNING TIMEOUT: unable to grab output of " +
214-
get_rcm_server_command + " on host:" + host)
215-
loop = False
216-
return rcm_server_command

0 commit comments

Comments
 (0)