Files
nodepool/nodepool/config.py
T
Ian Wienand 9367cf8ed8 Add a dib-cmd option for diskimages
This change allows you to specify a dib-cmd parameter for disk images,
which overrides the default call to "disk-image-create".  This allows
you to essentially decide the disk-image-create binary to be called
for each disk image configured.

It is inspired by a couple of things:

The "--fake" argument to nodepool-builder has always been a bit of a
wart; a case of testing-only functionality leaking across into the
production code.  It would be clearer if the tests used exposed
methods to configure themselves to use the fake builder.

Because disk-image-create is called from the $PATH, it makes it more
difficult to use nodepool from a virtualenv.  You can not just run
"nodepool-builder"; you have to ". activate" the virtualenv before
running the daemon so that the path is set to find the virtualenv
disk-image-create.

In addressing activation issues by automatically choosing the
in-virtualenv binary in Ie0e24fa67b948a294aa46f8164b077c8670b4025, it
was pointed out that others are already using wrappers in various ways
where preferring the co-installed virtualenv version would break.

With this, such users can ensure they call the "disk-image-create"
binary they want.  We can then make a change to prefer the
co-installed version without fear of breaking.

In theory, there's no reason why a totally separate
"/custom/venv/bin/disk-image-create" would not be valid if you
required a customised dib for some reason for just one image.  This is
not currently possible, even modulo PATH hacks, etc., all images will
use the same binary to build.  It is for this flexibility I think this
is best at the diskimage level, rather than as, say a global setting
for the whole builder instance.

Thus add a dib-cmd option for diskimages.  In the testing case, this
points to the fake-image-create script, and the --fake command-line
option and related bits are removed.

It should have no backwards compatibility effects; documentation and a
release note is added.

Change-Id: I6677e11823df72f8c69973c83039a987b67eb2af
2019-08-22 10:09:00 +10:00

290 lines
9.7 KiB
Python
Executable File

