blob: 985e53c6da01f55874b870e9c797d19fcc42b65c [file] [log] [blame]
# Copyright 2017 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Helpers for interacting with LUCI Milo service."""
from __future__ import print_function
import base64
import collections
import json
from chromite.lib import prpc
from chromite.cbuildbot import topology
class MiloClient(prpc.PRPCClient):
"""Milo client to interact with the LUCI Milo service."""
def _GetHost(self):
"""Get LUCI Milo Server host from topology."""
return topology.topology.get(topology.LUCI_MILO_HOST_KEY)
def GetBuildbotBuildJSON(self, master, builder, build_num, dryrun=False):
"""Get Buildbot build summary JSON file.
Args:
master: waterfall master to query.
builder: builder to query.
build_num: build number to query.
dryrun: Whether a dryrun.
Returns:
Dictionary corresponding to parsed JSON file.
"""
body = json.dumps({
'master': master,
'builder': builder,
'build_num': int(build_num),
})
resp = self.SendRequest('prpc/milo.Buildbot', 'GetBuildbotBuildJSON',
body, dryrun=dryrun)
data = base64.b64decode(resp['data'])
if not data:
return None
result = json.loads(data)
properties = {p[0] : p[1] for p in result['properties']}
result['properties'] = properties
steps = {step['name'] : step for step in result['steps']}
result['steps'] = steps
result['masterName'] = master
return result
def BuildInfoGetBuildbot(self, master, builder, build_num,
project_hint=None, dryrun=False):
"""Get BuildInfo corresponding to Buildbot build.
Args:
master: waterfall master to query.
builder: builder to query.
build_num: build number to query.
project_hint: Logdog project hint.
dryrun: Whether a dryrun.
Returns:
Dictionary of response protobuf including extra key 'steps'.
'steps' value is a collections.OrderedDict() keyed by tuple of
step name path (and a scalar of level 1 steps) to the steps.
The protobuf allows duplicates but the steps dictionary only
includes the most recent occurrence.
"""
request = {
'buildbot': {
'masterName': master,
'builderName': builder,
'buildNumber': int(build_num),
},
}
if project_hint is not None:
request['projectHint'] = project_hint
result = self.SendRequest('prpc/milo.BuildInfo', 'Get',
json.dumps(request), dryrun=dryrun)
def AddSteps(steps, step, root):
"""Recursive helper function to build mapping of step names to steps."""
if step is not None:
# Build a tuple of the path to the step. Root is always unnamed
# step so start keys at the level 1 substeps.
# Level 0: root = (), name = (None,), key = (None,)
# Level 1: root = (None,), name = (xyz,), key = (xyz,)
# Level 2: root = (xyz,), name = (abc,), key = (xyz,abc)
name = (step.get('name'),)
key = (root + name) if root != (None,) else name
# Add both the full path tuple, and the scalar value for level 1 steps.
# This allows handling most lookups as buildinfo['steps']['cidb name'].
steps[key] = step
if len(key) == 1:
steps[key[0]] = step
# Recurse.
for substep in step.get('substep', []):
AddSteps(steps, substep.get('step', None), key)
steps = collections.OrderedDict()
AddSteps(steps, result.get('step'), ())
result['steps'] = steps
return result