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

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: include fix and test case to test the fix 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 _DEFAULT_BOT_GROUPS = _BotGroups( 73 _DEFAULT_BOT_GROUPS = _BotGroups(
65 direct_matches={}, 74 direct_matches={},
66 prefix_matches=[], 75 prefix_matches=[],
67 machine_types={}, 76 machine_types={},
68 default_group=BotGroupConfig( 77 default_group=BotGroupConfig(
69 version='default', 78 version='default',
70 require_luci_machine_token=False, 79 require_luci_machine_token=False,
71 require_service_account=None, 80 require_service_account=None,
72 ip_whitelist=auth.BOTS_IP_WHITELIST, 81 ip_whitelist=auth.BOTS_IP_WHITELIST,
73 owners=(), 82 owners=(),
74 dimensions={})) 83 dimensions={},
84 bot_config_script='',
85 bot_config_script_content=''))
75 86
76 87
77 def _gen_version(fields): 88 def _gen_version(fields):
78 """Looks at BotGroupConfig fields and derives a digest that summarizes them. 89 """Looks at BotGroupConfig fields and derives a digest that summarizes them.
79 90
80 This digest is going to be sent to the bot in /handshake, and bot would 91 This digest is going to be sent to the bot in /handshake, and bot would
81 include it in its state (and thus send it with each /poll). If server detects 92 include it in its state (and thus send it with each /poll). If server detects
82 that the bot is using older version of the config, it would ask the bot 93 that the bot is using older version of the config, it would ask the bot
83 to restart. 94 to restart.
84 95
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after
133 # In validated config 'dim_kv_pair' is always 'key:value', but be cautious. 144 # In validated config 'dim_kv_pair' is always 'key:value', but be cautious.
134 parts = unicode(dim_kv_pair).split(':', 1) 145 parts = unicode(dim_kv_pair).split(':', 1)
135 if len(parts) != 2: 146 if len(parts) != 2:
136 logging.error('Invalid dimension in bots.cfg - "%s"', dim_kv_pair) 147 logging.error('Invalid dimension in bots.cfg - "%s"', dim_kv_pair)
137 continue 148 continue
138 k, v = parts[0], parts[1] 149 k, v = parts[0], parts[1]
139 dimensions.setdefault(k, set()).add(v) 150 dimensions.setdefault(k, set()).add(v)
140 151
141 auth_cfg = msg.auth or bots_pb2.BotAuth() 152 auth_cfg = msg.auth or bots_pb2.BotAuth()
142 153
154 content = ''
155 if msg.bot_config_script:
156 rev, content = config.get_self_config(
157 'scripts/' + msg.bot_config_script,
158 store_last_good=True)
159 if not rev or not content:
160 # The entry is invalid. It points to a non existing file. It could be
161 # because of a typo in the file name. An empty file is an invalid file,
162 # log an error to alert the admins.
163 logging.error(
164 'Configuration referenced non existing bot_config file %r\n%s',
165 msg.bot_config_script, msg)
143 return _make_bot_group_config( 166 return _make_bot_group_config(
144 require_luci_machine_token=auth_cfg.require_luci_machine_token, 167 require_luci_machine_token=auth_cfg.require_luci_machine_token,
145 require_service_account=auth_cfg.require_service_account, 168 require_service_account=auth_cfg.require_service_account,
146 ip_whitelist=auth_cfg.ip_whitelist, 169 ip_whitelist=auth_cfg.ip_whitelist,
147 owners=tuple(msg.owners), 170 owners=tuple(msg.owners),
148 dimensions={k: sorted(v) for k, v in dimensions.iteritems()}) 171 dimensions={k: sorted(v) for k, v in dimensions.iteritems()},
172 bot_config_script=msg.bot_config_script or '',
173 bot_config_script_content=content or '')
149 174
150 175
151 def _expand_bot_id_expr(expr): 176 def _expand_bot_id_expr(expr):
152 """Expands string with bash-like sets (if they are there). 177 """Expands string with bash-like sets (if they are there).
153 178
154 E.g. takes "vm{1..3}-m1" and yields "vm1-m1", "vm2-m1", "vm3-m1". Also 179 E.g. takes "vm{1..3}-m1" and yields "vm1-m1", "vm2-m1", "vm3-m1". Also
155 supports list syntax ({1,2,3}). Either one should be used, but not both, e.g. 180 supports list syntax ({1,2,3}). Either one should be used, but not both, e.g.
156 following WILL NOT work: {1..3,4,5}. 181 following WILL NOT work: {1..3,4,5}.
157 182
158 Yields original string if it doesn't have '{...}' section. 183 Yields original string if it doesn't have '{...}' section.
(...skipping 226 matching lines...) Expand 10 before | Expand all | Expand 10 after
385 try: 410 try:
386 auth.Identity(auth.IDENTITY_USER, own) 411 auth.Identity(auth.IDENTITY_USER, own)
387 except ValueError: 412 except ValueError:
388 ctx.error('invalid owner email "%s"', own) 413 ctx.error('invalid owner email "%s"', own)
389 414
390 # Validate 'dimensions'. 415 # Validate 'dimensions'.
391 for dim in entry.dimensions: 416 for dim in entry.dimensions:
392 if not local_config.validate_flat_dimension(dim): 417 if not local_config.validate_flat_dimension(dim):
393 ctx.error('bad dimension %r', dim) 418 ctx.error('bad dimension %r', dim)
394 419
420 # Validate 'bot_config_script': the supplemental bot_config.py.
421 if entry.bot_config_script:
422 # Another check in bot_code.py confirms that the script itself is valid
423 # python.
424 if not entry.bot_config_script.endswith('.py'):
425 ctx.error('Invalid bot_config_script name: must end with .py')
426 if os.path.basename(entry.bot_config_script) != entry.bot_config_script:
427 ctx.error(
428 'Invalid bot_config_script name: must not contain path entry')
429 # We can't validate that the file exists here. It'll fail in
430 # _bot_group_proto_to_tuple() which is called by _fetch_bot_groups() and
431 # cached for 60 seconds.
432
395 # Now verify bot_id_prefix is never a prefix of other prefix. It causes 433 # Now verify bot_id_prefix is never a prefix of other prefix. It causes
396 # ambiguities. 434 # ambiguities.
397 for smaller, s_idx in bot_id_prefixes.iteritems(): 435 for smaller, s_idx in bot_id_prefixes.iteritems():
398 for larger, l_idx in bot_id_prefixes.iteritems(): 436 for larger, l_idx in bot_id_prefixes.iteritems():
399 if smaller == larger: 437 if smaller == larger:
400 continue # we've already checked prefixes have no duplicated 438 continue # we've already checked prefixes have no duplicated
401 if larger.startswith(smaller): 439 if larger.startswith(smaller):
402 ctx.error( 440 ctx.error(
403 'bot_id_prefix "%s", defined in group #%d, is subprefix of "%s", ' 441 'bot_id_prefix "%s", defined in group #%d, is subprefix of "%s", '
404 'defined in group #%d; it makes group assigned for bots with ' 442 'defined in group #%d; it makes group assigned for bots with '
405 'prefix "%s" ambigious', smaller, s_idx, larger, l_idx, larger) 443 'prefix "%s" ambigious', smaller, s_idx, larger, l_idx, larger)
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698