#!/usr/bin/env python
# Copyright (C) 2011-2013 OpenStack Foundation
#
# 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 math
import time
import yaml
from nodepool import zk
from nodepool.driver import ConfigValue
from nodepool.driver import Drivers
class Config(ConfigValue):
'''
Class representing the nodepool configuration.
This class implements methods to read each of the top-level configuration
items found in the YAML config file, and set attributes accordingly.
'''
def __init__(self):
self.diskimages = {}
self.labels = {}
self.providers = {}
self.provider_managers = {}
self.zookeeper_servers = {}
self.elementsdir = None
self.imagesdir = None
self.build_log_dir = None
self.build_log_retention = None
self.max_hold_age = None
self.webapp = None
def __eq__(self, other):
if isinstance(other, Config):
return (self.diskimages == other.diskimages and
self.labels == other.labels and
self.providers == other.providers and
self.provider_managers == other.provider_managers and
self.zookeeper_servers == other.zookeeper_servers and
self.elementsdir == other.elementsdir and
self.imagesdir == other.imagesdir and
self.build_log_dir == other.build_log_dir and
self.build_log_retention == other.build_log_retention and
self.max_hold_age == other.max_hold_age and
self.webapp == other.webapp)
return False
def setElementsDir(self, value):
self.elementsdir = value
def setImagesDir(self, value):
self.imagesdir = value
def setBuildLog(self, directory, retention):
if retention is None:
retention = 7
self.build_log_dir = directory
self.build_log_retention = retention
def setMaxHoldAge(self, value):
if value is None or value <= 0:
value = math.inf
self.max_hold_age = value
def setWebApp(self, webapp_cfg):
if webapp_cfg is None:
webapp_cfg = {}
self.webapp = {
'port': webapp_cfg.get('port', 8005),
'listen_address': webapp_cfg.get('listen_address', '0.0.0.0')
}
def setZooKeeperServers(self, zk_cfg):
if not zk_cfg:
return
for server in zk_cfg:
z = zk.ZooKeeperConnectionConfig(server['host'],
server.get('port', 2181),
server.get('chroot', None))
name = z.host + '_' + str(z.port)
self.zookeeper_servers[name] = z
def setDiskImages(self, diskimages_cfg):
if not diskimages_cfg:
return
for diskimage in diskimages_cfg:
d = DiskImage()
d.name = diskimage['name']
if 'elements' in diskimage:
d.elements = u' '.join(diskimage['elements'])
else:
d.elements = ''
d.dib_cmd = str(diskimage.get('dib-cmd', 'disk-image-create'))
# must be a string, as it's passed as env-var to
# d-i-b, but might be untyped in the yaml and
# interpreted as a number (e.g. "21" for fedora)
d.release = str(diskimage.get('release', ''))
d.rebuild_age = int(diskimage.get('rebuild-age', 86400))
d.env_vars = diskimage.get('env-vars', {})
if not isinstance(d.env_vars, dict):
d.env_vars = {}
d.image_types = set(diskimage.get('formats', []))
d.pause = bool(diskimage.get('pause', False))
d.username = diskimage.get('username', 'zuul')
d.python_path = diskimage.get('python-path', '/usr/bin/python2')
d.build_timeout = diskimage.get('build-timeout', (8 * 60 * 60))
self.diskimages[d.name] = d
def setSecureDiskimageEnv(self, diskimages, secure_config_path):
for diskimage in diskimages:
if diskimage['name'] not in self.diskimages:
raise Exception('%s: unknown diskimage %s' %
(secure_config_path, diskimage['name']))
self.diskimages[diskimage['name']].env_vars.update(
diskimage['env-vars'])
def setLabels(self, labels_cfg):
if not labels_cfg:
return
for label in labels_cfg:
l = Label()
l.name = label['name']
l.max_ready_age = label.get('max-ready-age', 0)
l.min_ready = label.get('min-ready', 0)
l.pools = []
self.labels[l.name] = l
def setProviders(self, providers_cfg):
if not providers_cfg:
return
for provider in providers_cfg:
p = get_provider_config(provider)
p.load(self)
self.providers[p.name] = p
class Label(ConfigValue):
def __init__(self):
self.name = None
self.max_ready_age = None
self.min_ready = None
self.pools = None
def __eq__(self, other):
if isinstance(other, Label):
return (self.name == other.name and
self.max_ready_age == other.max_ready_age and
self.min_ready == other.min_ready and
self.pools == other.pools)
return False
def __repr__(self):
return "<Label %s>" % self.name
class DiskImage(ConfigValue):
def __init__(self):
self.name = None
self.elements = None
self.dib_path = None
self.release = None
self.rebuild_age = None
self.env_vars = None
self.image_types = None
self.pause = False
self.username = None
self.python_path = None
self.build_timeout = None
def __eq__(self, other):
if isinstance(other, DiskImage):
return (other.name == self.name and
other.elements == self.elements and
other.dib_path == self.dib_path and
other.release == self.release and
other.rebuild_age == self.rebuild_age and
other.env_vars == self.env_vars and
other.image_types == self.image_types and
other.pause == self.pause and
other.username == self.username and
other.python_path == self.python_path and
other.build_timeout == self.build_timeout)
return False
def __repr__(self):
return "<DiskImage %s>" % self.name
def as_list(item):
if not item:
return []
if isinstance(item, list):
return item
return [item]
def get_provider_config(provider):
provider.setdefault('driver', 'openstack')
# Ensure legacy configuration still works when using fake cloud
if provider.get('name', '').startswith('fake'):
provider['driver'] = 'fake'
driver = Drivers.get(provider['driver'])
return driver.getProviderConfig(provider)
def openConfig(path):
retry = 3
# Since some nodepool code attempts to dynamically re-read its config
# file, we need to handle the race that happens if an outside entity
# edits it (causing it to temporarily not exist) at the same time we
# attempt to reload it.
while True:
try:
config = yaml.safe_load(open(path))
break
except IOError as e:
if e.errno == 2:
retry = retry - 1
time.sleep(.5)
else:
raise e
if retry == 0:
raise e
return config
def loadConfig(config_path):
config = openConfig(config_path)
# Call driver config reset now to clean global hooks like openstacksdk
for driver in Drivers.drivers.values():
driver.reset()
newconfig = Config()
newconfig.setElementsDir(config.get('elements-dir'))
newconfig.setImagesDir(config.get('images-dir'))
newconfig.setBuildLog(config.get('build-log-dir'),
config.get('build-log-retention'))
newconfig.setMaxHoldAge(config.get('max-hold-age'))
newconfig.setWebApp(config.get('webapp'))
newconfig.setZooKeeperServers(config.get('zookeeper-servers'))
newconfig.setDiskImages(config.get('diskimages'))
newconfig.setLabels(config.get('labels'))
newconfig.setProviders(config.get('providers'))
# Ensure at least qcow2 is set to be passed to qemu-img.
# Note that this needs to be after setting the providers as they
# add their supported image types to the diskimages.
for diskimage in newconfig.diskimages.values():
if not diskimage.image_types:
diskimage.image_types.add('qcow2')
return newconfig
def loadSecureConfig(config, secure_config_path):
secure = openConfig(secure_config_path)
if not secure: # empty file
return
# Eliminate any servers defined in the normal config
if secure.get('zookeeper-servers', []):
config.zookeeper_servers = {}
# TODO(Shrews): Support ZooKeeper auth
config.setZooKeeperServers(secure.get('zookeeper-servers'))
config.setSecureDiskimageEnv(
secure.get('diskimages', []), secure_config_path)