blob: 79b248ce4a94589e5be458590bd3685337a10d6b [file] [log] [blame]
Dennis Dmitriev61115112018-05-31 06:48:43 +03001from __future__ import print_function
Dmitry Tyzhnenkob39de052019-03-21 17:05:07 +02002import datetime
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +03003import time
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +02004
5import jenkins
Dennis Dmitriev27a96792018-07-30 07:52:03 +03006import json
Dennis Dmitriev61115112018-05-31 06:48:43 +03007import requests
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +02008
9from devops.helpers import helpers
10
Dmitry Tyzhnenko80ce0202019-02-07 13:27:19 +020011from requests.exceptions import ConnectionError
12
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +020013
Hanna Arhipovab2522692020-09-23 15:25:11 +030014def retry(max_count=6,
15 sleep_before_retry=10):
16 def _retry(func):
17 def __retry(*args, **kwargs):
18 """
19 Waits some time and retries the requests if it fails with
20 with any error
21 Raises Exceptions after all unsuccessful tries
22
23 :param func: callable
24 :param args, kwargs: parameters of decorated functions
25 :param max_count: times of retries
26 :param sleep_before_retry: how many seconds needs to wait before
27 the next retry
28 :return: response
29 :raise ConnectionError after several unsuccessful connections
30 """
Hanna Arhipova6d788142021-03-12 11:46:47 +020031 err_msg = None
Hanna Arhipovab2522692020-09-23 15:25:11 +030032 for count in range(max_count):
33 try:
34 return func(*args, **kwargs)
35 except Exception as err:
Hanna Arhipova6d788142021-03-12 11:46:47 +020036 err_msg = err
37 print("Try {count}/{max_count} caught "
38 "Exception in {fn}: {err}."
39 "\n... repeat after {secs} secs".
Hanna Arhipovab2522692020-09-23 15:25:11 +030040 format(err=err,
41 count=count+1,
42 max_count=max_count,
Hanna Arhipova6d788142021-03-12 11:46:47 +020043 secs=sleep_before_retry,
44 fn=func.__name__))
Hanna Arhipovab2522692020-09-23 15:25:11 +030045 time.sleep(sleep_before_retry)
46 print("Function failed in {total_time} seconds".format(
47 total_time=max_count*sleep_before_retry)
48 )
Hanna Arhipova6d788142021-03-12 11:46:47 +020049 raise err_msg
Hanna Arhipovab2522692020-09-23 15:25:11 +030050 return __retry
51 return _retry
52
53
54@retry(max_count=5, sleep_before_retry=2)
Hanna Arhipovae1f0b472020-01-03 17:38:11 +020055def send_request(action, endpoint):
56 """
Hanna Arhipovab2522692020-09-23 15:25:11 +030057 Makes request with described operation to endpoint
58 :param action: string, type of operation GET, POST, DELETE and so on
59 :param endpoint: string, url to send request
60 :return: response
Hanna Arhipovae1f0b472020-01-03 17:38:11 +020061 """
Hanna Arhipovab2522692020-09-23 15:25:11 +030062 response = requests.Request(action, endpoint)
63 return response
Hanna Arhipovae1f0b472020-01-03 17:38:11 +020064
65
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +020066class JenkinsClient(object):
67
Dennis Dmitriev27a96792018-07-30 07:52:03 +030068 def __init__(self, host=None, username='admin', password='r00tme'):
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +020069 host = host or 'http://172.16.44.33:8081'
obutenkoca858402019-07-04 18:31:39 +030070 self.__client = jenkins.Jenkins(
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +020071 host,
72 username=username,
73 password=password)
obutenkoca858402019-07-04 18:31:39 +030074 self.__client._session.verify = False
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +020075
Hanna Arhipovab2522692020-09-23 15:25:11 +030076 @retry()
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +020077 def jobs(self):
78 return self.__client.get_jobs()
79
80 def find_jobs(self, name):
81 return filter(lambda x: name in x['fullname'], self.jobs())
82
Hanna Arhipovab2522692020-09-23 15:25:11 +030083 @retry()
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +020084 def job_info(self, name):
Hanna Arhipovab2522692020-09-23 15:25:11 +030085 return self.__client.get_job_info(name)
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +020086
87 def list_builds(self, name):
88 return self.job_info(name).get('builds')
89
Hanna Arhipovab2522692020-09-23 15:25:11 +030090 @retry()
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +020091 def build_info(self, name, build_id):
Hanna Arhipovab2522692020-09-23 15:25:11 +030092 return self.__client.get_build_info(name, build_id)
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +020093
94 def job_params(self, name):
95 job = self.job_info(name)
96 job_params = next(
97 p for p in job['property'] if
98 'hudson.model.ParametersDefinitionProperty' == p['_class'])
99 job_params = job_params['parameterDefinitions']
100 return job_params
101
102 def make_defults_params(self, name):
103 job_params = self.job_params(name)
104 def_params = dict(
105 [(j['name'], j['defaultParameterValue']['value'])
106 for j in job_params])
107 return def_params
108
Hanna Arhipovab2522692020-09-23 15:25:11 +0300109 @retry()
Dennis Dmitrieva5bd1652018-05-31 20:57:19 +0300110 def run_build(self, name, params=None, timeout=600, verbose=False):
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +0200111 params = params or self.make_defults_params(name)
Dennis Dmitrieva5bd1652018-05-31 20:57:19 +0300112 num = self.__client.build_job(name, params)
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300113 time.sleep(2) # wait while job is started
Dennis Dmitrieva5bd1652018-05-31 20:57:19 +0300114
Dmitry Tyzhnenkob39de052019-03-21 17:05:07 +0200115 def is_build_queued():
116 try:
117 item = self.__client.get_queue_item(num)
118 ts = item['inQueueSince'] / 1000
119 since_time = datetime.datetime.fromtimestamp(ts)
120 print("Build in the queue since {}".format(since_time))
121 return True
122 except jenkins.JenkinsException:
123 if verbose:
124 print("Build have not been queued {} yet".format(num))
125
126 helpers.wait(
127 is_build_queued,
128 timeout=timeout,
129 interval=30,
130 timeout_msg='Timeout waiting to queue the build '
131 'for {} job'.format(name))
132
Dennis Dmitrieva5bd1652018-05-31 20:57:19 +0300133 def is_blocked():
134 queued = self.__client.get_queue_item(num)
135 status = not queued['blocked']
136 if not status and verbose:
137 print("pending the job [{}] : {}".format(name, queued['why']))
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300138 return (status and
139 'executable' in (queued or {}) and
140 'number' in (queued['executable'] or {}))
Dennis Dmitrieva5bd1652018-05-31 20:57:19 +0300141
142 helpers.wait(
143 is_blocked,
144 timeout=timeout,
145 interval=30,
146 timeout_msg='Timeout waiting to run the job [{}]'.format(name))
Dennis Dmitrieva5bd1652018-05-31 20:57:19 +0300147 build_id = self.__client.get_queue_item(num)['executable']['number']
Dmitry Tyzhnenkob39de052019-03-21 17:05:07 +0200148
149 def is_build_started():
150 try:
151 build = self.__client.get_build_info(name, build_id)
152 ts = float(build['timestamp']) / 1000
153 start_time = datetime.datetime.fromtimestamp(ts)
154 print("the build {} in {} have started at {} UTC".format(
155 build_id, name, start_time))
156 return True
157 except jenkins.JenkinsException:
158 if verbose:
159 print("the build {} in {} have not strated yet".format(
160 build_id, name))
161 helpers.wait(
162 is_build_started,
163 timeout=timeout,
164 interval=30,
165 timeout_msg='Timeout waiting to run build of '
166 'the job [{}]'.format(name))
167
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +0200168 return name, build_id
169
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300170 def wait_end_of_build(self, name, build_id, timeout=600, interval=5,
171 verbose=False, job_output_prefix=''):
172 '''Wait until the specified build is finished
173
174 :param name: ``str``, job name
175 :param build_id: ``int``, build id
176 :param timeout: ``int``, timeout waiting the job, sec
177 :param interval: ``int``, interval of polling the job result, sec
178 :param verbose: ``bool``, print the job console updates during waiting
179 :param job_output_prefix: ``str``, print the prefix for each console
180 output line, with the pre-defined
181 substitution keys:
182 - '{name}' : the current job name
183 - '{build_id}' : the current build-id
184 - '{time}' : the current time
185 :returns: requests object with headers and console output, ``obj``
186 '''
Dennis Dmitriev61115112018-05-31 06:48:43 +0300187 start = [0]
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300188 time_str = time.strftime("%H:%M:%S")
189 prefix = "\n" + job_output_prefix.format(job_name=name,
190 build_number=build_id,
191 time=time_str)
192 if verbose:
193 print(prefix, end='')
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +0200194
195 def building():
Dmitry Tyzhnenko80ce0202019-02-07 13:27:19 +0200196 try:
Hanna Arhipova6d788142021-03-12 11:46:47 +0200197 # Nested retry decorator. Need to wait >30 min
198 # During mcp-upgrade job the Jenkins can being upgrading
199 # and can be inaccessible for >20 min
200 status = not retry(max_count=30)(
201 self.build_info)(name, build_id)['building']
Dmitry Tyzhnenko80ce0202019-02-07 13:27:19 +0200202 except ConnectionError:
203 status = False
204
Dennis Dmitrieva5bd1652018-05-31 20:57:19 +0300205 if verbose:
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300206 time_str = time.strftime("%H:%M:%S")
207 prefix = "\n" + job_output_prefix.format(
208 job_name=name, build_number=build_id, time=time_str)
Dennis Dmitriev61115112018-05-31 06:48:43 +0300209 res = self.get_progressive_build_output(name,
210 build_id,
211 start=start[0])
212 if 'X-Text-Size' in res.headers:
213 text_size = int(res.headers['X-Text-Size'])
214 if start[0] < text_size:
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300215 text = res.content.decode('utf-8',
216 errors='backslashreplace')
217 print(text.replace("\n", prefix), end='')
Dennis Dmitriev61115112018-05-31 06:48:43 +0300218 start[0] = text_size
219 return status
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +0200220
221 helpers.wait(
222 building,
223 timeout=timeout,
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300224 interval=interval,
Dennis Dmitriev13e804b2018-10-09 19:25:14 +0300225 timeout_msg=('Timeout waiting the job {0}:{1} in {2} sec.'
226 .format(name, build_id, timeout)))
Dmitry Tyzhnenkob610afd2018-02-19 15:43:45 +0200227
Hanna Arhipovab2522692020-09-23 15:25:11 +0300228 @retry()
Dmitry Tyzhnenkob610afd2018-02-19 15:43:45 +0200229 def get_build_output(self, name, build_id):
230 return self.__client.get_build_console_output(name, build_id)
Dennis Dmitriev61115112018-05-31 06:48:43 +0300231
Hanna Arhipovab2522692020-09-23 15:25:11 +0300232 @retry(max_count=12)
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300233 def get_progressive_build_output(self, name, build_id, start=0):
Dennis Dmitriev61115112018-05-31 06:48:43 +0300234 '''Get build console text.
235
236 :param name: Job name, ``str``
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300237 :param build_id: Build id, ``int``
238 :param start: Start offset, ``int``
Dennis Dmitriev61115112018-05-31 06:48:43 +0300239 :returns: requests object with headers and console output, ``obj``
240 '''
241 folder_url, short_name = self.__client._get_job_folder(name)
242
243 PROGRESSIVE_CONSOLE_OUTPUT = (
244 '%(folder_url)sjob/%(short_name)s/%(build_id)d/'
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300245 'logText/progressiveText?start=%(start)d')
Hanna Arhipovae1f0b472020-01-03 17:38:11 +0200246 req = send_request(
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300247 'GET',
248 self.__client._build_url(PROGRESSIVE_CONSOLE_OUTPUT, locals()))
249 return(self.__client.jenkins_request(req))
Dennis Dmitriev27a96792018-07-30 07:52:03 +0300250
Hanna Arhipovab2522692020-09-23 15:25:11 +0300251 @retry(max_count=12)
Dennis Dmitriev27a96792018-07-30 07:52:03 +0300252 def get_workflow(self, name, build_id, enode=None, mode='describe'):
253 '''Get workflow results from pipeline job
254
255 :param name: job name
256 :param build_id: str, build number or 'lastBuild'
257 :param enode: int, execution node in the workflow
258 :param mode: the stage or execution node description if 'describe',
259 the execution node log if 'log'
260 '''
261 folder_url, short_name = self.__client._get_job_folder(name)
262
263 if enode:
264 WORKFLOW_DESCRIPTION = (
265 '%(folder_url)sjob/%(short_name)s/%(build_id)s/'
266 'execution/node/%(enode)d/wfapi/%(mode)s')
267 else:
268 WORKFLOW_DESCRIPTION = (
269 '%(folder_url)sjob/%(short_name)s/%(build_id)s/wfapi/%(mode)s')
Hanna Arhipovae1f0b472020-01-03 17:38:11 +0200270 req = send_request(
Dennis Dmitriev27a96792018-07-30 07:52:03 +0300271 'GET',
272 self.__client._build_url(WORKFLOW_DESCRIPTION, locals()))
273 response = self.__client.jenkins_open(req)
274 return json.loads(response)
Dennis Dmitriev8565c342019-02-11 23:45:03 +0200275
Hanna Arhipovab2522692020-09-23 15:25:11 +0300276 @retry(max_count=12)
Dennis Dmitriev8565c342019-02-11 23:45:03 +0200277 def get_artifact(self, name, build_id, artifact_path, destination_name):
278 '''Wait until the specified build is finished
279
280 :param name: ``str``, job name
281 :param build_id: ``str``, build id or "lastBuild"
282 :param artifact_path: ``str``, path and filename of the artifact
283 relative to the job URL
284 :param artifact_path: ``str``, destination path and filename
285 on the local filesystem where to save
286 the artifact content
287 :returns: requests object with headers and console output, ``obj``
288 '''
289 folder_url, short_name = self.__client._get_job_folder(name)
290
291 DOWNLOAD_URL = ('%(folder_url)sjob/%(short_name)s/%(build_id)s/'
292 'artifact/%(artifact_path)s')
Hanna Arhipovae1f0b472020-01-03 17:38:11 +0200293 req = send_request(
Dennis Dmitriev8565c342019-02-11 23:45:03 +0200294 'GET',
295 self.__client._build_url(DOWNLOAD_URL, locals()))
296
297 response = self.__client.jenkins_request(req)
298 return response.content