nodepool/nodepool/cmd/config_validator.py

159 lines
4.9 KiB
Python

# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
import os
import voluptuous as v
import yaml
from nodepool.driver import ProviderConfig
from nodepool.config import get_provider_config, substitute_env_vars
log = logging.getLogger(__name__)
class ConfigValidator:
"""Check the layout and certain configuration options"""
def __init__(self, config_file):
self.config_file = config_file
@staticmethod
def getSchema():
label = {
'name': str,
'min-ready': int,
'max-ready-age': int,
}
diskimage = {
v.Required('name'): str,
'abstract': bool,
'dib-cmd': str,
'pause': bool,
'elements': [str],
'formats': [str],
'parent': str,
'release': v.Any(str, int),
'rebuild-age': int,
'env-vars': {str: str},
'username': str,
'python-path': str,
'build-timeout': int,
}
webapp = {
'port': int,
'listen_address': str,
}
zk_tls = dict(
cert=v.Required(str),
key=v.Required(str),
ca=v.Required(str),
)
tenant_resouce_limit = {
'tenant-name': v.Required(str),
'max-cores': int,
'max-ram': int,
'max-servers': int,
}
top_level = {
'webapp': webapp,
'elements-dir': str,
'images-dir': str,
'build-log-dir': str,
'build-log-retention': int,
'zookeeper-servers': [{
'host': str,
'port': int,
'chroot': str,
}],
'zookeeper-tls': zk_tls,
'zookeeper-timeout': float,
'providers': list,
'labels': [label],
'diskimages': [diskimage],
'max-hold-age': int,
'tenant-resource-limits': [tenant_resouce_limit],
}
return v.Schema(top_level)
def validate(self, env=os.environ):
'''
Validate a configuration file
:return: 0 for success, non-zero for failure
'''
provider = ProviderConfig.getCommonSchemaDict()
log.info("validating %s" % self.config_file)
try:
with open(self.config_file) as f:
config = yaml.safe_load(substitute_env_vars(f.read(), env))
except Exception:
log.exception('YAML parsing failed')
return 1
self.config = config
try:
# validate the overall schema
ConfigValidator.getSchema()(config)
for provider_dict in config.get('providers', []):
provider_schema = \
get_provider_config(provider_dict).getSchema()
provider_schema.extend(provider)(provider_dict)
except Exception:
log.exception('Schema validation failed')
return 1
errors = False
# Ensure in openstack provider sections, diskimages have
# top-level labels
labels = [x['name'] for x in config.get('labels', [])]
for provider in config.get('providers', []):
if provider.get('driver', 'openstack') != 'openstack':
continue
for pool in provider.get('pools', []):
for label in pool.get('labels', []):
if label['name'] not in labels:
errors = True
log.error("diskimage %s in provider %s "
"not in top-level labels" %
(label['name'], provider['name']))
diskimages = {}
for diskimage in config.get('diskimages', []):
name = diskimage['name']
if name in diskimages:
log.error("diskimage %s already defined" % name)
errors = True
diskimages[name] = diskimage
# Now check every parent is defined
for diskimage in config.get('diskimages', []):
if diskimage.get('parent', None):
if diskimage['parent'] not in diskimages:
log.error("diskimage %s has non-existant parent: %s" %
(diskimage['name'], diskimage['parent']))
errors = True
if errors is True:
return 1
else:
return 0