Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(502)

Side by Side Diff: appengine/swarming/server/bot_groups_config.py

Issue 2689483004: swarming: Add server-side implementation for supplemental bot_config (Closed)
Patch Set: Rebased Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 # Copyright 2016 The LUCI Authors. All rights reserved. 1 # Copyright 2016 The LUCI Authors. All rights reserved.
2 # Use of this source code is governed under the Apache License, Version 2.0 2 # Use of this source code is governed under the Apache License, Version 2.0
3 # that can be found in the LICENSE file. 3 # that can be found in the LICENSE file.
4 4
5 """Functions to fetch and interpret bots.cfg file with list of bot groups.""" 5 """Functions to fetch and interpret bots.cfg file with list of bot groups."""
6 6
7 import collections 7 import collections
8 import hashlib 8 import hashlib
9 import logging 9 import logging
10 import os
10 import re 11 import re
11 12
12 from components import auth 13 from components import auth
13 from components import config 14 from components import config
14 from components import utils 15 from components import utils
15 from components.config import validation 16 from components.config import validation
16 17
17 from proto import bots_pb2 18 from proto import bots_pb2
18 from server import config as local_config 19 from server import config as local_config
19 20
(...skipping 20 matching lines...) Expand all
40 'ip_whitelist', 41 'ip_whitelist',
41 42
42 # Tuple with emails of bot owners. 43 # Tuple with emails of bot owners.
43 'owners', 44 'owners',
44 45
45 # Dict {key => list of values}. Always contains all the keys specified by 46 # Dict {key => list of values}. Always contains all the keys specified by
46 # 'trusted_dimensions' set in BotsCfg. If BotGroup doesn't define some 47 # 'trusted_dimensions' set in BotsCfg. If BotGroup doesn't define some
47 # dimension from that set, the list of value for it will be empty. Key and 48 # dimension from that set, the list of value for it will be empty. Key and
48 # values are unicode strings. 49 # values are unicode strings.
49 'dimensions', 50 'dimensions',
51
52 # Name of the supplemental bot_config.py to inject to the bot during
53 # handshake.
54 'bot_config_script',
55
56 # Content of the supplemental bot_config.py to inject to the bot during
57 # handshake.
58 'bot_config_script_content',
50 ]) 59 ])
51 60
52 61
53 # Post-processed and validated read-only form of bots.cfg config. Its structure 62 # Post-processed and validated read-only form of bots.cfg config. Its structure
54 # is optimized for fast lookup of BotGroupConfig by bot_id. 63 # is optimized for fast lookup of BotGroupConfig by bot_id.
55 _BotGroups = collections.namedtuple('_BotGroups', [ 64 _BotGroups = collections.namedtuple('_BotGroups', [
56 'direct_matches', # dict bot_id => BotGroupConfig 65 'direct_matches', # dict bot_id => BotGroupConfig
57 'prefix_matches', # list of pairs (bot_id_prefix, BotGroupConfig) 66 'prefix_matches', # list of pairs (bot_id_prefix, BotGroupConfig)
58 'machine_types', # dict machine_type.name => BotGroupConfig 67 'machine_types', # dict machine_type.name => BotGroupConfig
59 'default_group', # fallback BotGroupConfig or None if not defined 68 'default_group', # fallback BotGroupConfig or None if not defined
60 ]) 69 ])
61 70
62 71
63 # Default config to use on unconfigured server. 72 # Default config to use on unconfigured server.
64 def _default_bot_groups(): 73 def _default_bot_groups():
65 return _BotGroups( 74 return _BotGroups(
66 direct_matches={}, 75 direct_matches={},
67 prefix_matches=[], 76 prefix_matches=[],
68 machine_types={}, 77 machine_types={},
69 default_group=BotGroupConfig( 78 default_group=BotGroupConfig(
70 version='default', 79 version='default',
71 require_luci_machine_token=False, 80 require_luci_machine_token=False,
72 require_service_account=None, 81 require_service_account=None,
73 ip_whitelist=auth.bots_ip_whitelist(), 82 ip_whitelist=auth.bots_ip_whitelist(),
74 owners=(), 83 owners=(),
75 dimensions={})) 84 dimensions={},
85 bot_config_script='',
86 bot_config_script_content=''))
76 87
77 88
78 def _gen_version(fields): 89 def _gen_version(fields):
79 """Looks at BotGroupConfig fields and derives a digest that summarizes them. 90 """Looks at BotGroupConfig fields and derives a digest that summarizes them.
80 91
81 This digest is going to be sent to the bot in /handshake, and bot would 92 This digest is going to be sent to the bot in /handshake, and bot would
82 include it in its state (and thus send it with each /poll). If server detects 93 include it in its state (and thus send it with each /poll). If server detects
83 that the bot is using older version of the config, it would ask the bot 94 that the bot is using older version of the config, it would ask the bot
84 to restart. 95 to restart.
85 96
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after
134 # In validated config 'dim_kv_pair' is always 'key:value', but be cautious. 145 # In validated config 'dim_kv_pair' is always 'key:value', but be cautious.
135 parts = unicode(dim_kv_pair).split(':', 1) 146 parts = unicode(dim_kv_pair).split(':', 1)
136 if len(parts) != 2: 147 if len(parts) != 2:
137 logging.error('Invalid dimension in bots.cfg - "%s"', dim_kv_pair) 148 logging.error('Invalid dimension in bots.cfg - "%s"', dim_kv_pair)
138 continue 149 continue
139 k, v = parts[0], parts[1] 150 k, v = parts[0], parts[1]
140 dimensions.setdefault(k, set()).add(v) 151 dimensions.setdefault(k, set()).add(v)
141 152
142 auth_cfg = msg.auth or bots_pb2.BotAuth() 153 auth_cfg = msg.auth or bots_pb2.BotAuth()
143 154
155 content = ''
156 if msg.bot_config_script:
157 rev, content = config.get_self_config(
158 'scripts/' + msg.bot_config_script,
159 store_last_good=True)
160 if not rev or not content:
161 # The entry is invalid. It points to a non existing file. It could be
162 # because of a typo in the file name. An empty file is an invalid file,
163 # log an error to alert the admins.
164 logging.error(
165 'Configuration referenced non existing bot_config file %r\n%s',
166 msg.bot_config_script, msg)
144 return _make_bot_group_config( 167 return _make_bot_group_config(
145 require_luci_machine_token=auth_cfg.require_luci_machine_token, 168 require_luci_machine_token=auth_cfg.require_luci_machine_token,
146 require_service_account=auth_cfg.require_service_account, 169 require_service_account=auth_cfg.require_service_account,
147 ip_whitelist=auth_cfg.ip_whitelist, 170 ip_whitelist=auth_cfg.ip_whitelist,
148 owners=tuple(msg.owners), 171 owners=tuple(msg.owners),
149 dimensions={k: sorted(v) for k, v in dimensions.iteritems()}) 172 dimensions={k: sorted(v) for k, v in dimensions.iteritems()},
173 bot_config_script=msg.bot_config_script or '',
174 bot_config_script_content=content or '')
150 175
151 176
152 def _expand_bot_id_expr(expr): 177 def _expand_bot_id_expr(expr):
153 """Expands string with bash-like sets (if they are there). 178 """Expands string with bash-like sets (if they are there).
154 179
155 E.g. takes "vm{1..3}-m1" and yields "vm1-m1", "vm2-m1", "vm3-m1". Also 180 E.g. takes "vm{1..3}-m1" and yields "vm1-m1", "vm2-m1", "vm3-m1". Also
156 supports list syntax ({1,2,3}). Either one should be used, but not both, e.g. 181 supports list syntax ({1,2,3}). Either one should be used, but not both, e.g.
157 following WILL NOT work: {1..3,4,5}. 182 following WILL NOT work: {1..3,4,5}.
158 183
159 Yields original string if it doesn't have '{...}' section. 184 Yields original string if it doesn't have '{...}' section.
(...skipping 226 matching lines...) Expand 10 before | Expand all | Expand 10 after
386 try: 411 try:
387 auth.Identity(auth.IDENTITY_USER, own) 412 auth.Identity(auth.IDENTITY_USER, own)
388 except ValueError: 413 except ValueError:
389 ctx.error('invalid owner email "%s"', own) 414 ctx.error('invalid owner email "%s"', own)
390 415
391 # Validate 'dimensions'. 416 # Validate 'dimensions'.
392 for dim in entry.dimensions: 417 for dim in entry.dimensions:
393 if not local_config.validate_flat_dimension(dim): 418 if not local_config.validate_flat_dimension(dim):
394 ctx.error('bad dimension %r', dim) 419 ctx.error('bad dimension %r', dim)
395 420
421 # Validate 'bot_config_script': the supplemental bot_config.py.
422 if entry.bot_config_script:
423 # Another check in bot_code.py confirms that the script itself is valid
424 # python.
425 if not entry.bot_config_script.endswith('.py'):
426 ctx.error('Invalid bot_config_script name: must end with .py')
427 if os.path.basename(entry.bot_config_script) != entry.bot_config_script:
428 ctx.error(
429 'Invalid bot_config_script name: must not contain path entry')
430 # We can't validate that the file exists here. It'll fail in
431 # _bot_group_proto_to_tuple() which is called by _fetch_bot_groups() and
432 # cached for 60 seconds.
433
396 # Now verify bot_id_prefix is never a prefix of other prefix. It causes 434 # Now verify bot_id_prefix is never a prefix of other prefix. It causes
397 # ambiguities. 435 # ambiguities.
398 for smaller, s_idx in bot_id_prefixes.iteritems(): 436 for smaller, s_idx in bot_id_prefixes.iteritems():
399 for larger, l_idx in bot_id_prefixes.iteritems(): 437 for larger, l_idx in bot_id_prefixes.iteritems():
400 if smaller == larger: 438 if smaller == larger:
401 continue # we've already checked prefixes have no duplicated 439 continue # we've already checked prefixes have no duplicated
402 if larger.startswith(smaller): 440 if larger.startswith(smaller):
403 ctx.error( 441 ctx.error(
404 'bot_id_prefix "%s", defined in group #%d, is subprefix of "%s", ' 442 'bot_id_prefix "%s", defined in group #%d, is subprefix of "%s", '
405 'defined in group #%d; it makes group assigned for bots with ' 443 'defined in group #%d; it makes group assigned for bots with '
406 'prefix "%s" ambigious', smaller, s_idx, larger, l_idx, larger) 444 'prefix "%s" ambigious', smaller, s_idx, larger, l_idx, larger)
OLDNEW
« no previous file with comments | « appengine/swarming/handlers_bot_test.py ('k') | appengine/swarming/server/bot_groups_config_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698