2017-04-12 15:52:00 -05:00
|
|
|
from datetime import datetime
|
|
|
|
import dateutil.parser
|
2017-01-13 01:16:05 -06:00
|
|
|
from lxml import etree as ET
|
2017-04-28 15:42:09 -05:00
|
|
|
from osc import conf
|
2018-01-12 15:30:12 -06:00
|
|
|
from osc.core import show_project_meta
|
2018-01-17 18:12:42 -06:00
|
|
|
from osclib.core import devel_project_fallback
|
2018-01-25 21:27:29 -06:00
|
|
|
from osclib.core import request_age
|
2019-09-09 15:31:24 -05:00
|
|
|
from osclib.util import sha1_short
|
2017-04-17 23:06:24 -05:00
|
|
|
import re
|
2017-01-13 01:16:05 -06:00
|
|
|
|
2022-02-18 17:15:48 +01:00
|
|
|
|
2017-01-13 01:16:05 -06:00
|
|
|
class RequestSplitter(object):
|
2018-10-31 16:24:16 -05:00
|
|
|
def __init__(self, api, requests, in_ring, stageable=True):
|
2017-01-13 01:16:05 -06:00
|
|
|
self.api = api
|
|
|
|
self.requests = requests
|
|
|
|
self.in_ring = in_ring
|
2018-10-31 16:24:16 -05:00
|
|
|
self.stageable = stageable
|
2017-04-28 22:24:32 -05:00
|
|
|
self.config = conf.config[self.api.project]
|
|
|
|
|
2017-04-12 15:52:00 -05:00
|
|
|
# 55 minutes to avoid two staging bot loops of 30 minutes
|
2017-04-28 22:24:32 -05:00
|
|
|
self.request_age_threshold = int(self.config.get('splitter-request-age-threshold', 55 * 60))
|
|
|
|
self.staging_age_max = int(self.config.get('splitter-staging-age-max', 8 * 60 * 60))
|
2019-11-27 11:08:06 +01:00
|
|
|
special_packages = self.config.get('splitter-special-packages')
|
2018-01-12 15:40:56 -06:00
|
|
|
if special_packages is not None:
|
2017-10-16 16:48:17 -05:00
|
|
|
StrategySpecial.PACKAGES = special_packages.split(' ')
|
2017-02-17 21:51:11 -06:00
|
|
|
|
2017-01-13 01:16:05 -06:00
|
|
|
self.requests_ignored = self.api.get_ignored_requests()
|
2017-02-17 21:51:11 -06:00
|
|
|
|
2017-01-13 01:16:05 -06:00
|
|
|
self.reset()
|
2017-02-17 21:51:11 -06:00
|
|
|
# after propose_assignment()
|
|
|
|
self.proposal = {}
|
2017-01-13 01:16:05 -06:00
|
|
|
|
|
|
|
def reset(self):
|
2017-02-17 21:51:11 -06:00
|
|
|
self.strategy = None
|
2017-01-13 01:16:05 -06:00
|
|
|
self.filters = []
|
|
|
|
self.groups = []
|
|
|
|
|
|
|
|
# after split()
|
|
|
|
self.filtered = []
|
|
|
|
self.other = []
|
|
|
|
self.grouped = {}
|
2017-02-17 21:51:11 -06:00
|
|
|
|
2018-10-31 16:24:16 -05:00
|
|
|
if self.stageable:
|
|
|
|
# Require requests to be stageable (submit or delete package).
|
|
|
|
self.filter_add('./action[@type="submit" or (@type="delete" and ./target[@package])]')
|
|
|
|
|
2017-02-17 21:51:11 -06:00
|
|
|
def strategy_set(self, name, **kwargs):
|
|
|
|
self.reset()
|
|
|
|
|
|
|
|
class_name = 'Strategy{}'.format(name.lower().title())
|
|
|
|
cls = globals()[class_name]
|
|
|
|
self.strategy = cls(**kwargs)
|
|
|
|
self.strategy.apply(self)
|
|
|
|
|
|
|
|
def strategy_from_splitter_info(self, splitter_info):
|
|
|
|
strategy = splitter_info['strategy']
|
|
|
|
if 'args' in strategy:
|
|
|
|
self.strategy_set(strategy['name'], **strategy['args'])
|
|
|
|
else:
|
|
|
|
self.strategy_set(strategy['name'])
|
2017-01-13 01:16:05 -06:00
|
|
|
|
|
|
|
def filter_add(self, xpath):
|
|
|
|
self.filters.append(ET.XPath(xpath))
|
|
|
|
|
|
|
|
def filter_add_requests(self, requests):
|
|
|
|
requests = ' ' + ' '.join(requests) + ' '
|
|
|
|
self.filter_add('contains("{requests}", concat(" ", @id, " ")) or '
|
|
|
|
'contains("{requests}", concat(" ", ./action/target/@package, " "))'
|
|
|
|
.format(requests=requests))
|
|
|
|
|
2017-04-17 23:05:04 -05:00
|
|
|
def group_by(self, xpath, required=False):
|
2017-01-13 01:16:05 -06:00
|
|
|
self.groups.append(ET.XPath(xpath))
|
2017-04-17 23:05:04 -05:00
|
|
|
if required:
|
|
|
|
self.filter_add(xpath)
|
2017-01-13 01:16:05 -06:00
|
|
|
|
2019-11-19 18:07:22 +01:00
|
|
|
def is_staging_mergeable(self, staging):
|
|
|
|
return self.stagings[staging]['status'].find('staged_requests/request') is not None
|
|
|
|
|
2017-01-13 01:16:05 -06:00
|
|
|
def filter_only(self):
|
|
|
|
ret = []
|
|
|
|
for request in self.requests:
|
2017-04-12 15:39:47 -05:00
|
|
|
self.supplement(request)
|
2017-01-13 01:16:05 -06:00
|
|
|
if self.filter_check(request):
|
|
|
|
ret.append(request)
|
|
|
|
return ret
|
|
|
|
|
|
|
|
def split(self):
|
|
|
|
for request in self.requests:
|
2017-04-12 15:39:47 -05:00
|
|
|
self.supplement(request)
|
2017-01-13 01:16:05 -06:00
|
|
|
if not self.filter_check(request):
|
|
|
|
continue
|
|
|
|
|
2017-02-08 13:17:47 -06:00
|
|
|
ring = request.find('./action/target').get('ring')
|
|
|
|
if self.in_ring != (not ring):
|
2017-01-13 01:16:05 -06:00
|
|
|
# Request is of desired ring type.
|
|
|
|
key = self.group_key_build(request)
|
|
|
|
if key not in self.grouped:
|
|
|
|
self.grouped[key] = {
|
|
|
|
'bootstrap_required': False,
|
|
|
|
'requests': [],
|
|
|
|
}
|
|
|
|
|
|
|
|
self.grouped[key]['requests'].append(request)
|
|
|
|
|
|
|
|
if ring and ring.startswith('0'):
|
|
|
|
self.grouped[key]['bootstrap_required'] = True
|
|
|
|
else:
|
|
|
|
self.other.append(request)
|
|
|
|
|
2017-04-12 15:39:47 -05:00
|
|
|
def supplement(self, request):
|
2017-01-13 01:16:05 -06:00
|
|
|
""" Provide additional information for grouping """
|
2017-02-17 20:42:22 -06:00
|
|
|
if request.get('ignored'):
|
2017-04-17 22:55:41 -05:00
|
|
|
# Only supplement once.
|
2017-02-17 20:42:22 -06:00
|
|
|
return
|
|
|
|
|
2017-04-12 15:52:00 -05:00
|
|
|
history = request.find('history')
|
|
|
|
if history is not None:
|
2018-01-29 03:14:24 -06:00
|
|
|
age = request_age(request).total_seconds()
|
2018-01-25 21:27:29 -06:00
|
|
|
request.set('aged', str(age >= self.request_age_threshold))
|
2017-04-12 15:52:00 -05:00
|
|
|
|
2017-06-14 22:18:10 -05:00
|
|
|
request_type = request.find('./action').get('type')
|
2017-02-01 08:08:04 -06:00
|
|
|
target = request.find('./action/target')
|
|
|
|
target_project = target.get('project')
|
|
|
|
target_package = target.get('package')
|
2018-01-17 18:12:42 -06:00
|
|
|
devel, _ = devel_project_fallback(self.api.apiurl, target_project, target_package)
|
2017-06-14 22:18:10 -05:00
|
|
|
if not devel and request_type == 'submit':
|
|
|
|
devel = request.find('./action/source').get('project')
|
2017-01-13 01:16:05 -06:00
|
|
|
if devel:
|
2017-02-01 08:08:04 -06:00
|
|
|
target.set('devel_project', devel)
|
2017-04-17 23:06:24 -05:00
|
|
|
StrategySuper.supplement(request)
|
2017-01-13 01:16:05 -06:00
|
|
|
|
|
|
|
ring = self.ring_get(target_package)
|
|
|
|
if ring:
|
2017-02-01 08:08:04 -06:00
|
|
|
target.set('ring', ring)
|
2017-01-13 01:16:05 -06:00
|
|
|
|
|
|
|
request_id = int(request.get('id'))
|
|
|
|
if request_id in self.requests_ignored:
|
2017-03-03 14:57:16 -06:00
|
|
|
request.set('ignored', str(self.requests_ignored[request_id]))
|
2017-01-13 01:16:05 -06:00
|
|
|
else:
|
2017-04-12 15:44:05 -05:00
|
|
|
request.set('ignored', 'False')
|
2017-01-13 01:16:05 -06:00
|
|
|
|
2017-04-12 15:44:05 -05:00
|
|
|
request.set('postponed', 'False')
|
2017-02-18 00:00:12 -06:00
|
|
|
|
2017-01-13 01:16:05 -06:00
|
|
|
def ring_get(self, target_package):
|
2018-06-29 11:29:21 +02:00
|
|
|
if self.api.conlyadi:
|
|
|
|
return None
|
2017-01-13 01:16:05 -06:00
|
|
|
if self.api.crings:
|
|
|
|
ring = self.api.ring_packages_for_links.get(target_package)
|
|
|
|
if ring:
|
|
|
|
# Cut off *:Rings: prefix.
|
2019-11-27 11:08:06 +01:00
|
|
|
return ring[len(self.api.crings) + 1:]
|
2017-02-08 13:19:08 -06:00
|
|
|
else:
|
|
|
|
# Projects not using rings handle all requests as ring requests.
|
|
|
|
return self.api.project
|
2017-01-13 01:16:05 -06:00
|
|
|
return None
|
|
|
|
|
|
|
|
def filter_check(self, request):
|
|
|
|
for xpath in self.filters:
|
|
|
|
if not xpath(request):
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
|
|
def group_key_build(self, request):
|
|
|
|
if len(self.groups) == 0:
|
|
|
|
return 'all'
|
|
|
|
|
|
|
|
key = []
|
|
|
|
for xpath in self.groups:
|
|
|
|
element = xpath(request)
|
|
|
|
if element:
|
|
|
|
key.append(element[0])
|
2017-02-08 13:13:17 -06:00
|
|
|
if len(key) == 0:
|
|
|
|
return '00'
|
2017-01-13 01:16:05 -06:00
|
|
|
return '__'.join(key)
|
2017-01-13 01:23:17 -06:00
|
|
|
|
2019-11-19 18:07:22 +01:00
|
|
|
def should_staging_merge(self, staging):
|
|
|
|
staging = self.stagings[staging]
|
|
|
|
if (not staging['bootstrapped'] and
|
|
|
|
staging['splitter_info']['strategy']['name'] in ('devel', 'super') and
|
2022-02-18 15:13:50 +01:00
|
|
|
staging['status'].get('state') not in ('acceptable', 'review')):
|
2017-05-04 16:48:57 -05:00
|
|
|
# Simplistic attempt to allow for followup requests to be staged
|
|
|
|
# after age max has been passed while still stopping when ready.
|
|
|
|
return True
|
|
|
|
|
2019-11-19 18:07:22 +01:00
|
|
|
if 'activated' not in staging['splitter_info']:
|
2017-04-14 17:03:56 -05:00
|
|
|
# No information on the age of the staging.
|
|
|
|
return False
|
|
|
|
|
|
|
|
# Allows for immediate staging when possible while not blocking requests
|
|
|
|
# created shortly after. This method removes the need to wait to create
|
|
|
|
# a larger staging at once while not ending up with lots of tiny
|
|
|
|
# stagings. As such this handles both high and low request backlogs.
|
2019-11-19 18:07:22 +01:00
|
|
|
activated = dateutil.parser.parse(staging['splitter_info']['activated'])
|
2017-04-14 17:03:56 -05:00
|
|
|
delta = datetime.utcnow() - activated
|
|
|
|
return delta.total_seconds() <= self.staging_age_max
|
2017-02-17 21:51:11 -06:00
|
|
|
|
2019-11-19 18:07:22 +01:00
|
|
|
def is_staging_considerable(self, staging):
|
|
|
|
staging = self.stagings[staging]
|
|
|
|
if staging['status'].find('staged_requests/request') is not None:
|
|
|
|
return False
|
|
|
|
return self.api.prj_frozen_enough(staging['project'])
|
2017-02-17 21:51:11 -06:00
|
|
|
|
2017-02-17 21:27:50 -06:00
|
|
|
def stagings_load(self, stagings):
|
2017-02-17 21:51:11 -06:00
|
|
|
self.stagings = {}
|
|
|
|
self.stagings_considerable = []
|
|
|
|
self.stagings_mergeable = []
|
|
|
|
self.stagings_mergeable_none = []
|
2017-01-13 01:23:17 -06:00
|
|
|
|
|
|
|
# Use specified list of stagings, otherwise only empty, letter stagings.
|
|
|
|
if len(stagings) == 0:
|
2017-04-28 22:24:32 -05:00
|
|
|
whitelist = self.config.get('splitter-whitelist')
|
2017-04-28 15:42:09 -05:00
|
|
|
if whitelist:
|
|
|
|
stagings = whitelist.split()
|
|
|
|
else:
|
|
|
|
stagings = self.api.get_staging_projects_short()
|
2017-04-14 17:03:18 -05:00
|
|
|
should_always = False
|
|
|
|
else:
|
|
|
|
# If the an explicit list of stagings was included then always
|
|
|
|
# attempt to use even if the normal conditions are not met.
|
|
|
|
should_always = True
|
2017-01-13 01:23:17 -06:00
|
|
|
|
|
|
|
for staging in stagings:
|
|
|
|
project = self.api.prj_from_short(staging)
|
2019-11-19 18:07:22 +01:00
|
|
|
status = self.api.project_status(project)
|
2017-12-21 00:31:01 -06:00
|
|
|
bootstrapped = self.api.is_staging_bootstrapped(project)
|
2017-02-17 21:51:11 -06:00
|
|
|
|
|
|
|
# Store information about staging.
|
|
|
|
self.stagings[staging] = {
|
|
|
|
'project': project,
|
2017-05-09 19:40:15 -05:00
|
|
|
'bootstrapped': bootstrapped,
|
2019-11-19 18:07:22 +01:00
|
|
|
# TODO: find better place for splitter info
|
2022-02-18 17:23:02 +01:00
|
|
|
'splitter_info': {'strategy': {'name': 'none'}},
|
2019-11-19 18:07:22 +01:00
|
|
|
'status': status
|
2017-02-17 21:51:11 -06:00
|
|
|
}
|
2017-01-13 01:23:17 -06:00
|
|
|
|
2017-02-17 21:51:11 -06:00
|
|
|
# Decide if staging of interested.
|
2019-11-19 18:07:22 +01:00
|
|
|
if self.is_staging_mergeable(staging) and (should_always or self.should_staging_merge(staging)):
|
|
|
|
if self.stagings[staging]['splitter_info']['strategy']['name'] == 'none':
|
2017-02-17 21:51:11 -06:00
|
|
|
self.stagings_mergeable_none.append(staging)
|
|
|
|
else:
|
|
|
|
self.stagings_mergeable.append(staging)
|
2019-11-19 18:07:22 +01:00
|
|
|
elif self.is_staging_considerable(staging):
|
2017-02-17 21:51:11 -06:00
|
|
|
self.stagings_considerable.append(staging)
|
2017-01-13 01:23:17 -06:00
|
|
|
|
|
|
|
# Allow both considered and remaining to be accessible after proposal.
|
2017-02-17 21:51:11 -06:00
|
|
|
self.stagings_available = list(self.stagings_considerable)
|
2017-01-13 01:23:17 -06:00
|
|
|
|
2017-02-17 21:51:11 -06:00
|
|
|
return (len(self.stagings_considerable) +
|
|
|
|
len(self.stagings_mergeable) +
|
|
|
|
len(self.stagings_mergeable_none))
|
2017-01-13 01:23:17 -06:00
|
|
|
|
2017-02-17 21:12:47 -06:00
|
|
|
def propose_assignment(self):
|
2017-02-17 21:51:11 -06:00
|
|
|
# Attempt to assign groups that have bootstrap_required first.
|
2017-01-13 01:23:17 -06:00
|
|
|
for group in sorted(self.grouped.keys()):
|
|
|
|
if self.grouped[group]['bootstrap_required']:
|
2017-02-17 21:51:11 -06:00
|
|
|
staging = self.propose_staging(choose_bootstrapped=True)
|
|
|
|
if staging:
|
|
|
|
self.requests_assign(group, staging)
|
2017-02-18 00:00:12 -06:00
|
|
|
else:
|
|
|
|
self.requests_postpone(group)
|
2017-01-13 01:23:17 -06:00
|
|
|
|
|
|
|
# Assign groups that do not have bootstrap_required and fallback to a
|
|
|
|
# bootstrapped staging if no non-bootstrapped stagings available.
|
|
|
|
for group in sorted(self.grouped.keys()):
|
|
|
|
if not self.grouped[group]['bootstrap_required']:
|
2017-02-17 21:51:11 -06:00
|
|
|
staging = self.propose_staging(choose_bootstrapped=False)
|
|
|
|
if staging:
|
|
|
|
self.requests_assign(group, staging)
|
2017-01-13 01:23:17 -06:00
|
|
|
continue
|
|
|
|
|
2017-02-17 21:51:11 -06:00
|
|
|
staging = self.propose_staging(choose_bootstrapped=True)
|
|
|
|
if staging:
|
|
|
|
self.requests_assign(group, staging)
|
2017-02-18 00:00:12 -06:00
|
|
|
else:
|
|
|
|
self.requests_postpone(group)
|
2017-02-17 21:51:11 -06:00
|
|
|
|
|
|
|
def requests_assign(self, group, staging, merge=False):
|
|
|
|
# Arbitrary, but descriptive group key for proposal.
|
|
|
|
key = '{}#{}@{}'.format(len(self.proposal), self.strategy.key, group)
|
|
|
|
self.proposal[key] = {
|
|
|
|
'bootstrap_required': self.grouped[group]['bootstrap_required'],
|
|
|
|
'group': group,
|
|
|
|
'requests': {},
|
|
|
|
'staging': staging,
|
|
|
|
'strategy': self.strategy.info(),
|
|
|
|
}
|
|
|
|
if merge:
|
|
|
|
self.proposal[key]['merge'] = True
|
|
|
|
|
|
|
|
# Covert request nodes to simple proposal form.
|
|
|
|
for request in self.grouped[group]['requests']:
|
|
|
|
self.proposal[key]['requests'][int(request.get('id'))] = request.find('action/target').get('package')
|
|
|
|
self.requests.remove(request)
|
|
|
|
|
|
|
|
return key
|
2017-01-13 01:23:17 -06:00
|
|
|
|
2017-02-18 00:00:12 -06:00
|
|
|
def requests_postpone(self, group):
|
|
|
|
if self.strategy.name == 'none':
|
|
|
|
return
|
|
|
|
|
|
|
|
for request in self.grouped[group]['requests']:
|
2017-04-12 15:44:05 -05:00
|
|
|
request.set('postponed', 'True')
|
2017-01-13 01:23:17 -06:00
|
|
|
|
|
|
|
def propose_staging(self, choose_bootstrapped):
|
|
|
|
found = False
|
2017-02-17 21:51:11 -06:00
|
|
|
for staging in sorted(self.stagings_available):
|
|
|
|
if choose_bootstrapped == self.stagings[staging]['bootstrapped']:
|
2017-01-13 01:23:17 -06:00
|
|
|
found = True
|
|
|
|
break
|
|
|
|
|
|
|
|
if found:
|
2017-02-17 21:51:11 -06:00
|
|
|
self.stagings_available.remove(staging)
|
2017-01-13 01:23:17 -06:00
|
|
|
return staging
|
|
|
|
|
|
|
|
return None
|
2017-02-17 21:51:11 -06:00
|
|
|
|
|
|
|
def strategies_try(self):
|
|
|
|
strategies = (
|
|
|
|
'special',
|
2017-04-17 23:54:54 -05:00
|
|
|
'quick',
|
2017-04-17 23:06:24 -05:00
|
|
|
'super',
|
2017-02-17 21:51:11 -06:00
|
|
|
'devel',
|
|
|
|
)
|
|
|
|
|
2019-09-03 15:17:32 -05:00
|
|
|
for strategy in strategies:
|
|
|
|
self.strategy_try(strategy)
|
2017-02-17 21:51:11 -06:00
|
|
|
|
|
|
|
def strategy_try(self, name):
|
|
|
|
self.strategy_set(name)
|
|
|
|
self.split()
|
|
|
|
|
|
|
|
groups = self.strategy.desirable(self)
|
|
|
|
if len(groups) == 0:
|
|
|
|
return
|
|
|
|
self.filter_grouped(groups)
|
|
|
|
|
|
|
|
self.propose_assignment()
|
|
|
|
|
|
|
|
def strategy_do(self, name, **kwargs):
|
|
|
|
self.strategy_set(name, **kwargs)
|
|
|
|
self.split()
|
|
|
|
self.propose_assignment()
|
2017-02-18 00:03:55 -06:00
|
|
|
|
|
|
|
def strategy_do_non_bootstrapped(self, name, **kwargs):
|
|
|
|
self.strategy_set(name, **kwargs)
|
|
|
|
self.filter_add('./action/target[not(starts-with(@ring, "0"))]')
|
|
|
|
self.split()
|
|
|
|
self.propose_assignment()
|
2017-02-17 21:51:11 -06:00
|
|
|
|
|
|
|
def filter_grouped(self, groups):
|
|
|
|
for group in sorted(self.grouped.keys()):
|
|
|
|
if group not in groups:
|
|
|
|
del self.grouped[group]
|
|
|
|
|
2019-11-19 18:07:22 +01:00
|
|
|
def merge_staging(self, staging):
|
|
|
|
staging = self.stagings[staging]
|
|
|
|
splitter_info = staging['splitter_info']
|
2017-02-17 21:51:11 -06:00
|
|
|
self.strategy_from_splitter_info(splitter_info)
|
|
|
|
|
|
|
|
if not self.stagings[staging]['bootstrapped']:
|
|
|
|
# If when the strategy was first run the resulting staging was not
|
|
|
|
# bootstrapped then ensure no bootstrapped packages are included.
|
|
|
|
self.filter_add('./action/target[not(starts-with(@ring, "0"))]')
|
|
|
|
|
|
|
|
self.split()
|
|
|
|
|
|
|
|
group = splitter_info['group']
|
|
|
|
if group in self.grouped:
|
2022-02-18 17:01:38 +01:00
|
|
|
self.requests_assign(group, staging, merge=True)
|
2017-02-17 21:51:11 -06:00
|
|
|
|
|
|
|
def merge(self, strategy_none=False):
|
|
|
|
stagings = self.stagings_mergeable_none if strategy_none else self.stagings_mergeable
|
|
|
|
for staging in sorted(stagings):
|
2019-11-19 18:07:22 +01:00
|
|
|
self.merge_staging(staging)
|
2017-02-17 21:51:11 -06:00
|
|
|
|
2022-02-18 17:15:48 +01:00
|
|
|
|
2017-02-17 21:51:11 -06:00
|
|
|
class Strategy(object):
|
|
|
|
def __init__(self, **kwargs):
|
|
|
|
self.kwargs = kwargs
|
|
|
|
self.name = self.__class__.__name__[8:].lower()
|
|
|
|
self.key = self.name
|
|
|
|
if kwargs:
|
2019-09-09 15:31:24 -05:00
|
|
|
self.key += '_' + sha1_short(str(kwargs))
|
2017-02-17 21:51:11 -06:00
|
|
|
|
|
|
|
def info(self):
|
|
|
|
info = {'name': self.name}
|
|
|
|
if self.kwargs:
|
|
|
|
info['args'] = self.kwargs
|
|
|
|
return info
|
|
|
|
|
2017-04-17 23:54:19 -05:00
|
|
|
def desirable(self, splitter):
|
|
|
|
return splitter.grouped.keys()
|
|
|
|
|
2022-02-18 17:15:48 +01:00
|
|
|
|
2017-02-17 21:51:11 -06:00
|
|
|
class StrategyNone(Strategy):
|
|
|
|
def apply(self, splitter):
|
2017-04-12 15:52:00 -05:00
|
|
|
# All other strategies that inherit this are not restricted by age as
|
|
|
|
# the age restriction is used to allow other strategies to be observed.
|
|
|
|
if type(self) is StrategyNone:
|
|
|
|
splitter.filter_add('@aged="True"')
|
2017-04-12 15:44:05 -05:00
|
|
|
splitter.filter_add('@ignored="False"')
|
|
|
|
splitter.filter_add('@postponed="False"')
|
2017-02-17 21:51:11 -06:00
|
|
|
|
2022-02-18 17:15:48 +01:00
|
|
|
|
2017-02-17 21:51:11 -06:00
|
|
|
class StrategyRequests(Strategy):
|
|
|
|
def apply(self, splitter):
|
|
|
|
splitter.filter_add_requests(self.kwargs['requests'])
|
|
|
|
|
2022-02-18 17:15:48 +01:00
|
|
|
|
2017-02-17 21:51:11 -06:00
|
|
|
class StrategyCustom(StrategyNone):
|
|
|
|
def apply(self, splitter):
|
|
|
|
if 'filters' not in self.kwargs:
|
|
|
|
super(StrategyCustom, self).apply(splitter)
|
|
|
|
else:
|
2019-09-03 15:17:32 -05:00
|
|
|
for xpath in self.kwargs['filters']:
|
|
|
|
splitter.filter_add(xpath)
|
2017-02-17 21:51:11 -06:00
|
|
|
|
|
|
|
if 'groups' in self.kwargs:
|
2019-09-03 15:17:32 -05:00
|
|
|
for group in self.kwargs['groups']:
|
|
|
|
splitter.group_by(group)
|
2017-02-17 21:51:11 -06:00
|
|
|
|
2022-02-18 17:15:48 +01:00
|
|
|
|
2017-02-17 21:51:11 -06:00
|
|
|
class StrategyDevel(StrategyNone):
|
|
|
|
GROUP_MIN = 7
|
2017-03-14 01:22:13 -05:00
|
|
|
GROUP_MIN_MAP = {
|
|
|
|
'YaST:Head': 2,
|
|
|
|
}
|
2017-02-17 21:51:11 -06:00
|
|
|
|
|
|
|
def apply(self, splitter):
|
|
|
|
super(StrategyDevel, self).apply(splitter)
|
2017-04-17 23:05:04 -05:00
|
|
|
splitter.group_by('./action/target/@devel_project', True)
|
2017-02-17 21:51:11 -06:00
|
|
|
|
|
|
|
def desirable(self, splitter):
|
|
|
|
groups = []
|
|
|
|
for group, info in sorted(splitter.grouped.items()):
|
2017-03-14 01:22:13 -05:00
|
|
|
if len(info['requests']) >= self.GROUP_MIN_MAP.get(group, self.GROUP_MIN):
|
2017-02-17 21:51:11 -06:00
|
|
|
groups.append(group)
|
|
|
|
return groups
|
|
|
|
|
2022-02-18 17:15:48 +01:00
|
|
|
|
2017-04-17 23:06:24 -05:00
|
|
|
class StrategySuper(StrategyDevel):
|
|
|
|
# Regex pattern prefix representing super devel projects that should be
|
|
|
|
# grouped together. The whole pattern will be used and the last colon
|
|
|
|
# stripped, otherwise the first match group.
|
|
|
|
PATTERNS = [
|
|
|
|
'KDE:',
|
|
|
|
'GNOME:',
|
|
|
|
'(multimedia):(?:libs|apps)',
|
|
|
|
'zypp:'
|
|
|
|
]
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def init(cls):
|
|
|
|
cls.patterns = []
|
|
|
|
for pattern in cls.PATTERNS:
|
|
|
|
cls.patterns.append(re.compile(pattern))
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def supplement(cls, request):
|
|
|
|
if not hasattr(cls, 'patterns'):
|
|
|
|
cls.init()
|
|
|
|
|
|
|
|
target = request.find('./action/target')
|
|
|
|
devel_project = target.get('devel_project')
|
|
|
|
for pattern in cls.patterns:
|
|
|
|
match = pattern.match(devel_project)
|
|
|
|
if match:
|
|
|
|
prefix = match.group(0 if len(match.groups()) == 0 else 1).rstrip(':')
|
|
|
|
target.set('devel_project_super', prefix)
|
|
|
|
break
|
|
|
|
|
|
|
|
def apply(self, splitter):
|
|
|
|
super(StrategySuper, self).apply(splitter)
|
|
|
|
splitter.groups = []
|
|
|
|
splitter.group_by('./action/target/@devel_project_super', True)
|
|
|
|
|
2022-02-18 17:15:48 +01:00
|
|
|
|
2017-04-17 23:54:54 -05:00
|
|
|
class StrategyQuick(StrategyNone):
|
|
|
|
def apply(self, splitter):
|
|
|
|
super(StrategyQuick, self).apply(splitter)
|
2018-01-12 15:30:12 -06:00
|
|
|
|
2019-08-13 16:55:42 -05:00
|
|
|
# Origin manager accepted which means any extra reviews have been added.
|
|
|
|
splitter.filter_add('./review[@by_user="origin-manager" and @state="accepted"]')
|
2018-01-12 15:30:12 -06:00
|
|
|
|
|
|
|
# No @by_project reviews that are not accepted. If not first round stage
|
|
|
|
# this should also ignore previous staging project reviews or already
|
|
|
|
# accepted human reviews.
|
|
|
|
splitter.filter_add('not(./review[@by_project and @state!="accepted"])')
|
|
|
|
|
|
|
|
# Only allow reviews by whitelisted groups and users as all others will
|
|
|
|
# be considered non-quick (like @by_group="legal-auto"). The allowed
|
|
|
|
# groups are only those configured as reviewers on the target project.
|
2018-08-16 00:26:53 -05:00
|
|
|
meta = ET.fromstringlist(show_project_meta(splitter.api.apiurl, splitter.api.project))
|
2018-01-12 15:30:12 -06:00
|
|
|
allowed_groups = meta.xpath('group[@role="reviewer"]/@groupid')
|
|
|
|
self.filter_review_whitelist(splitter, 'by_group', allowed_groups)
|
|
|
|
|
|
|
|
def filter_review_whitelist(self, splitter, attribute, allowed):
|
|
|
|
# Rather than generate a bunch of @attribute="allowed[0]" pairs
|
|
|
|
# contains is used, but the attribute must be asserted first since
|
|
|
|
# concat() loses that requirement.
|
|
|
|
allowed = ' ' + ' '.join(allowed) + ' '
|
|
|
|
splitter.filter_add(
|
|
|
|
# Assert that no(non-whitelisted and not accepted) review is found.
|
|
|
|
'not(./review[@{attribute} and '
|
|
|
|
'not(contains("{allowed}", concat(" ", @{attribute}, " "))) and '
|
|
|
|
'@state!="accepted"])'.format(attribute=attribute, allowed=allowed))
|
2017-04-17 23:54:54 -05:00
|
|
|
|
2022-02-18 17:15:48 +01:00
|
|
|
|
2017-02-17 21:51:11 -06:00
|
|
|
class StrategySpecial(StrategyNone):
|
2018-01-12 15:40:56 -06:00
|
|
|
# Configurable via splitter-special-packages.
|
2017-02-17 21:51:11 -06:00
|
|
|
PACKAGES = [
|
|
|
|
'gcc',
|
2018-06-15 21:01:47 +08:00
|
|
|
'gcc8',
|
2017-02-17 21:51:11 -06:00
|
|
|
'glibc',
|
|
|
|
'kernel-source',
|
|
|
|
]
|
|
|
|
|
|
|
|
def apply(self, splitter):
|
|
|
|
super(StrategySpecial, self).apply(splitter)
|
|
|
|
splitter.filter_add_requests(self.PACKAGES)
|
|
|
|
splitter.group_by('./action/target/@package')
|