Package coprs :: Package logic :: Module actions_logic
[hide private]
[frames] | no frames]

Source Code for Module coprs.logic.actions_logic

  1  import json 
  2  import time 
  3   
  4  from sqlalchemy import and_, or_ 
  5  from sqlalchemy.exc import IntegrityError 
  6   
  7  from copr_common.enums import ActionTypeEnum, BackendResultEnum 
  8  from coprs import db 
  9  from coprs import models 
 10  from coprs import helpers 
 11  from coprs import exceptions 
 12  from .helpers import get_graph_parameters 
13 14 -class ActionsLogic(object):
15 16 @classmethod
17 - def get(cls, action_id):
18 """ 19 Return single action identified by `action_id` 20 """ 21 22 query = models.Action.query.filter(models.Action.id == action_id) 23 return query
24 25 @classmethod
26 - def get_many(cls, action_type=None, result=None):
27 query = models.Action.query 28 if action_type is not None: 29 query = query.filter(models.Action.action_type == 30 int(action_type)) 31 if result is not None: 32 query = query.filter(models.Action.result == 33 int(result)) 34 35 return query
36 37 @classmethod
38 - def get_waiting(cls):
39 """ 40 Return actions that aren't finished 41 """ 42 43 query = (models.Action.query 44 .filter(models.Action.result == 45 BackendResultEnum("waiting")) 46 .filter(models.Action.action_type != 47 ActionTypeEnum("legal-flag")) 48 .order_by(models.Action.created_on.asc())) 49 50 return query
51 52 @classmethod
53 - def get_by_ids(cls, ids):
54 """ 55 Return actions matching passed `ids` 56 """ 57 58 return models.Action.query.filter(models.Action.id.in_(ids))
59 60 @classmethod
61 - def update_state_from_dict(cls, action, upd_dict):
62 """ 63 Update `action` object with `upd_dict` data 64 65 Updates result, message and ended_on parameters. 66 """ 67 68 for attr in ["result", "message"]: 69 value = upd_dict.get(attr, None) 70 if value: 71 setattr(action, attr, value) 72 73 if upd_dict.get('result', None) in [BackendResultEnum("success"), 74 BackendResultEnum("failure")]: 75 action.ended_on = time.time() 76 db.session.add(action) 77 return action
78 79 @classmethod
80 - def send_createrepo(cls, copr, dirnames=None):
81 possible_dirnames = [copr_dir.name for copr_dir in copr.dirs] 82 if not dirnames: 83 # by default we createrepo for all of them 84 dirnames = possible_dirnames 85 else: 86 missing = set(dirnames) - set(possible_dirnames) 87 if missing: 88 raise exceptions.NotFoundException( 89 "Can't createrepo for {} dirnames in {} project".format( 90 missing, copr.full_name)) 91 data_dict = { 92 "ownername": copr.owner_name, 93 "projectname": copr.name, 94 "project_dirnames": dirnames, 95 "chroots": [chroot.name for chroot in copr.active_chroots], 96 } 97 action = models.Action( 98 action_type=ActionTypeEnum("createrepo"), 99 object_type="repository", 100 object_id=0, 101 data=json.dumps(data_dict), 102 created_on=int(time.time()), 103 ) 104 db.session.add(action) 105 return action
106 107 @classmethod
108 - def send_delete_copr(cls, copr):
109 data_dict = { 110 "ownername": copr.owner_name, 111 "project_dirnames": [copr_dir.name for copr_dir in copr.dirs], 112 } 113 action = models.Action(action_type=ActionTypeEnum("delete"), 114 object_type="copr", 115 object_id=copr.id, 116 data=json.dumps(data_dict), 117 created_on=int(time.time())) 118 db.session.add(action) 119 return action
120 121 @classmethod
122 - def get_chroot_builddirs(cls, build):
123 """ 124 Creates a dictionary of chroot builddirs for build delete action 125 :type build: models.build 126 """ 127 chroot_builddirs = {} 128 129 # plan to remove sub-dir in srpm-builds/ directory 130 if build.result_dir: 131 chroot_builddirs['srpm-builds'] = [build.result_dir] 132 133 # and all chroot sub-dirs 134 for build_chroot in build.build_chroots: 135 if not build_chroot.result_dir: 136 # when we cancel build when the src.rpm (e.g. SCM method) is not 137 # yet generated 138 continue 139 chroot_builddirs[build_chroot.name] = [build_chroot.result_dir] 140 141 return chroot_builddirs
142 143 @classmethod
144 - def get_build_delete_data(cls, build):
145 """ 146 Creates data needed for build delete action 147 :type build: models.build 148 """ 149 return { 150 "ownername": build.copr.owner_name, 151 "projectname": build.copr_name, 152 "project_dirname": 153 build.copr_dirname if build.copr_dir else build.copr_name, 154 "chroot_builddirs": cls.get_chroot_builddirs(build) 155 }
156 157 @classmethod
158 - def send_delete_build(cls, build):
159 """ 160 Schedules build delete action 161 :type build: models.Build 162 """ 163 action = models.Action( 164 action_type=ActionTypeEnum("delete"), 165 object_type="build", 166 object_id=build.id, 167 data=json.dumps(cls.get_build_delete_data(build)), 168 created_on=int(time.time()) 169 ) 170 db.session.add(action) 171 return action
172 173 @classmethod
174 - def send_delete_multiple_builds(cls, builds):
175 """ 176 Schedules builds delete action for builds belonging to the same project 177 :type build: list of models.Build 178 """ 179 project_dirnames = {} 180 data = {'project_dirnames': project_dirnames} 181 182 build_ids = [] 183 for build in builds: 184 build_delete_data = cls.get_build_delete_data(build) 185 build_ids.append(build.id) 186 187 # inherit some params from the first build 188 for param in ['ownername', 'projectname']: 189 new = build_delete_data[param] 190 if param in data and data[param] != new: 191 # this shouldn't happen 192 raise exceptions.BadRequest("Can not delete builds " 193 "from more projects") 194 data[param] = new 195 196 dirname = build_delete_data['project_dirname'] 197 if not dirname in project_dirnames: 198 project_dirnames[dirname] = {} 199 200 project_dirname = project_dirnames[dirname] 201 for chroot, subdirs in build_delete_data['chroot_builddirs'].items(): 202 if chroot not in project_dirname: 203 project_dirname[chroot] = subdirs 204 else: 205 project_dirname[chroot].extend(subdirs) 206 207 data['build_ids'] = build_ids 208 209 # not object_id here, we are working with multiple IDs 210 action = models.Action( 211 action_type=ActionTypeEnum("delete"), 212 object_type="builds", 213 data=json.dumps(data), 214 created_on=int(time.time()) 215 ) 216 db.session.add(action) 217 return action
218 219 @classmethod
220 - def send_cancel_build(cls, build):
221 """ 222 Schedule build cancel. The build is marked as canceled immediately, but 223 to not waste the resources we propagate this information to Backend 224 which may deallocate the builder resources. 225 226 :type build: models.Build 227 """ 228 if build.canceled: 229 return 230 db.session.add(models.CancelRequest(what=str(build.id))) 231 for chroot in build.build_chroots: 232 db.session.add(models.CancelRequest(what=chroot.task_id))
233 234 @classmethod
235 - def send_update_comps(cls, chroot):
236 """ Schedules update comps.xml action 237 238 :type copr_chroot: models.CoprChroot 239 """ 240 241 url_path = helpers.copr_url("coprs_ns.chroot_view_comps", chroot.copr, chrootname=chroot.name) 242 data_dict = { 243 "ownername": chroot.copr.owner_name, 244 "projectname": chroot.copr.name, 245 "chroot": chroot.name, 246 "comps_present": chroot.comps_zlib is not None, 247 "url_path": url_path, 248 } 249 250 action = models.Action( 251 action_type=ActionTypeEnum("update_comps"), 252 object_type="copr_chroot", 253 data=json.dumps(data_dict), 254 created_on=int(time.time()) 255 ) 256 db.session.add(action) 257 return action
258 259 @classmethod
260 - def send_create_gpg_key(cls, copr):
261 """ 262 :type copr: models.Copr 263 """ 264 265 data_dict = { 266 "ownername": copr.owner_name, 267 "projectname": copr.name, 268 } 269 270 action = models.Action( 271 action_type=ActionTypeEnum("gen_gpg_key"), 272 object_type="copr", 273 data=json.dumps(data_dict), 274 created_on=int(time.time()), 275 ) 276 db.session.add(action) 277 return action
278 279 @classmethod
280 - def send_rawhide_to_release(cls, data):
281 action = models.Action( 282 action_type=ActionTypeEnum("rawhide_to_release"), 283 object_type="None", 284 data=json.dumps(data), 285 created_on=int(time.time()), 286 ) 287 db.session.add(action) 288 return action
289 290 @classmethod
291 - def send_fork_copr(cls, src, dst, builds_map):
292 """ 293 :type src: models.Copr 294 :type dst: models.Copr 295 :type builds_map: dict where keys are forked builds IDs and values are IDs from the original builds. 296 """ 297 298 action = models.Action( 299 action_type=ActionTypeEnum("fork"), 300 object_type="copr", 301 old_value="{0}".format(src.full_name), 302 new_value="{0}".format(dst.full_name), 303 data=json.dumps({"user": dst.owner_name, "copr": dst.name, "builds_map": builds_map}), 304 created_on=int(time.time()), 305 ) 306 db.session.add(action) 307 return action
308 309 @classmethod
310 - def send_build_module(cls, copr, module):
311 """ 312 :type copr: models.Copr 313 :type modulemd: str content of module yaml file 314 """ 315 316 mock_chroots = set.intersection(*[set(b.chroots) for b in module.builds]) 317 data = { 318 "chroots": [ch.name for ch in mock_chroots], 319 "builds": [b.id for b in module.builds], 320 } 321 322 action = models.Action( 323 action_type=ActionTypeEnum("build_module"), 324 object_type="module", 325 object_id=module.id, 326 old_value="", 327 new_value="", 328 data=json.dumps(data), 329 created_on=int(time.time()), 330 ) 331 db.session.add(action) 332 return action
333 334 @classmethod
335 - def send_delete_chroot(cls, copr_chroot):
336 """ 337 Schedules deletion of a chroot directory from project 338 Useful to remove outdated chroots 339 :type build: models.CoprChroot 340 """ 341 data_dict = { 342 "ownername": copr_chroot.copr.owner_name, 343 "projectname": copr_chroot.copr.name, 344 "chrootname": copr_chroot.name, 345 } 346 347 action = models.Action( 348 action_type=ActionTypeEnum("delete"), 349 object_type="chroot", 350 object_id=None, 351 data=json.dumps(data_dict), 352 created_on=int(time.time()) 353 ) 354 db.session.add(action) 355 return action
356 357 @classmethod
358 - def cache_action_graph_data(cls, type, time, waiting, success, failure):
359 result = models.ActionsStatistics.query\ 360 .filter(models.ActionsStatistics.stat_type == type)\ 361 .filter(models.ActionsStatistics.time == time).first() 362 if result: 363 return 364 365 try: 366 cached_data = models.ActionsStatistics( 367 time = time, 368 stat_type = type, 369 waiting = waiting, 370 success = success, 371 failed = failure 372 ) 373 db.session.add(cached_data) 374 db.session.commit() # @FIXME We should not commit here 375 except IntegrityError: # other process already calculated the graph data and cached it 376 db.session.rollback()
377 378 @classmethod
379 - def get_actions_bucket(cls, start, end, actionType):
380 if actionType == 0: 381 # used for getting data for "processed" line of action graphs 382 result = models.Action.query\ 383 .filter(and_( 384 models.Action.created_on <= end, 385 or_( 386 models.Action.ended_on > start, 387 models.Action.ended_on == None 388 )))\ 389 .count() 390 return result 391 392 else: 393 # used to getting data for "successed and failure" line of action graphs 394 result = models.Action.query\ 395 .filter(models.Action.ended_on <= end)\ 396 .filter(models.Action.ended_on > start)\ 397 .filter(models.Action.result == actionType)\ 398 .count() 399 return result
400 401 @classmethod
402 - def get_cached_action_data(cls, params):
403 data = { 404 "waiting": [], 405 "success": [], 406 "failure": [], 407 } 408 result = models.ActionsStatistics.query\ 409 .filter(models.ActionsStatistics.stat_type == params["type"])\ 410 .filter(models.ActionsStatistics.time >= params["start"])\ 411 .filter(models.ActionsStatistics.time <= params["end"])\ 412 .order_by(models.ActionsStatistics.time) 413 for row in result: 414 data["waiting"].append(row.waiting) 415 data["success"].append(row.success) 416 data["failure"].append(row.failed) 417 418 return data
419 420 @classmethod
421 - def get_action_graph_data(cls, type):
422 data = [["processed"], ["success"], ["failure"], ["time"] ] 423 params = get_graph_parameters(type) 424 cached_data = cls.get_cached_action_data(params) 425 for actionType in ["waiting", "success", "failure"]: 426 data[BackendResultEnum(actionType)].extend(cached_data[actionType]) 427 for i in range(len(data[0]) - 1, params["steps"]): 428 step_start = params["start"] + i * params["step"] 429 step_end = step_start + params["step"] 430 waiting = cls.get_actions_bucket(step_start, step_end, BackendResultEnum("waiting")) 431 success = cls.get_actions_bucket(step_start, step_end, BackendResultEnum("success")) 432 failure = cls.get_actions_bucket(step_start, step_end, BackendResultEnum("failure")) 433 data[0].append(waiting) 434 data[1].append(success) 435 data[2].append(failure) 436 cls.cache_action_graph_data(type, time=step_start, waiting=waiting, success=success, failure=failure) 437 438 for i in range(params["start"], params["end"], params["step"]): 439 data[3].append(time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(i))) 440 441 return data
442