4
4
5
5
import datetime
6
6
import json
7
+ import functools
7
8
import os
8
9
import platform
9
10
import re
11
+ import signal
10
12
import sys
13
+ import time
11
14
12
15
from lib .config import (
13
16
CommonConfig ,
17
+ TestConfig ,
14
18
)
15
19
16
20
from lib .util import (
34
38
docker_version
35
39
)
36
40
41
+ from lib .thread import (
42
+ WrappedThread ,
43
+ )
44
+
45
+ from lib .constants import (
46
+ TIMEOUT_PATH ,
47
+ )
48
+
49
+ from lib .test import (
50
+ TestTimeout ,
51
+ )
52
+
37
53
38
54
class EnvConfig (CommonConfig ):
39
55
"""Configuration for the tools command."""
@@ -43,14 +59,26 @@ def __init__(self, args):
43
59
"""
44
60
super (EnvConfig , self ).__init__ (args , 'env' )
45
61
46
- self .show = args .show or not args . dump
62
+ self .show = args .show
47
63
self .dump = args .dump
64
+ self .timeout = args .timeout
48
65
49
66
50
67
def command_env (args ):
51
68
"""
52
69
:type args: EnvConfig
53
70
"""
71
+ show_dump_env (args )
72
+ set_timeout (args )
73
+
74
+
75
+ def show_dump_env (args ):
76
+ """
77
+ :type args: EnvConfig
78
+ """
79
+ if not args .show and not args .dump :
80
+ return
81
+
54
82
data = dict (
55
83
ansible = dict (
56
84
version = get_ansible_version (args ),
@@ -84,6 +112,105 @@ def command_env(args):
84
112
results_fd .write (json .dumps (data , sort_keys = True ))
85
113
86
114
115
+ def set_timeout (args ):
116
+ """
117
+ :type args: EnvConfig
118
+ """
119
+ if args .timeout is None :
120
+ return
121
+
122
+ if args .timeout :
123
+ deadline = (datetime .datetime .utcnow () + datetime .timedelta (minutes = args .timeout )).strftime ('%Y-%m-%dT%H:%M:%SZ' )
124
+
125
+ display .info ('Setting a %d minute test timeout which will end at: %s' % (args .timeout , deadline ), verbosity = 1 )
126
+ else :
127
+ deadline = None
128
+
129
+ display .info ('Clearing existing test timeout.' , verbosity = 1 )
130
+
131
+ if args .explain :
132
+ return
133
+
134
+ if deadline :
135
+ data = dict (
136
+ duration = args .timeout ,
137
+ deadline = deadline ,
138
+ )
139
+
140
+ with open (TIMEOUT_PATH , 'w' ) as timeout_fd :
141
+ json .dump (data , timeout_fd , indent = 4 , sort_keys = True )
142
+ elif os .path .exists (TIMEOUT_PATH ):
143
+ os .remove (TIMEOUT_PATH )
144
+
145
+
146
+ def get_timeout ():
147
+ """
148
+ :rtype: dict[str, any] | None
149
+ """
150
+ if not os .path .exists (TIMEOUT_PATH ):
151
+ return None
152
+
153
+ with open (TIMEOUT_PATH , 'r' ) as timeout_fd :
154
+ data = json .load (timeout_fd )
155
+
156
+ data ['deadline' ] = datetime .datetime .strptime (data ['deadline' ], '%Y-%m-%dT%H:%M:%SZ' )
157
+
158
+ return data
159
+
160
+
161
+ def configure_timeout (args ):
162
+ """
163
+ :type args: CommonConfig
164
+ """
165
+ if isinstance (args , TestConfig ):
166
+ configure_test_timeout (args ) # only tests are subject to the timeout
167
+
168
+
169
+ def configure_test_timeout (args ):
170
+ """
171
+ :type args: TestConfig
172
+ """
173
+ timeout = get_timeout ()
174
+
175
+ if not timeout :
176
+ return
177
+
178
+ timeout_start = datetime .datetime .utcnow ()
179
+ timeout_duration = timeout ['duration' ]
180
+ timeout_deadline = timeout ['deadline' ]
181
+ timeout_remaining = timeout_deadline - timeout_start
182
+
183
+ test_timeout = TestTimeout (timeout_duration )
184
+
185
+ if timeout_remaining <= datetime .timedelta ():
186
+ test_timeout .write (args )
187
+
188
+ raise ApplicationError ('The %d minute test timeout expired %s ago at %s.' % (
189
+ timeout_duration , timeout_remaining * - 1 , timeout_deadline ))
190
+
191
+ display .info ('The %d minute test timeout expires in %s at %s.' % (
192
+ timeout_duration , timeout_remaining , timeout_deadline ), verbosity = 1 )
193
+
194
+ def timeout_handler (_dummy1 , _dummy2 ):
195
+ """Runs when SIGUSR1 is received."""
196
+ test_timeout .write (args )
197
+
198
+ raise ApplicationError ('Tests aborted after exceeding the %d minute time limit.' % timeout_duration )
199
+
200
+ def timeout_waiter (timeout_seconds ):
201
+ """
202
+ :type timeout_seconds: int
203
+ """
204
+ time .sleep (timeout_seconds )
205
+ os .kill (os .getpid (), signal .SIGUSR1 )
206
+
207
+ signal .signal (signal .SIGUSR1 , timeout_handler )
208
+
209
+ instance = WrappedThread (functools .partial (timeout_waiter , timeout_remaining .seconds ))
210
+ instance .daemon = True
211
+ instance .start ()
212
+
213
+
87
214
def show_dict (data , verbose , root_verbosity = 0 , path = None ):
88
215
"""
89
216
:type data: dict[str, any]
0 commit comments