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

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: rebase 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',
50 ]) 55 ])
51 56
52 57
53 # Post-processed and validated read-only form of bots.cfg config. Its structure 58 # Post-processed and validated read-only form of bots.cfg config. Its structure
54 # is optimized for fast lookup of BotGroupConfig by bot_id. 59 # is optimized for fast lookup of BotGroupConfig by bot_id.
55 _BotGroups = collections.namedtuple('_BotGroups', [ 60 _BotGroups = collections.namedtuple('_BotGroups', [
56 'direct_matches', # dict bot_id => BotGroupConfig 61 'direct_matches', # dict bot_id => BotGroupConfig
57 'prefix_matches', # list of pairs (bot_id_prefix, BotGroupConfig) 62 'prefix_matches', # list of pairs (bot_id_prefix, BotGroupConfig)
58 'machine_types', # dict machine_type.name => BotGroupConfig 63 'machine_types', # dict machine_type.name => BotGroupConfig
59 'default_group', # fallback BotGroupConfig or None if not defined 64 'default_group', # fallback BotGroupConfig or None if not defined
60 ]) 65 ])
61 66
62 67
63 # Default config to use on unconfigured server. 68 # Default config to use on unconfigured server.
64 _DEFAULT_BOT_GROUPS = _BotGroups( 69 _DEFAULT_BOT_GROUPS = _BotGroups(
65 direct_matches={}, 70 direct_matches={},
66 prefix_matches=[], 71 prefix_matches=[],
67 machine_types={}, 72 machine_types={},
68 default_group=BotGroupConfig( 73 default_group=BotGroupConfig(
69 version='default', 74 version='default',
70 require_luci_machine_token=False, 75 require_luci_machine_token=False,
71 require_service_account=None, 76 require_service_account=None,
72 ip_whitelist=auth.BOTS_IP_WHITELIST, 77 ip_whitelist=auth.BOTS_IP_WHITELIST,
73 owners=(), 78 owners=(),
74 dimensions={})) 79 dimensions={},
80 bot_config_script=''))
75 81
76 82
77 def _gen_version(fields): 83 def _gen_version(fields):
78 """Looks at BotGroupConfig fields and derives a digest that summarizes them. 84 """Looks at BotGroupConfig fields and derives a digest that summarizes them.
79 85
80 This digest is going to be sent to the bot in /handshake, and bot would 86 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 87 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 88 that the bot is using older version of the config, it would ask the bot
83 to restart. 89 to restart.
84 90
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after
138 k, v = parts[0], parts[1] 144 k, v = parts[0], parts[1]
139 dimensions.setdefault(k, set()).add(v) 145 dimensions.setdefault(k, set()).add(v)
140 146
141 auth_cfg = msg.auth or bots_pb2.BotAuth() 147 auth_cfg = msg.auth or bots_pb2.BotAuth()
142 148
143 return _make_bot_group_config( 149 return _make_bot_group_config(
144 require_luci_machine_token=auth_cfg.require_luci_machine_token, 150 require_luci_machine_token=auth_cfg.require_luci_machine_token,
145 require_service_account=auth_cfg.require_service_account, 151 require_service_account=auth_cfg.require_service_account,
146 ip_whitelist=auth_cfg.ip_whitelist, 152 ip_whitelist=auth_cfg.ip_whitelist,
147 owners=tuple(msg.owners), 153 owners=tuple(msg.owners),
148 dimensions={k: sorted(v) for k, v in dimensions.iteritems()}) 154 dimensions={k: sorted(v) for k, v in dimensions.iteritems()},
155 bot_config_script=msg.bot_config_script)
149 156
150 157
151 def _expand_bot_id_expr(expr): 158 def _expand_bot_id_expr(expr):
152 """Expands string with bash-like sets (if they are there). 159 """Expands string with bash-like sets (if they are there).
153 160
154 E.g. takes "vm{1..3}-m1" and yields "vm1-m1", "vm2-m1", "vm3-m1". Also 161 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. 162 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}. 163 following WILL NOT work: {1..3,4,5}.
157 164
158 Yields original string if it doesn't have '{...}' section. 165 Yields original string if it doesn't have '{...}' section.
(...skipping 226 matching lines...) Expand 10 before | Expand all | Expand 10 after
385 try: 392 try:
386 auth.Identity(auth.IDENTITY_USER, own) 393 auth.Identity(auth.IDENTITY_USER, own)
387 except ValueError: 394 except ValueError:
388 ctx.error('invalid owner email "%s"', own) 395 ctx.error('invalid owner email "%s"', own)
389 396
390 # Validate 'dimensions'. 397 # Validate 'dimensions'.
391 for dim in entry.dimensions: 398 for dim in entry.dimensions:
392 if not local_config.validate_flat_dimension(dim): 399 if not local_config.validate_flat_dimension(dim):
393 ctx.error('bad dimension %r', dim) 400 ctx.error('bad dimension %r', dim)
394 401
402 # Validate 'bot_config_script': the supplemental bot_config.py.
403 if entry.bot_config_script:
404 # Another check confirms that the script itself is valid python.
405 if not entry.bot_config_script.endswith('.py'):
406 ctx.error('Invalid bot_config_script name')
407 if os.path.basename(entry.bot_config_script) != entry.bot_config_script:
408 ctx.error('Invalid bot_config_script name')
409 # The file must exist and must be valid non empty python script.
410 path = 'scripts/' + entry.bot_config_script
411 if not config.get_self_config(path, store_last_good=True)[1]:
412 # Work around a problem in luci-config, this will require a full RPC
413 # but better be safe than sorry.
414 logging.warning('failed to find cached %s', path)
415 if not config.get_self_config(path)[1]:
Vadim Sh. 2017/02/09 22:22:00 I think this will not generally work. Luci config
M-A Ruel 2017/02/09 23:47:02 Nodir can confirm if what I do is wrong. It's not
M-A Ruel 2017/02/10 18:33:26 I tested and confirmed it doesn't. Just removed th
nodir 2017/02/10 18:41:43 Sorry for being late, Vadim's reasoning is correct
M-A Ruel 2017/02/10 19:53:19 No big deal, the latest patchset is confirmed to w
416 ctx.error(
417 'Referenced bot_config_script %s must exist in scripts/' %
418 entry.bot_config_script)
419
395 # Now verify bot_id_prefix is never a prefix of other prefix. It causes 420 # Now verify bot_id_prefix is never a prefix of other prefix. It causes
396 # ambiguities. 421 # ambiguities.
397 for smaller, s_idx in bot_id_prefixes.iteritems(): 422 for smaller, s_idx in bot_id_prefixes.iteritems():
398 for larger, l_idx in bot_id_prefixes.iteritems(): 423 for larger, l_idx in bot_id_prefixes.iteritems():
399 if smaller == larger: 424 if smaller == larger:
400 continue # we've already checked prefixes have no duplicated 425 continue # we've already checked prefixes have no duplicated
401 if larger.startswith(smaller): 426 if larger.startswith(smaller):
402 ctx.error( 427 ctx.error(
403 'bot_id_prefix "%s", defined in group #%d, is subprefix of "%s", ' 428 'bot_id_prefix "%s", defined in group #%d, is subprefix of "%s", '
404 'defined in group #%d; it makes group assigned for bots with ' 429 'defined in group #%d; it makes group assigned for bots with '
405 'prefix "%s" ambigious', smaller, s_idx, larger, l_idx, larger) 430 'prefix "%s" ambigious', smaller, s_idx, larger, l_idx, larger)
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698