12
12
import os
13
13
import subprocess
14
14
import sys
15
+ import time
15
16
16
17
from .global_constants import LOCAL_PATH_INDICATOR , LOG_FILE_NAME
17
18
@@ -198,6 +199,82 @@ def expand_local_url(url, field):
198
199
# subprocess
199
200
#
200
201
# ---------------------------------------------------------------------
202
+ _TIMEOUT_MSG = """ Timout errors typically occur when svn or git requires
203
+ authentication to access a private repository. On some systems, svn
204
+ and git requests for authentication information will not be displayed
205
+ to the user. In this case, the program will appear to hang and
206
+ generate a timeout error. Ensure you can run svn and git manually and
207
+ access all repositories without entering your authentication
208
+ information."""
209
+
210
+ _TIMEOUT_SEC = 300
211
+ _POLL_DELTA_SEC = 0.02
212
+
213
+
214
+ def _poll_subprocess (commands , status_to_caller , output_to_caller ,
215
+ timeout_sec = _TIMEOUT_SEC ):
216
+ """Create a subprocess and poll the process until complete.
217
+
218
+ Impose a timeout limit and checkout process output for known
219
+ conditions that require user interaction.
220
+
221
+ NOTE: the timeout_delta has significant impact on run time. If it
222
+ is too long, and the many quick local subprocess calls will
223
+ drastically increase the run time, especially in tests.
224
+
225
+ NOTE: This function is broken out into for ease of
226
+ understanding. It does no error checking. It should only be called
227
+ from execute_subprocess, never directly.
228
+
229
+ """
230
+ logging .info (' ' .join (commands ))
231
+ output = []
232
+ start = time .time ()
233
+
234
+ proc = subprocess .Popen (commands ,
235
+ shell = False ,
236
+ stdout = subprocess .PIPE ,
237
+ stderr = subprocess .STDOUT ,
238
+ universal_newlines = True )
239
+ while proc .poll () is None :
240
+ time .sleep (_POLL_DELTA_SEC )
241
+ if time .time () - start > timeout_sec :
242
+ proc .kill ()
243
+ time .sleep (_POLL_DELTA_SEC * 5 )
244
+ msg = ("subprocess call to '{0}' has exceeded timeout limit of "
245
+ "{1} seconds.\n {2}" .format (commands [0 ], timeout_sec ,
246
+ _TIMEOUT_MSG ))
247
+ fatal_error (msg )
248
+ finish = time .time ()
249
+
250
+ run_time_msg = "run time : {0:.2f} seconds" .format (finish - start )
251
+ logging .info (run_time_msg )
252
+ output = proc .stdout .read ()
253
+ log_process_output (output )
254
+ status = proc .returncode
255
+
256
+ # NOTE(bja, 2018-03) need to cleanup open files. In python3 use
257
+ # "with subprocess.Popen(...) as proc:", but that is not available
258
+ # with python2 unless we create a context manager.
259
+ proc .stdout .close ()
260
+
261
+ if status != 0 :
262
+ raise subprocess .CalledProcessError (returncode = status ,
263
+ cmd = commands ,
264
+ output = output )
265
+
266
+ if status_to_caller and output_to_caller :
267
+ ret_value = (status , output )
268
+ elif status_to_caller :
269
+ ret_value = status
270
+ elif output_to_caller :
271
+ ret_value = output
272
+ else :
273
+ ret_value = None
274
+
275
+ return ret_value
276
+
277
+
201
278
def execute_subprocess (commands , status_to_caller = False ,
202
279
output_to_caller = False ):
203
280
"""Wrapper around subprocess.check_output to handle common
@@ -211,20 +288,30 @@ def execute_subprocess(commands, status_to_caller=False,
211
288
return code, otherwise execute_subprocess treats non-zero return
212
289
status as an error and raises an exception.
213
290
291
+ NOTE(bja, 2018-03) if the user doesn't have credentials setup
292
+ correctly, then svn and git will prompt for a username/password or
293
+ accepting the domain as trusted. We need to detect this and print
294
+ enough info for the user to determine what happened and enter the
295
+ appropriate information. When we detect some pre-determined
296
+ conditions, we turn on screen output so the user can see what is
297
+ needed. There doesn't appear to be a way to detect if the user
298
+ entered any information in the terminal. So there is no way to
299
+ turn off output.
300
+
301
+ NOTE(bja, 2018-03) we are polling the running process to avoid
302
+ having it hang indefinitely if there is input that we don't
303
+ detect. Some large checkouts are multiple minutes long. For now we
304
+ are setting the timeout interval to five minutes.
305
+
214
306
"""
215
307
msg = 'In directory: {0}\n execute_subprocess running command:' .format (
216
308
os .getcwd ())
217
309
logging .info (msg )
218
310
logging .info (commands )
219
311
return_to_caller = status_to_caller or output_to_caller
220
- status = - 1
221
- output = ''
222
312
try :
223
- logging .info (' ' .join (commands ))
224
- output = subprocess .check_output (commands , stderr = subprocess .STDOUT ,
225
- universal_newlines = True )
226
- log_process_output (output )
227
- status = 0
313
+ ret_value = _poll_subprocess (
314
+ commands , status_to_caller , output_to_caller )
228
315
except OSError as error :
229
316
msg = failed_command_msg (
230
317
'Command execution failed. Does the executable exist?' ,
@@ -246,24 +333,12 @@ def execute_subprocess(commands, status_to_caller=False,
246
333
if not return_to_caller :
247
334
msg_context = ('Process did not run successfully; '
248
335
'returned status {0}' .format (error .returncode ))
249
- msg = failed_command_msg (
250
- msg_context ,
251
- commands ,
252
- output = error .output )
336
+ msg = failed_command_msg (msg_context , commands ,
337
+ output = error .output )
253
338
logging .error (error )
254
- logging .error (msg )
255
339
log_process_output (error .output )
256
340
fatal_error (msg )
257
- status = error .returncode
258
-
259
- if status_to_caller and output_to_caller :
260
- ret_value = (status , output )
261
- elif status_to_caller :
262
- ret_value = status
263
- elif output_to_caller :
264
- ret_value = output
265
- else :
266
- ret_value = None
341
+ ret_value = error .returncode
267
342
268
343
return ret_value
269
344
0 commit comments