Parse review comments to get results of external CI

It's assumed that CI was executed on the review if comment message is left and:
 a) message satisfies success and failure patterns (if they exist)
or
 b) vote is set

Thus the process supports case when CI votes on review and non-voting CIs that
just leave messages. However it is required that CI leaves a comment.

Note: the schema is updated to support CI definitions like these:
            "ci": {
                "id": "cisco_neutron_ci",
                "success_pattern": "neutron_zuul \\S+ : SUCCESS",
                "failure_pattern": "neutron_zuul \\S+ : FAILURE"
            }

Closes bug 1317554

Change-Id: Ia73ab70fd633b2278a3d5d3f1ba67d3018370e4b
This commit is contained in:
Ilya Shakhat 2014-05-12 23:06:58 +04:00 committed by Ilya Shakhat
parent 4593269222
commit 889fb8fd7b
5 changed files with 227 additions and 128 deletions

View File

@ -45,6 +45,17 @@ def find_comment(review, ci):
return None
def find_vote(review, ci_id):
for approval in (review['currentPatchSet'].get('approvals') or []):
if approval['type'] not in ['Verified', 'VRIF']:
continue
if approval['by'].get('username') == ci_id:
return approval['value'] in ['1', '2']
return None
def process_reviews(review_iterator, ci_ids_map, project_id):
branch_ci_set = set()
@ -52,36 +63,53 @@ def process_reviews(review_iterator, ci_ids_map, project_id):
review_url = review['url']
branch = review['branch']
for approval in (review['currentPatchSet'].get('approvals') or []):
if approval['type'] not in ['Verified', 'VRIF']:
for comment in reversed(review.get('comments') or []):
ci_id = comment['reviewer'].get('username')
if ci_id not in ci_ids_map:
continue
ci = approval['by']['username']
if ci not in ci_ids_map:
continue
branch_ci = (branch, ci)
branch_ci = (branch, ci_id)
if branch_ci in branch_ci_set:
continue # already seen, ignore
branch_ci_set.add(branch_ci)
comment = find_comment(review, ci)
message = comment['message']
prefix = 'Patch Set %s:' % review['currentPatchSet']['number']
if comment['message'].find(prefix) != 0:
break # all comments from the latest patch set passed
for one_ci in ci_ids_map[ci]:
yield {
(project_id,
one_ci['vendor'].lower(),
one_ci['driver_name'].lower()): {
'os_versions_map': {
branch: {
'comment': comment,
'timestamp': approval['grantedOn'],
'review_url': review_url
message = message[len(prefix):].strip()
for one_ci in ci_ids_map[ci_id]:
result = None
# try to get result by parsing comment message
success_pattern = one_ci.get('success_pattern')
failure_pattern = one_ci.get('failure_pattern')
if success_pattern and re.search(success_pattern, message):
result = True
elif failure_pattern and re.search(failure_pattern, message):
result = False
# try to get result from vote
if not result:
result = find_vote(review, ci_id)
if result:
yield {
(project_id,
one_ci['vendor'].lower(),
one_ci['driver_name'].lower()): {
'os_versions_map': {
branch: {
'comment': message,
'timestamp': comment['timestamp'],
'review_url': review_url
}
}
}
}
}
}
def update_generator(memcached, default_data, ci_ids_map, force_update=False):
@ -116,20 +144,32 @@ def _get_hash(data):
def build_ci_map(drivers):
ci_map = collections.defaultdict(list)
for driver in drivers:
if 'ci_id' in driver:
if 'ci' in driver:
value = {
'vendor': driver['vendor'],
'driver_name': driver['name'],
}
if 'success_pattern' in driver:
value['success_pattern'] = driver['success_pattern']
if 'failure_pattern' in driver:
value['failure_pattern'] = driver['failure_pattern']
ci = driver['ci']
if 'success_pattern' in ci:
value['success_pattern'] = ci['success_pattern']
if 'failure_pattern' in ci:
value['failure_pattern'] = ci['failure_pattern']
ci_map[driver['ci_id']].append(value)
ci_map[ci['id']].append(value)
return ci_map
def transform_default_data(default_data):
for driver in default_data['drivers']:
driver['os_versions_map'] = {}
if 'releases' in driver:
for release in driver['releases']:
driver['os_versions_map'][release] = {
'success': True,
'comment': 'self-tested verification'
}
def main():
# init conf and logging
conf = cfg.CONF
@ -153,21 +193,7 @@ def main():
LOG.critical('Unable to load default data')
return not 0
ci_ids_map = collections.defaultdict(set)
for driver in default_data['drivers']:
vendor = driver['vendor']
driver_name = driver['name']
if 'ci_id' in driver:
ci_id = driver['ci_id']
ci_ids_map[ci_id].add((vendor, driver_name))
driver['os_versions_map'] = {}
if 'releases' in driver:
for release in driver['releases']:
driver['os_versions_map'][release] = {
'success': True,
'comment': 'self-tested verification'
}
ci_ids_map = build_ci_map(default_data['drivers'])
update = {}
if not cfg.CONF.force_update:
@ -191,13 +217,17 @@ def main():
else:
update[key]['os_versions_map'][os_version] = info
# write default data into memcache
transform_default_data(default_data)
memcache_inst.set('driverlog:default_data', default_data)
memcache_inst.set('driverlog:update', update)
old_dd_hash = memcache_inst.get('driverlog:default_data_hash')
new_dd_hash = _get_hash(default_data)
memcache_inst.set('driverlog:default_data_hash', new_dd_hash)
# write update into memcache
memcache_inst.set('driverlog:update', update)
if has_update or old_dd_hash != new_dd_hash:
memcache_inst.set('driverlog:update_hash', time.time())

View File

@ -412,7 +412,6 @@
}
],
"wiki": "http://docs.openstack.org/trunk/config-reference/content/lvm-volume-driver.html",
"ci_id": "jenkins",
"releases": ["Folsom", "Grizzly", "Havana", "Icehouse"]
},
{
@ -524,7 +523,9 @@
],
"wiki": "https://wiki.openstack.org/wiki/Arista-neutron-ml2-driver",
"releases": ["Havana", "Icehouse", "Juno"],
"ci_id": "arista-test"
"ci": {
"id": "arista-test"
}
},
{
"project_id": "openstack/neutron",
@ -539,7 +540,9 @@
}
],
"wiki": "http://docs.openstack.org/trunk/config-reference/content/networking-plugin-ml2_bigswitch.html",
"ci_id": "bsn",
"ci": {
"id": "bsn"
},
"releases": ["Icehouse"]
},
{
@ -555,7 +558,9 @@
}
],
"wiki": "http://www.openflowhub.org/display/floodlightcontroller/OpenStack",
"ci_id": "bsn",
"ci": {
"id": "bsn"
},
"releases": ["Folsom", "Grizzly", "Havana", "Icehouse"]
},
{
@ -572,7 +577,9 @@
],
"wiki": "https://wiki.openstack.org/wiki/Brocade-neutron-plugin",
"releases": ["Folsom", "Grizzly", "Havana", "Icehouse"],
"ci_id": "bci"
"ci": {
"id": "bci"
}
},
{
"project_id": "openstack/neutron",
@ -588,7 +595,9 @@
],
"wiki": "https://wiki.openstack.org/wiki/Brocade-ML2-Mechanism",
"releases": ["Icehouse"],
"ci_id": "bci"
"ci": {
"id": "bci"
}
},
{
"project_id": "openstack/neutron",
@ -603,7 +612,9 @@
],
"wiki": "https://wiki.openstack.org/wiki/Neutron/ML2/MechCiscoNexus",
"releases": ["Havana", "Icehouse"],
"ci_id": "cisco_neutron_ci"
"ci": {
"id": "cisco_neutron_ci"
}
},
{
"project_id": "openstack/neutron",
@ -618,7 +629,9 @@
],
"wiki": "https://wiki.openstack.org/wiki/Cisco-neutron",
"releases": ["Grizzly", "Havana", "Icehouse"],
"ci_id": "cisco_neutron_ci"
"ci": {
"id": "cisco_neutron_ci"
}
},
{
"project_id": "openstack/neutron",
@ -638,7 +651,9 @@
}
],
"wiki": "http://www.cloudbase.it/quantum-hyper-v-plugin/",
"ci_id": "hyper-v-ci",
"ci": {
"id": "hyper-v-ci"
},
"releases": ["Grizzly", "Havana", "Icehouse"]
},
{
@ -654,7 +669,9 @@
}
],
"wiki": "https://wiki.openstack.org/wiki/Neutron/EmbraneNeutronPlugin",
"ci_id": "eci"
"ci": {
"id": "eci"
}
},
{
"project_id": "openstack/neutron",
@ -670,7 +687,9 @@
],
"releases": ["Icehouse"],
"wiki": "https://wiki.openstack.org/wiki/Neutron/LBaaS/EmbraneDriver",
"ci_id": "eci"
"ci": {
"id": "eci"
}
},
{
"project_id": "openstack/neutron",
@ -713,7 +732,9 @@
],
"wiki": "https://wiki.openstack.org/wiki/Mellanox-Neutron",
"releases": ["Havana", "Icehouse"],
"ci_id": "mellanox"
"ci": {
"id": "mellanox"
}
},
{
"project_id": "openstack/neutron",
@ -728,7 +749,9 @@
],
"wiki": "https://wiki.openstack.org/wiki/Mellanox-Neutron-ML2",
"releases": ["Icehouse", "Juno"],
"ci_id": "mellanox"
"ci": {
"id": "mellanox"
}
},
{
"project_id": "openstack/neutron",
@ -743,7 +766,9 @@
}
],
"wiki": "https://wiki.openstack.org/wiki/Spec-QuantumMidoNetPlugin",
"ci_id": "midokura"
"ci": {
"id": "midokura"
}
},
{
"project_id": "openstack/neutron",
@ -758,7 +783,9 @@
}
],
"wiki": "https://wiki.openstack.org/wiki/Neutron/NEC_OpenFlow_Plugin",
"ci_id": "nec-openstack-ci"
"ci": {
"id": "nec-openstack-ci"
}
},
{
"project_id": "openstack/neutron",
@ -773,7 +800,9 @@
}
],
"wiki": "http://docs.openstack.org/trunk/config-reference/content/networking-plugin-nuage.html",
"ci_id": "nuage-ci"
"ci": {
"id": "nuage-ci"
}
},
{
"project_id": "openstack/neutron",
@ -781,7 +810,9 @@
"name": "One Convergence NVSD Controller",
"description": "One Convergence Neutron plugin provides Neutron APIs for the network virtualization solution implemented with One Convergence Network Virtualization and Services Delivery(NVSD) Controller.",
"wiki": "https://github.com/openstack/neutron/tree/master/neutron/plugins/oneconvergence",
"ci_id": "oneconvergence"
"ci": {
"id": "oneconvergence"
}
},
{
"project_id": "openstack/neutron",
@ -796,7 +827,9 @@
}
],
"wiki": "http://openstack.redhat.com/OpenDaylight_integration",
"ci_id": "odl-jenkins"
"ci": {
"id": "odl-jenkins"
}
},
{
"project_id": "openstack/neutron",
@ -838,7 +871,9 @@
}
],
"wiki": "https://github.com/osrg/ryu/wiki/configuration_openstack_havana_with_ryu",
"ci_id": "neutronryu"
"ci": {
"id": "neutronryu"
}
},
{
"project_id": "openstack/neutron",
@ -867,7 +902,9 @@
}
],
"wiki": "https://wiki.openstack.org/wiki/PLUMgrid-Neutron",
"ci_id": "plumgrid-ci"
"ci": {
"id": "plumgrid-ci"
}
},
{
"project_id": "openstack/neutron",
@ -883,7 +920,9 @@
],
"releases": ["Havana", "Icehouse", "Juno"],
"wiki": "https://wiki.openstack.org/wiki/Neutron/ML2/Tail-f-NCS-neutron-ml2-driver",
"ci_id": "tailfncs"
"ci": {
"id": "tailfncs"
}
},
{
"project_id": "openstack/neutron",
@ -900,7 +939,9 @@
}
],
"releases": ["Essex", "Folsom", "Grizzly", "Havana", "Icehouse"],
"ci_id": "vmwareminesweeper"
"ci": {
"id": "vmwareminesweeper"
}
},
{
"project_id": "openstack/neutron",
@ -915,7 +956,9 @@
}
],
"wiki": "https://wiki.openstack.org/wiki/Neutron/vArmour-Firewall",
"ci_id": "varmourci"
"ci": {
"id": "varmourci"
}
},
{
"project_id": "openstack/nova",
@ -935,7 +978,9 @@
}
],
"wiki": "http://wiki.cloudbase.it/hyperv-tempest-exclusions",
"ci_id": "hyper-v-ci",
"ci": {
"id": "hyper-v-ci"
},
"releases": ["Folsom", "Grizzly", "Havana", "Icehouse"]
},
{
@ -1003,8 +1048,7 @@
"email": "mikal@stillhq.com"
}
],
"wiki": "http://docs.openstack.org/trunk/config-reference/content/kvm.html",
"ci_id": "jenkins"
"wiki": "http://docs.openstack.org/trunk/config-reference/content/kvm.html"
},
{
"project_id": "openstack/nova",
@ -1030,8 +1074,7 @@
"email": "mikal@stillhq.com"
}
],
"wiki": "http://docs.openstack.org/trunk/config-reference/content/qemu.html",
"ci_id": "jenkins"
"wiki": "http://docs.openstack.org/trunk/config-reference/content/qemu.html"
},
{
"project_id": "openstack/nova",
@ -1045,7 +1088,9 @@
}
],
"wiki": "http://docs.openstack.org/trunk/config-reference/content/introduction-to-xen.html",
"ci_id": "citrix_xenserver_ci",
"ci": {
"id": "citrix_xenserver_ci"
},
"releases": ["Austin", "Bexar", "Cactus", "Diablo", "Essex", "Folsom", "Grizzly", "Havana", "Icehouse"]
},
{
@ -1067,7 +1112,9 @@
"name": "VMware vSphere",
"description": "OpenStack Compute supports the VMware vSphere product family. The VMware vCenter driver enables the nova-compute service to communicate with a VMware vCenter server that manages one or more ESX host clusters.",
"wiki": "http://docs.openstack.org/trunk/config-reference/content/vmware.html",
"ci_id": "vmwareminesweeper",
"ci": {
"id": "vmwareminesweeper"
},
"releases": ["Grizzly", "Havana", "Icehouse"],
"maintainers": [
{
@ -1112,7 +1159,9 @@
}
],
"wiki": "http://docs.openstack.org/developer/sahara/userdoc/vanilla_plugin.html",
"ci_id": "savanna-ci"
"ci": {
"id": "savanna-ci"
}
}
]
}

