borenet | b76c1f7 | 2015-07-29 07:38:49 -0700 | [diff] [blame] | 1 | # Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 | # Use of this source code is governed by a BSD-style license that can be |
| 3 | # found in the LICENSE file. |
| 4 | |
| 5 | |
| 6 | """ Utilities for dealing with builder names. This module obtains its attributes |
| 7 | dynamically from builder_name_schema.json. """ |
| 8 | |
| 9 | |
| 10 | import json |
| 11 | import os |
| 12 | |
| 13 | |
| 14 | # All of these global variables are filled in by _LoadSchema(). |
| 15 | |
| 16 | # The full schema. |
| 17 | BUILDER_NAME_SCHEMA = None |
| 18 | |
| 19 | # Character which separates parts of a builder name. |
| 20 | BUILDER_NAME_SEP = None |
| 21 | |
| 22 | # Builder roles. |
| 23 | BUILDER_ROLE_CANARY = 'Canary' |
| 24 | BUILDER_ROLE_BUILD = 'Build' |
| 25 | BUILDER_ROLE_HOUSEKEEPER = 'Housekeeper' |
| 26 | BUILDER_ROLE_PERF = 'Perf' |
| 27 | BUILDER_ROLE_TEST = 'Test' |
| 28 | BUILDER_ROLES = (BUILDER_ROLE_CANARY, |
| 29 | BUILDER_ROLE_BUILD, |
| 30 | BUILDER_ROLE_HOUSEKEEPER, |
| 31 | BUILDER_ROLE_PERF, |
| 32 | BUILDER_ROLE_TEST) |
| 33 | |
| 34 | # Suffix which distinguishes trybots from normal bots. |
| 35 | TRYBOT_NAME_SUFFIX = None |
| 36 | |
| 37 | |
| 38 | def _LoadSchema(): |
| 39 | """ Load the builder naming schema from the JSON file. """ |
| 40 | |
| 41 | def _UnicodeToStr(obj): |
| 42 | """ Convert all unicode strings in obj to Python strings. """ |
| 43 | if isinstance(obj, unicode): |
| 44 | return str(obj) |
| 45 | elif isinstance(obj, dict): |
| 46 | return dict(map(_UnicodeToStr, obj.iteritems())) |
| 47 | elif isinstance(obj, list): |
| 48 | return list(map(_UnicodeToStr, obj)) |
| 49 | elif isinstance(obj, tuple): |
| 50 | return tuple(map(_UnicodeToStr, obj)) |
| 51 | else: |
| 52 | return obj |
| 53 | |
| 54 | builder_name_json_filename = os.path.join( |
| 55 | os.path.dirname(__file__), 'builder_name_schema.json') |
| 56 | builder_name_schema_json = json.load(open(builder_name_json_filename)) |
| 57 | |
| 58 | global BUILDER_NAME_SCHEMA |
| 59 | BUILDER_NAME_SCHEMA = _UnicodeToStr( |
| 60 | builder_name_schema_json['builder_name_schema']) |
| 61 | |
| 62 | global BUILDER_NAME_SEP |
| 63 | BUILDER_NAME_SEP = _UnicodeToStr( |
| 64 | builder_name_schema_json['builder_name_sep']) |
| 65 | |
| 66 | global TRYBOT_NAME_SUFFIX |
| 67 | TRYBOT_NAME_SUFFIX = _UnicodeToStr( |
| 68 | builder_name_schema_json['trybot_name_suffix']) |
| 69 | |
| 70 | # Since the builder roles are dictionary keys, just assert that the global |
| 71 | # variables above account for all of them. |
| 72 | assert len(BUILDER_ROLES) == len(BUILDER_NAME_SCHEMA) |
| 73 | for role in BUILDER_ROLES: |
| 74 | assert role in BUILDER_NAME_SCHEMA |
| 75 | |
| 76 | |
| 77 | _LoadSchema() |
| 78 | |
| 79 | |
| 80 | def MakeBuilderName(role, extra_config=None, is_trybot=False, **kwargs): |
| 81 | schema = BUILDER_NAME_SCHEMA.get(role) |
| 82 | if not schema: |
| 83 | raise ValueError('%s is not a recognized role.' % role) |
| 84 | for k, v in kwargs.iteritems(): |
| 85 | if BUILDER_NAME_SEP in v: |
| 86 | raise ValueError('%s not allowed in %s.' % (BUILDER_NAME_SEP, v)) |
| 87 | if not k in schema: |
| 88 | raise ValueError('Schema does not contain "%s": %s' %(k, schema)) |
| 89 | if extra_config and BUILDER_NAME_SEP in extra_config: |
| 90 | raise ValueError('%s not allowed in %s.' % (BUILDER_NAME_SEP, |
| 91 | extra_config)) |
| 92 | name_parts = [role] |
| 93 | name_parts.extend([kwargs[attribute] for attribute in schema]) |
| 94 | if extra_config: |
| 95 | name_parts.append(extra_config) |
| 96 | if is_trybot: |
| 97 | name_parts.append(TRYBOT_NAME_SUFFIX) |
| 98 | return BUILDER_NAME_SEP.join(name_parts) |
| 99 | |
| 100 | |
| 101 | def BuilderNameFromObject(obj, is_trybot=False): |
| 102 | """Create a builder name based on properties of the given object. |
| 103 | |
| 104 | Args: |
| 105 | obj: the object from which to create the builder name. The object must |
| 106 | have as properties: |
| 107 | - A valid builder role, as defined in the JSON file |
| 108 | - All properties listed in the JSON file for that role |
| 109 | - Optionally, an extra_config property |
| 110 | is_trybot: bool; whether or not the builder is a trybot. |
| 111 | Returns: |
| 112 | string which combines the properties of the given object into a valid |
| 113 | builder name. |
| 114 | """ |
| 115 | schema = BUILDER_NAME_SCHEMA.get(obj.role) |
| 116 | if not schema: |
| 117 | raise ValueError('%s is not a recognized role.' % obj.role) |
| 118 | name_parts = [obj.role] |
| 119 | for attr_name in schema: |
| 120 | attr_val = getattr(obj, attr_name) |
| 121 | name_parts.append(attr_val) |
| 122 | extra_config = getattr(obj, 'extra_config', None) |
| 123 | if extra_config: |
| 124 | name_parts.append(extra_config) |
| 125 | if is_trybot: |
| 126 | name_parts.append(TRYBOT_NAME_SUFFIX) |
| 127 | return BUILDER_NAME_SEP.join(name_parts) |
| 128 | |
| 129 | |
| 130 | def IsTrybot(builder_name): |
| 131 | """ Returns true if builder_name refers to a trybot (as opposed to a |
| 132 | waterfall bot). """ |
| 133 | return builder_name.endswith(TRYBOT_NAME_SUFFIX) |
| 134 | |
| 135 | |
| 136 | def GetWaterfallBot(builder_name): |
| 137 | """Returns the name of the waterfall bot for this builder. If it is not a |
| 138 | trybot, builder_name is returned unchanged. If it is a trybot the name is |
| 139 | returned without the trybot suffix.""" |
| 140 | if not IsTrybot(builder_name): |
| 141 | return builder_name |
| 142 | return _WithoutSuffix(builder_name, BUILDER_NAME_SEP + TRYBOT_NAME_SUFFIX) |
| 143 | |
| 144 | |
| 145 | def TrybotName(builder_name): |
| 146 | """Returns the name of the trybot clone of this builder. |
| 147 | |
| 148 | If the given builder is a trybot, the name is returned unchanged. If not, the |
| 149 | TRYBOT_NAME_SUFFIX is appended. |
| 150 | """ |
| 151 | if builder_name.endswith(TRYBOT_NAME_SUFFIX): |
| 152 | return builder_name |
| 153 | return builder_name + BUILDER_NAME_SEP + TRYBOT_NAME_SUFFIX |
| 154 | |
| 155 | |
| 156 | def _WithoutSuffix(string, suffix): |
| 157 | """ Returns a copy of string 'string', but with suffix 'suffix' removed. |
| 158 | Raises ValueError if string does not end with suffix. """ |
| 159 | if not string.endswith(suffix): |
| 160 | raise ValueError('_WithoutSuffix: string %s does not end with suffix %s' % ( |
| 161 | string, suffix)) |
| 162 | return string[:-len(suffix)] |
| 163 | |
| 164 | |
| 165 | def DictForBuilderName(builder_name): |
| 166 | """Makes a dictionary containing details about the builder from its name.""" |
| 167 | split_name = builder_name.split(BUILDER_NAME_SEP) |
| 168 | |
| 169 | def pop_front(): |
| 170 | try: |
| 171 | return split_name.pop(0) |
| 172 | except: |
| 173 | raise ValueError('Invalid builder name: %s' % builder_name) |
| 174 | |
| 175 | result = {'is_trybot': False} |
| 176 | |
| 177 | if split_name[-1] == TRYBOT_NAME_SUFFIX: |
| 178 | result['is_trybot'] = True |
| 179 | split_name.pop() |
| 180 | |
| 181 | if split_name[0] in BUILDER_NAME_SCHEMA.keys(): |
| 182 | key_list = BUILDER_NAME_SCHEMA[split_name[0]] |
| 183 | result['role'] = pop_front() |
| 184 | for key in key_list: |
| 185 | result[key] = pop_front() |
| 186 | if split_name: |
| 187 | result['extra_config'] = pop_front() |
| 188 | if split_name: |
| 189 | raise ValueError('Invalid builder name: %s' % builder_name) |
| 190 | else: |
| 191 | raise ValueError('Invalid builder name: %s' % builder_name) |
| 192 | return result |
| 193 | |
| 194 | |