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(default='') |
| 30 # Text-formatted proto.config_pb2.InstanceGroupManagerConfig. | 33 # Text-formatted proto.config_pb2.InstanceGroupManagerConfig. |
| 31 manager_config = ndb.TextProperty() | 34 manager_config = ndb.TextProperty(default='') |
| 32 # Revision of the configs. | 35 # Revision of the configs. |
| 33 revision = ndb.StringProperty() | 36 revision = ndb.StringProperty(default='') |
| 34 | 37 |
| 35 @classmethod | 38 @classmethod |
| 36 def load(cls): | 39 def load(cls): |
| 37 """Loads text-formatted template and manager configs into message.Messages. | 40 """Loads text-formatted template and manager configs into message.Messages. |
| 38 | 41 |
| 39 Returns: | 42 Returns: |
| 40 A 2-tuple of (InstanceTemplateConfig, InstanceGroupManagerConfig). | 43 A 2-tuple of (InstanceTemplateConfig, InstanceGroupManagerConfig). |
| 41 """ | 44 """ |
| 45 def _adds_cfg_to_message(name, text_cfg, proto_cfg): | |
| 46 try: | |
| 47 protobuf.text_format.Merge(text_cfg, proto_cfg) | |
| 48 except protobuf.text_format.ParseError as ex: | |
| 49 logging.error('Invalid %s: %s', name, ex) | |
| 50 raise ValueError(ex) | |
| 51 return proto_cfg | |
| 52 | |
| 42 configuration = cls.cached() | 53 configuration = cls.cached() |
| 43 template_config = config_pb2.InstanceTemplateConfig() | 54 template_cfg = _adds_cfg_to_message( |
| 44 protobuf.text_format.Merge(configuration.template_config, template_config) | 55 'template.cfg', configuration.template_config, |
| 45 manager_config = config_pb2.InstanceGroupManagerConfig() | 56 config_pb2.InstanceTemplateConfig() |
| 46 protobuf.text_format.Merge(configuration.manager_config, manager_config) | 57 ) |
| 47 return template_config, manager_config | 58 manager_cfg = _adds_cfg_to_message( |
| 59 'manager.cfg', configuration.manager_config, | |
| 60 config_pb2.InstanceGroupManagerConfig() | |
| 61 ) | |
| 62 return template_cfg, manager_cfg | |
| 48 | 63 |
| 49 | 64 |
| 50 def update_config(): | 65 def update_template_configs(): |
| 51 """Updates the local configuration from the config service.""" | 66 """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 | 67 |
| 65 # Validity of each config. | 68 Ensures that all config files are at the same path revision. |
| 66 configs = { | 69 """ |
| 67 'managers.cfg': True, | 70 template_revision, template_config = config.get_self_config( |
|
smut
2016/08/12 21:34:56
You can't get_self_config because the GCE Backend'
M-A Ruel
2016/08/12 23:16:02
I'm confused, this kind of difference, where the G
smut
2016/08/12 23:22:44
This has nothing to do with the GCE hosting projec
Vadim Sh.
2016/08/12 23:54:24
I think it's safe to change get_self_config to alw
ryanmartens
2016/08/15 13:57:05
I went with the last solution proposed and trimmed
| |
| 68 'templates.cfg': True, | 71 TEMPLATES_CFG_FILENAME, config_pb2.InstanceTemplateConfig, |
| 69 } | 72 store_last_good=True) |
| 73 manager_revision, manager_config = config.get_self_config( | |
| 74 MANAGERS_CFG_FILENAME, config_pb2.InstanceGroupManagerConfig, | |
| 75 store_last_good=True) | |
| 70 | 76 |
| 71 context = config.validation_context.Context.logging() | 77 if template_revision != manager_revision: |
| 72 validate_template_config(template_config, context) | 78 logging.error('Not updating configuration due to revision mismatch.') |
| 73 if context.result().has_errors: | |
| 74 logging.error('Not updating configuration due to errors in templates.cfg') | |
| 75 configs['templates.cfg'] = False | |
| 76 | |
| 77 context = config.validation_context.Context.logging() | |
| 78 validate_manager_config(manager_config, context) | |
| 79 if context.result().has_errors: | |
| 80 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 | 79 return |
| 88 | 80 |
| 89 stored_config = Configuration.fetch() | 81 stored_config = Configuration.cached() |
| 90 if stored_config.revision != revision: | 82 if stored_config.revision != template_revision: |
| 91 logging.info('Updating configuration to %s', revision) | 83 context = config.validation_context.Context.logging() |
| 84 if template_config: | |
| 85 validate_template_config(template_config, context) | |
| 86 if context.result().has_errors: | |
| 87 logging.warning( | |
| 88 'Not updating configuration due to errors in templates.cfg') | |
| 89 return | |
| 90 | |
| 91 context = config.validation_context.Context.logging() | |
| 92 if manager_config: | |
| 93 validate_manager_config(manager_config, context) | |
| 94 if context.result().has_errors: | |
| 95 logging.error('Not updating configuration due to errors in managers.cfg') | |
| 96 return | |
| 97 | |
| 98 logging.info('Updating configuration to %s', template_revision) | |
| 92 stored_config.modify( | 99 stored_config.modify( |
| 93 manager_config=protobuf.text_format.MessageToString(manager_config), | 100 manager_config = protobuf.text_format.MessageToString(manager_config), |
| 94 revision=revision, | 101 revision = template_revision, |
| 95 template_config=protobuf.text_format.MessageToString(template_config), | 102 template_config = protobuf.text_format.MessageToString(template_config), |
| 96 ) | 103 ) |
| 97 | 104 |
| 98 | 105 |
| 106 @validation.self_rule(TEMPLATES_CFG_FILENAME, config_pb2.InstanceTemplateConfig) | |
| 99 def validate_template_config(config, context): | 107 def validate_template_config(config, context): |
| 100 """Validates an InstanceTemplateConfig instance.""" | 108 """Validates an InstanceTemplateConfig instance.""" |
| 101 # We don't do any GCE-specific validation here. Just require globally | 109 # 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. | 110 # unique base name because base name is used as the key in the datastore. |
| 103 base_names = set() | 111 base_names = set() |
| 112 valid = True | |
| 104 for template in config.templates: | 113 for template in config.templates: |
| 105 if template.base_name in base_names: | 114 if template.base_name in base_names: |
| 106 context.error('base_name %s is not globally unique.', template.base_name) | 115 context.error('base_name %s is not globally unique.', template.base_name) |
| 116 valid = False | |
| 107 else: | 117 else: |
| 108 base_names.add(template.base_name) | 118 base_names.add(template.base_name) |
| 109 | 119 |
| 120 metrics.config_valid.set(valid, fields={'config': TEMPLATES_CFG_FILENAME}) | |
| 110 | 121 |
| 122 | |
| 123 @validation.self_rule( | |
| 124 MANAGERS_CFG_FILENAME, config_pb2.InstanceGroupManagerConfig) | |
| 111 def validate_manager_config(config, context): | 125 def validate_manager_config(config, context): |
| 112 """Validates an InstanceGroupManagerConfig instance.""" | 126 """Validates an InstanceGroupManagerConfig instance.""" |
| 113 # We don't do any GCE-specific validation here. Just require per-template | 127 # 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. | 128 # unique zone because template+zone is used as a key in the datastore. |
| 115 zones = collections.defaultdict(set) | 129 zones = collections.defaultdict(set) |
| 130 valid = True | |
| 116 for manager in config.managers: | 131 for manager in config.managers: |
| 117 if manager.zone in zones[manager.template_base_name]: | 132 if manager.zone in zones[manager.template_base_name]: |
| 118 context.error( | 133 context.error( |
| 119 'zone %s is not unique in template %s.', | 134 'zone %s is not unique in template %s.', |
| 120 manager.zone, | 135 manager.zone, |
| 121 manager.template_base_name, | 136 manager.template_base_name, |
| 122 ) | 137 ) |
| 138 valid = False | |
| 123 else: | 139 else: |
| 124 zones[manager.template_base_name].add(manager.zone) | 140 zones[manager.template_base_name].add(manager.zone) |
| 141 | |
| 142 metrics.config_valid.set(valid, fields={'config': MANAGERS_CFG_FILENAME}) | |
| OLD | NEW |