View File

@ -65,17 +65,12 @@
"releases": {
"type": "array",
"items": {
"type": "string"
"type": "string",
"pattern": "^[\\w]+$"
}
},
"ci_id": {
"type": "string"
},
"success_pattern": {
"type": "string"
},
"failure_pattern": {
"type": "string"
"ci": {
"$ref": "#/definitions/ci"
}
},
"required": ["project_id", "vendor", "name"],
@ -106,6 +101,23 @@
},
"required": ["name"],
"additionalProperties": false
},
"ci": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^[\\S]+$"
},
"success_pattern": {
"type": "string"
},
"failure_pattern": {
"type": "string"
}
},
"required": ["id"],
"additionalProperties": false
}
}
}

View File

@ -40,7 +40,9 @@
}],
"wiki": "https://wiki.openstack.org/wiki/Arista-neutron-ml2-driver",
"releases": ["Havana", "Icehouse", "Juno"],
"ci_id": "arista-test"
"ci": {
"id": "arista-test"
}
},
{
"project_id": "openstack/neutron",
@ -53,9 +55,11 @@
}],
"wiki": "https://wiki.openstack.org/wiki/Neutron/ML2/MechCiscoNexus",
"releases": ["Havana", "Icehouse"],
"ci_id": "cisco_neutron_ci",
"success_pattern": "neutron_zuul [^:]+: SUCCESS",
"failure_pattern": "neutron_zuul [^:]+: FAILURE"
"ci": {
"id": "cisco_neutron_ci",
"success_pattern": "neutron_zuul \\S+ : SUCCESS",
"failure_pattern": "neutron_zuul \\S+ : FAILURE"
}
},
{
"project_id": "openstack/neutron",
@ -68,7 +72,11 @@
}],
"wiki": "https://wiki.openstack.org/wiki/Cisco-neutron",
"releases": ["Grizzly", "Havana", "Icehouse"],
"ci_id": "cisco_neutron_ci"
"ci": {
"id": "cisco_neutron_ci",
"success_pattern": "neutron_zuul \\S+ : SUCCESS",
"failure_pattern": "neutron_zuul \\S+ : FAILURE"
}
}
]
}
}

