Chromium Code Reviews| OLD | NEW |
|---|---|
| 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 """Utilities for reading GCE Backend configuration.""" | 5 """Utilities for reading GCE Backend configuration.""" |
| 6 | 6 |
| 7 import collections | 7 import collections |
| 8 import logging | 8 import logging |
| 9 | 9 |
| 10 from components import utils | 10 from components import utils |
| 11 utils.fix_protobuf_package() | 11 utils.fix_protobuf_package() |
| 12 | 12 |
| 13 from google import protobuf | 13 from google import protobuf |
| 14 from google.appengine.ext import ndb | 14 from google.appengine.ext import ndb |
| 15 | 15 |
| 16 import metrics | 16 import metrics |
| 17 | 17 |
| 18 from components import config | 18 from components import config |
| 19 from components import datastore_utils | 19 from components import datastore_utils |
| 20 from components.config import validation | |
| 20 | 21 |
| 21 from proto import config_pb2 | 22 from proto import config_pb2 |
| 22 | 23 |
| 23 | 24 |
| 25 TEMPLATES_CFG_FILENAME = 'templates.cfg' | |
| 26 MANAGERS_CFG_FILENAME = 'managers.cfg' | |
| 27 | |
| 28 | |
| 24 class Configuration(datastore_utils.config.GlobalConfig): | 29 class Configuration(datastore_utils.config.GlobalConfig): |
| 25 """Configuration for this service.""" | 30 """Configuration for this service.""" |
| 26 # Name of the config set in the config service. | |
| 27 config_set = ndb.StringProperty() | |
| 28 # Text-formatted proto.config_pb2.InstanceTemplateConfig. | 31 # Text-formatted proto.config_pb2.InstanceTemplateConfig. |
| 29 template_config = ndb.TextProperty() | 32 template_config = ndb.TextProperty() |
| 30 # Text-formatted proto.config_pb2.InstanceGroupManagerConfig. | 33 # Text-formatted proto.config_pb2.InstanceGroupManagerConfig. |
| 31 manager_config = ndb.TextProperty() | 34 manager_config = ndb.TextProperty() |
| 32 # Revision of the configs. | 35 # Revision of the configs. |
| 33 revision = ndb.StringProperty() | 36 revision = ndb.StringProperty() |
| 34 | 37 |
| 38 def set_defaults(self): | |
| 39 """Sets defaults for Configuration entity.""" | |
| 40 self.template_config = '' | |
|
M-A Ruel
2016/08/12 18:50:25
why not use default='' on each property?
ryanmartens
2016/08/12 21:02:49
Done.
| |
| 41 self.manager_config = '' | |
| 42 self.revision = '' | |
| 43 | |
| 35 @classmethod | 44 @classmethod |
| 36 def load(cls): | 45 def load(cls): |
| 37 """Loads text-formatted template and manager configs into message.Messages. | 46 """Loads text-formatted template and manager configs into message.Messages. |
| 38 | 47 |
| 39 Returns: | 48 Returns: |
| 40 A 2-tuple of (InstanceTemplateConfig, InstanceGroupManagerConfig). | 49 A 2-tuple of (InstanceTemplateConfig, InstanceGroupManagerConfig). |
| 41 """ | 50 """ |
| 42 configuration = cls.cached() | 51 configuration = cls.cached() |
| 43 template_config = config_pb2.InstanceTemplateConfig() | 52 template_cfg = config_pb2.InstanceTemplateConfig() |
| 44 protobuf.text_format.Merge(configuration.template_config, template_config) | 53 if configuration.template_config: |
| 45 manager_config = config_pb2.InstanceGroupManagerConfig() | 54 protobuf.text_format.Merge(configuration.template_config, template_cfg) |
|
M-A Ruel
2016/08/12 18:50:25
do you want to trap protobuf.text_format.ParseErro
ryanmartens
2016/08/12 21:02:48
Sure. I've now loosely followed auth_service. It'd
| |
| 46 protobuf.text_format.Merge(configuration.manager_config, manager_config) | 55 manager_cfg = config_pb2.InstanceGroupManagerConfig() |
| 47 return template_config, manager_config | 56 if configuration.manager_config: |
| 57 protobuf.text_format.Merge(configuration.manager_config, manager_cfg) | |
| 58 return template_cfg, manager_cfg | |
| 48 | 59 |
| 49 | 60 |
| 50 def update_config(): | 61 def update_template_configs(): |
| 51 """Updates the local configuration from the config service.""" | 62 """Updates the local template configuration from the config service. |
| 52 config_set = Configuration.cached().config_set | |
| 53 revision, template_config = config.get( | |
| 54 config_set, | |
| 55 'templates.cfg', | |
| 56 dest_type=config_pb2.InstanceTemplateConfig, | |
| 57 ) | |
| 58 _, manager_config = config.get( | |
| 59 config_set, | |
| 60 'managers.cfg', | |
| 61 dest_type=config_pb2.InstanceGroupManagerConfig, | |
| 62 revision=revision, | |
| 63 ) | |
| 64 | 63 |
| 65 # Validity of each config. | 64 Ensures that all config files are at the same path revision. |
| 66 configs = { | 65 """ |
| 67 'managers.cfg': True, | 66 template_revision, template_config = config.get_self_config( |
| 68 'templates.cfg': True, | 67 TEMPLATES_CFG_FILENAME, config_pb2.InstanceTemplateConfig, |
|
M-A Ruel
2016/08/12 18:50:25
align arguments at +4
ryanmartens
2016/08/12 21:02:48
Done.
| |
| 69 } | 68 store_last_good=True) |
| 69 manager_revision, manager_config = config.get_self_config( | |
| 70 MANAGERS_CFG_FILENAME, config_pb2.InstanceGroupManagerConfig, | |
| 71 store_last_good=True) | |
| 70 | 72 |
| 71 context = config.validation_context.Context.logging() | 73 context = config.validation_context.Context.logging() |
| 72 validate_template_config(template_config, context) | 74 if template_config: |
| 75 validate_template_config(template_config, context) | |
| 73 if context.result().has_errors: | 76 if context.result().has_errors: |
| 74 logging.error('Not updating configuration due to errors in templates.cfg') | 77 logging.error('Not updating configuration due to errors in templates.cfg') |
| 75 configs['templates.cfg'] = False | 78 return |
| 76 | 79 |
| 77 context = config.validation_context.Context.logging() | 80 context = config.validation_context.Context.logging() |
| 78 validate_manager_config(manager_config, context) | 81 if manager_config: |
| 82 validate_manager_config(manager_config, context) | |
| 79 if context.result().has_errors: | 83 if context.result().has_errors: |
| 80 logging.error('Not updating configuration due to errors in managers.cfg') | 84 logging.error('Not updating configuration due to errors in managers.cfg') |
| 81 configs['managers.cfg'] = False | |
| 82 | |
| 83 for config_name, valid in configs.iteritems(): | |
| 84 metrics.config_valid.set(valid, fields={'config': config_name}) | |
| 85 | |
| 86 if not all(configs.values()): | |
| 87 return | 85 return |
| 88 | 86 |
| 89 stored_config = Configuration.fetch() | 87 if template_revision != manager_revision: |
|
M-A Ruel
2016/08/12 18:50:25
I think this check should be done first (?)
ryanmartens
2016/08/12 21:02:48
Done.
| |
| 90 if stored_config.revision != revision: | 88 logging.error('Not updating configuration due to revision mismatch.') |
|
M-A Ruel
2016/08/12 18:50:25
warning?
ryanmartens
2016/08/12 21:02:48
Done.
| |
| 91 logging.info('Updating configuration to %s', revision) | 89 return |
| 90 | |
| 91 stored_config = Configuration.cached() | |
| 92 if stored_config.revision != template_revision: | |
|
M-A Ruel
2016/08/12 18:50:25
No need to validate at every cron job when the sto
ryanmartens
2016/08/12 21:02:48
Done.
| |
| 93 logging.info('Updating configuration to %s', template_revision) | |
| 92 stored_config.modify( | 94 stored_config.modify( |
| 93 manager_config=protobuf.text_format.MessageToString(manager_config), | 95 manager_config = protobuf.text_format.MessageToString(manager_config), |
|
M-A Ruel
2016/08/12 18:50:25
align at +4 while at it
ryanmartens
2016/08/12 21:02:48
Done.
| |
| 94 revision=revision, | 96 revision = template_revision, |
| 95 template_config=protobuf.text_format.MessageToString(template_config), | 97 template_config = protobuf.text_format.MessageToString(template_config), |
| 96 ) | 98 ) |
| 97 | 99 |
| 98 | 100 |
| 101 @validation.self_rule(TEMPLATES_CFG_FILENAME, config_pb2.InstanceTemplateConfig) | |
| 99 def validate_template_config(config, context): | 102 def validate_template_config(config, context): |
| 100 """Validates an InstanceTemplateConfig instance.""" | 103 """Validates an InstanceTemplateConfig instance.""" |
| 101 # We don't do any GCE-specific validation here. Just require globally | 104 # We don't do any GCE-specific validation here. Just require globally |
| 102 # unique base name because base name is used as the key in the datastore. | 105 # unique base name because base name is used as the key in the datastore. |
| 103 base_names = set() | 106 base_names = set() |
| 107 valid = True | |
| 104 for template in config.templates: | 108 for template in config.templates: |
| 105 if template.base_name in base_names: | 109 if template.base_name in base_names: |
| 106 context.error('base_name %s is not globally unique.', template.base_name) | 110 context.error('base_name %s is not globally unique.', template.base_name) |
| 111 valid = False | |
| 107 else: | 112 else: |
| 108 base_names.add(template.base_name) | 113 base_names.add(template.base_name) |
| 109 | 114 |
| 115 metrics.config_valid.set(valid, fields={'config': TEMPLATES_CFG_FILENAME}) | |
| 110 | 116 |
| 117 | |
| 118 @validation.self_rule(MANAGERS_CFG_FILENAME, | |
|
M-A Ruel
2016/08/12 18:50:25
@validation.self_rule(
MANAGERS_CFG_FILENAME,
ryanmartens
2016/08/12 21:02:49
Done.
| |
| 119 config_pb2.InstanceGroupManagerConfig) | |
| 111 def validate_manager_config(config, context): | 120 def validate_manager_config(config, context): |
| 112 """Validates an InstanceGroupManagerConfig instance.""" | 121 """Validates an InstanceGroupManagerConfig instance.""" |
| 113 # We don't do any GCE-specific validation here. Just require per-template | 122 # We don't do any GCE-specific validation here. Just require per-template |
| 114 # unique zone because template+zone is used as a key in the datastore. | 123 # unique zone because template+zone is used as a key in the datastore. |
| 115 zones = collections.defaultdict(set) | 124 zones = collections.defaultdict(set) |
| 125 valid = True | |
| 116 for manager in config.managers: | 126 for manager in config.managers: |
| 117 if manager.zone in zones[manager.template_base_name]: | 127 if manager.zone in zones[manager.template_base_name]: |
| 118 context.error( | 128 context.error( |
| 119 'zone %s is not unique in template %s.', | 129 'zone %s is not unique in template %s.', |
| 120 manager.zone, | 130 manager.zone, |
| 121 manager.template_base_name, | 131 manager.template_base_name, |
| 122 ) | 132 ) |
| 133 valid = False | |
| 123 else: | 134 else: |
| 124 zones[manager.template_base_name].add(manager.zone) | 135 zones[manager.template_base_name].add(manager.zone) |
| 136 | |
| 137 metrics.config_valid.set(valid, fields={'config': MANAGERS_CFG_FILENAME}) | |
| OLD | NEW |