View File

@ -38,52 +38,13 @@ class TestMain(testtools.TestCase):
'driver_name': 'Arista Neutron ML2 Driver'
}], ci_map['arista-test'])
def test_process_reviews_ci_vote_no_comment(self):
# check that vote is processed even if there are no comment
review = {
"project": "openstack/neutron",
"branch": "master",
"url": "https://review.openstack.org/92468",
"currentPatchSet": {
"number": "2",
"approvals": [
{
"type": "Verified",
"description": "Verified",
"value": "1",
"grantedOn": 1399478047,
"by": {
"name": "Arista Testing",
"username": "arista-test"
}
}]}
}
ci_ids_map = main.build_ci_map(self.default_data['drivers'])
records = list(main.process_reviews(
[review], ci_ids_map, 'openstack/neutron'))
self.assertEqual(1, len(records), 'One record is expected')
expected_record = {
('openstack/neutron', 'arista', 'arista neutron ml2 driver'): {
'os_versions_map': {
'master': {
'comment': None,
'timestamp': 1399478047,
'review_url': 'https://review.openstack.org/92468',
}
}
}
}
self.assertEqual(expected_record, records[0])
def test_process_reviews_ci_vote_and_comment(self):
# check that vote and matching comment are found
ci_ids_map = main.build_ci_map(self.default_data['drivers'])
records = list(main.process_reviews(
[self.review], ci_ids_map, 'openstack/neutron'))
records = [r for r in records if r.keys()[0][1] == 'arista']
self.assertEqual(1, len(records), 'One record is expected')
@ -101,3 +62,42 @@ class TestMain(testtools.TestCase):
}
}
self.assertEqual(expected_record, records[0])
def test_process_reviews_ci_only_comments(self):
# check that comment is found and parsed correctly
ci_ids_map = main.build_ci_map(self.default_data['drivers'])
records = list(main.process_reviews(
[self.review], ci_ids_map, 'openstack/neutron'))
records = [r for r in records if r.keys()[0][1] == 'cisco']
self.assertEqual(2, len(records), '2 records are expected '
'(since there are 2 cisco entries)')
expected_record = {
(
'openstack/neutron', 'cisco',
'neutron ml2 driver for cisco nexus devices'
): {
'os_versions_map': {
'master': {
'comment': 'Build succeeded.\n\n'
'- neutron_zuul http://128.107.233.28:8080/'
'job/neutron_zuul/263 : SUCCESS in 18m 52s',
'timestamp': 1399481091,
'review_url': 'https://review.openstack.org/92468',
}
}
}
}
self.assertEqual(expected_record, records[0])
def test_tranform_default_data(self):
driver = {
"project_id": "openstack/neutron",
"releases": ["Grizzly", "Havana", "Icehouse"], }
dd = {'drivers': [driver]}
main.transform_default_data(dd)
self.assertTrue('Grizzly' in driver['os_versions_map'],
'Grizzly should be copied from releases into '
'os_version_map')