Refactor provider config to driver module
This change adds a new ProviderConfig driver interface so that driver can load and validate their config. This change also adds a new provider abstract method 'cleanupLeakedResources' that the openstack driver implements to clean floating ip. This removes the need for a shared clean-floating-ip provider config. Change-Id: I20319aa660ebf5fbe8df5d6af1d77028e1b18350
This commit is contained in:
parent
dffe198564
commit
6a716af6a2
|
@ -222,15 +222,37 @@ Example configuration::
|
|||
|
||||
.. _provider:
|
||||
|
||||
provider
|
||||
providers
|
||||
---------
|
||||
|
||||
Lists the OpenStack cloud providers Nodepool should use. Within each
|
||||
provider the available Nodepool image types are defined (see
|
||||
:ref:`provider_diskimages`.
|
||||
Lists the providers Nodepool should use. Each provider is associated to
|
||||
a driver listed below.
|
||||
|
||||
A provider's resources are partitioned into groups called "pools" (see
|
||||
:ref:`pools` for details), and within a pool, the node types which are
|
||||
**required**
|
||||
|
||||
``name``
|
||||
|
||||
|
||||
**optional**
|
||||
|
||||
``driver``
|
||||
Default to *openstack*
|
||||
|
||||
``max-concurrency``
|
||||
Maximum number of node requests that this provider is allowed to handle
|
||||
concurrently. The default, if not specified, is to have no maximum. Since
|
||||
each node request is handled by a separate thread, this can be useful for
|
||||
limiting the number of threads used by the nodepool-launcher daemon.
|
||||
|
||||
|
||||
OpenStack driver
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
Within each OpenStack provider the available Nodepool image types are defined
|
||||
(see :ref:`provider_diskimages`).
|
||||
|
||||
An OpenStack provider's resources are partitioned into groups called "pools"
|
||||
(see :ref:`pools` for details), and within a pool, the node types which are
|
||||
to be made available are listed (see :ref:`pool_labels` for
|
||||
details).
|
||||
|
||||
|
@ -238,6 +260,7 @@ Example::
|
|||
|
||||
providers:
|
||||
- name: provider1
|
||||
driver: openstack
|
||||
cloud: example
|
||||
region-name: 'region1'
|
||||
rate: 1.0
|
||||
|
@ -272,6 +295,8 @@ Example::
|
|||
min-ram: 8192
|
||||
diskimage: devstack-trusty
|
||||
- name: provider2
|
||||
driver: openstack
|
||||
cloud: example2
|
||||
region-name: 'region1'
|
||||
rate: 1.0
|
||||
image-name-format: '{image_name}-{timestamp}'
|
||||
|
@ -297,8 +322,6 @@ Example::
|
|||
|
||||
**required**
|
||||
|
||||
``name``
|
||||
|
||||
``cloud``
|
||||
Name of a cloud configured in ``clouds.yaml``.
|
||||
|
||||
|
@ -356,18 +379,12 @@ Example::
|
|||
OpenStack project and will attempt to clean unattached floating ips that
|
||||
may have leaked around restarts.
|
||||
|
||||
``max-concurrency``
|
||||
Maximum number of node requests that this provider is allowed to handle
|
||||
concurrently. The default, if not specified, is to have no maximum. Since
|
||||
each node request is handled by a separate thread, this can be useful for
|
||||
limiting the number of threads used by the nodepool-launcher daemon.
|
||||
|
||||
.. _pools:
|
||||
|
||||
pools
|
||||
~~~~~
|
||||
|
||||
A pool defines a group of resources from a provider. Each pool has a
|
||||
A pool defines a group of resources from an OpenStack provider. Each pool has a
|
||||
maximum number of nodes which can be launched from it, along with a
|
||||
number of cloud-related attributes used when launching nodes.
|
||||
|
||||
|
|
|
@ -14,6 +14,8 @@ import logging
|
|||
import voluptuous as v
|
||||
import yaml
|
||||
|
||||
from nodepool.config import get_provider_config
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
@ -24,71 +26,10 @@ class ConfigValidator:
|
|||
self.config_file = config_file
|
||||
|
||||
def validate(self):
|
||||
label_min_ram = v.Schema({v.Required('min-ram'): int}, extra=True)
|
||||
|
||||
label_flavor_name = v.Schema({v.Required('flavor-name'): str},
|
||||
extra=True)
|
||||
|
||||
label_diskimage = v.Schema({v.Required('diskimage'): str}, extra=True)
|
||||
|
||||
label_cloud_image = v.Schema({v.Required('cloud-image'): str}, extra=True)
|
||||
|
||||
pool_label_main = {
|
||||
v.Required('name'): str,
|
||||
v.Exclusive('diskimage', 'label-image'): str,
|
||||
v.Exclusive('cloud-image', 'label-image'): str,
|
||||
'min-ram': int,
|
||||
'flavor-name': str,
|
||||
'key-name': str,
|
||||
'console-log': bool,
|
||||
'boot-from-volume': bool,
|
||||
'volume-size': int,
|
||||
}
|
||||
|
||||
pool_label = v.All(pool_label_main,
|
||||
v.Any(label_min_ram, label_flavor_name),
|
||||
v.Any(label_diskimage, label_cloud_image))
|
||||
|
||||
pool = {
|
||||
'name': str,
|
||||
'networks': [str],
|
||||
'auto-floating-ip': bool,
|
||||
'max-servers': int,
|
||||
'labels': [pool_label],
|
||||
'availability-zones': [str],
|
||||
}
|
||||
|
||||
provider_diskimage = {
|
||||
'name': str,
|
||||
'pause': bool,
|
||||
'meta': dict,
|
||||
'config-drive': bool,
|
||||
}
|
||||
|
||||
provider_cloud_images = {
|
||||
'name': str,
|
||||
'config-drive': bool,
|
||||
v.Exclusive('image-id', 'cloud-image-name-or-id'): str,
|
||||
v.Exclusive('image-name', 'cloud-image-name-or-id'): str,
|
||||
}
|
||||
|
||||
provider = {
|
||||
'name': str,
|
||||
'name': v.Required(str),
|
||||
'driver': str,
|
||||
'region-name': str,
|
||||
v.Required('cloud'): str,
|
||||
'max-concurrency': int,
|
||||
'boot-timeout': int,
|
||||
'launch-timeout': int,
|
||||
'launch-retries': int,
|
||||
'nodepool-id': str,
|
||||
'rate': float,
|
||||
'hostname-format': str,
|
||||
'image-name-format': str,
|
||||
'clean-floating-ips': bool,
|
||||
'pools': [pool],
|
||||
'diskimages': [provider_diskimage],
|
||||
'cloud-images': [provider_cloud_images],
|
||||
}
|
||||
|
||||
label = {
|
||||
|
@ -122,7 +63,7 @@ class ConfigValidator:
|
|||
'port': int,
|
||||
'chroot': str,
|
||||
}],
|
||||
'providers': [provider],
|
||||
'providers': list,
|
||||
'labels': [label],
|
||||
'diskimages': [diskimage],
|
||||
}
|
||||
|
@ -133,3 +74,6 @@ class ConfigValidator:
|
|||
# validate the overall schema
|
||||
schema = v.Schema(top_level)
|
||||
schema(config)
|
||||
for provider_dict in config.get('providers', []):
|
||||
provider_schema = get_provider_config(provider_dict).get_schema()
|
||||
provider_schema.extend(provider)(provider_dict)
|
||||
|
|
|
@ -16,113 +16,25 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os_client_config
|
||||
from six.moves import configparser as ConfigParser
|
||||
import time
|
||||
import yaml
|
||||
|
||||
from nodepool import zk
|
||||
|
||||
|
||||
class ConfigValue(object):
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, ConfigValue):
|
||||
if other.__dict__ == self.__dict__:
|
||||
return True
|
||||
return False
|
||||
from nodepool.driver import ConfigValue
|
||||
from nodepool.driver.fake.config import FakeProviderConfig
|
||||
from nodepool.driver.openstack.config import OpenStackProviderConfig
|
||||
|
||||
|
||||
class Config(ConfigValue):
|
||||
pass
|
||||
|
||||
|
||||
class Driver(ConfigValue):
|
||||
pass
|
||||
|
||||
|
||||
class Provider(ConfigValue):
|
||||
def __eq__(self, other):
|
||||
if (other.cloud_config != self.cloud_config or
|
||||
other.pools != self.pools or
|
||||
other.image_type != self.image_type or
|
||||
other.rate != self.rate or
|
||||
other.boot_timeout != self.boot_timeout or
|
||||
other.launch_timeout != self.launch_timeout or
|
||||
other.clean_floating_ips != self.clean_floating_ips or
|
||||
other.max_concurrency != self.max_concurrency or
|
||||
other.diskimages != self.diskimages):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __repr__(self):
|
||||
return "<Provider %s>" % self.name
|
||||
|
||||
|
||||
class ProviderPool(ConfigValue):
|
||||
def __eq__(self, other):
|
||||
if (other.labels != self.labels or
|
||||
other.max_servers != self.max_servers or
|
||||
other.azs != self.azs or
|
||||
other.networks != self.networks):
|
||||
return False
|
||||
return True
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __repr__(self):
|
||||
return "<ProviderPool %s>" % self.name
|
||||
|
||||
|
||||
class ProviderDiskImage(ConfigValue):
|
||||
def __repr__(self):
|
||||
return "<ProviderDiskImage %s>" % self.name
|
||||
|
||||
|
||||
class ProviderCloudImage(ConfigValue):
|
||||
def __repr__(self):
|
||||
return "<ProviderCloudImage %s>" % self.name
|
||||
|
||||
@property
|
||||
def external(self):
|
||||
'''External identifier to pass to the cloud.'''
|
||||
if self.image_id:
|
||||
return dict(id=self.image_id)
|
||||
else:
|
||||
return self.image_name or self.name
|
||||
|
||||
@property
|
||||
def external_name(self):
|
||||
'''Human readable version of external.'''
|
||||
return self.image_id or self.image_name or self.name
|
||||
|
||||
|
||||
class Label(ConfigValue):
|
||||
def __repr__(self):
|
||||
return "<Label %s>" % self.name
|
||||
|
||||
|
||||
class ProviderLabel(ConfigValue):
|
||||
def __eq__(self, other):
|
||||
if (other.diskimage != self.diskimage or
|
||||
other.cloud_image != self.cloud_image or
|
||||
other.min_ram != self.min_ram or
|
||||
other.flavor_name != self.flavor_name or
|
||||
other.key_name != self.key_name):
|
||||
return False
|
||||
return True
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __repr__(self):
|
||||
return "<ProviderLabel %s>" % self.name
|
||||
|
||||
|
||||
class DiskImage(ConfigValue):
|
||||
def __eq__(self, other):
|
||||
if (other.name != self.name or
|
||||
|
@ -143,6 +55,17 @@ class DiskImage(ConfigValue):
|
|||
return "<DiskImage %s>" % self.name
|
||||
|
||||
|
||||
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'
|
||||
if provider['driver'] == 'fake':
|
||||
return FakeProviderConfig(provider)
|
||||
elif provider['driver'] == 'openstack':
|
||||
return OpenStackProviderConfig(provider)
|
||||
|
||||
|
||||
def loadConfig(config_path):
|
||||
retry = 3
|
||||
|
||||
|
@ -163,7 +86,8 @@ def loadConfig(config_path):
|
|||
if retry == 0:
|
||||
raise e
|
||||
|
||||
cloud_config = os_client_config.OpenStackConfig()
|
||||
# Reset the shared os_client_config instance
|
||||
OpenStackProviderConfig.os_client_config = None
|
||||
|
||||
newconfig = Config()
|
||||
newconfig.db = None
|
||||
|
@ -218,113 +142,9 @@ def loadConfig(config_path):
|
|||
l.pools = []
|
||||
|
||||
for provider in config.get('providers', []):
|
||||
provider.setdefault('driver', 'openstack')
|
||||
# Ensure legacy configuration still works when using fake name
|
||||
if provider.get('name', '').startswith('fake'):
|
||||
provider['driver'] = 'fake'
|
||||
p = Provider()
|
||||
p.name = provider['name']
|
||||
p.driver = Driver()
|
||||
p.driver.name = provider['driver']
|
||||
p.driver.manage_images = False
|
||||
p = get_provider_config(provider)
|
||||
p.load(newconfig)
|
||||
newconfig.providers[p.name] = p
|
||||
|
||||
cloud_kwargs = _cloudKwargsFromProvider(provider)
|
||||
p.cloud_config = None
|
||||
p.image_type = None
|
||||
if p.driver.name in ('openstack', 'fake'):
|
||||
p.driver.manage_images = True
|
||||
p.cloud_config = cloud_config.get_one_cloud(**cloud_kwargs)
|
||||
p.image_type = p.cloud_config.config['image_format']
|
||||
p.region_name = provider.get('region-name')
|
||||
p.max_concurrency = provider.get('max-concurrency', -1)
|
||||
p.rate = provider.get('rate', 1.0)
|
||||
p.boot_timeout = provider.get('boot-timeout', 60)
|
||||
p.launch_timeout = provider.get('launch-timeout', 3600)
|
||||
p.launch_retries = provider.get('launch-retries', 3)
|
||||
p.clean_floating_ips = provider.get('clean-floating-ips')
|
||||
p.hostname_format = provider.get(
|
||||
'hostname-format',
|
||||
'{label.name}-{provider.name}-{node.id}'
|
||||
)
|
||||
p.image_name_format = provider.get(
|
||||
'image-name-format',
|
||||
'{image_name}-{timestamp}'
|
||||
)
|
||||
p.diskimages = {}
|
||||
for image in provider.get('diskimages', []):
|
||||
i = ProviderDiskImage()
|
||||
i.name = image['name']
|
||||
p.diskimages[i.name] = i
|
||||
diskimage = newconfig.diskimages[i.name]
|
||||
diskimage.image_types.add(p.image_type)
|
||||
i.pause = bool(image.get('pause', False))
|
||||
i.config_drive = image.get('config-drive', None)
|
||||
|
||||
# This dict is expanded and used as custom properties when
|
||||
# the image is uploaded.
|
||||
i.meta = image.get('meta', {})
|
||||
# 5 elements, and no key or value can be > 255 chars
|
||||
# per Nova API rules
|
||||
if i.meta:
|
||||
if len(i.meta) > 5 or \
|
||||
any([len(k) > 255 or len(v) > 255
|
||||
for k, v in i.meta.items()]):
|
||||
# soft-fail
|
||||
#self.log.error("Invalid metadata for %s; ignored"
|
||||
# % i.name)
|
||||
i.meta = {}
|
||||
p.cloud_images = {}
|
||||
for image in provider.get('cloud-images', []):
|
||||
i = ProviderCloudImage()
|
||||
i.name = image['name']
|
||||
i.config_drive = image.get('config-drive', None)
|
||||
i.image_id = image.get('image-id', None)
|
||||
i.image_name = image.get('image-name', None)
|
||||
p.cloud_images[i.name] = i
|
||||
p.pools = {}
|
||||
for pool in provider.get('pools', []):
|
||||
pp = ProviderPool()
|
||||
pp.name = pool['name']
|
||||
pp.provider = p
|
||||
p.pools[pp.name] = pp
|
||||
pp.max_servers = pool['max-servers']
|
||||
pp.azs = pool.get('availability-zones')
|
||||
pp.networks = pool.get('networks', [])
|
||||
pp.auto_floating_ip = bool(pool.get('auto-floating-ip', True))
|
||||
pp.labels = {}
|
||||
for label in pool.get('labels', []):
|
||||
pl = ProviderLabel()
|
||||
pl.name = label['name']
|
||||
pl.pool = pp
|
||||
pp.labels[pl.name] = pl
|
||||
diskimage = label.get('diskimage', None)
|
||||
if diskimage:
|
||||
pl.diskimage = newconfig.diskimages[diskimage]
|
||||
else:
|
||||
pl.diskimage = None
|
||||
cloud_image_name = label.get('cloud-image', None)
|
||||
if cloud_image_name:
|
||||
cloud_image = p.cloud_images.get(cloud_image_name, None)
|
||||
if not cloud_image:
|
||||
raise ValueError(
|
||||
"cloud-image %s does not exist in provider %s"
|
||||
" but is referenced in label %s" %
|
||||
(cloud_image_name, p.name, pl.name))
|
||||
else:
|
||||
cloud_image = None
|
||||
pl.cloud_image = cloud_image
|
||||
pl.min_ram = label.get('min-ram', 0)
|
||||
pl.flavor_name = label.get('flavor-name', None)
|
||||
pl.key_name = label.get('key-name')
|
||||
pl.console_log = label.get('console-log', False)
|
||||
pl.boot_from_volume = bool(label.get('boot-from-volume',
|
||||
False))
|
||||
pl.volume_size = label.get('volume-size', 50)
|
||||
|
||||
top_label = newconfig.labels[pl.name]
|
||||
top_label.pools.append(pp)
|
||||
|
||||
return newconfig
|
||||
|
||||
|
||||
|
@ -333,13 +153,3 @@ def loadSecureConfig(config, secure_config_path):
|
|||
secure.readfp(open(secure_config_path))
|
||||
|
||||
#config.dburi = secure.get('database', 'dburi')
|
||||
|
||||
|
||||
def _cloudKwargsFromProvider(provider):
|
||||
cloud_kwargs = {}
|
||||
for arg in ['region-name', 'cloud']:
|
||||
if arg in provider:
|
||||
cloud_kwargs[arg] = provider[arg]
|
||||
if provider['driver'] == 'fake':
|
||||
cloud_kwargs['validate'] = False
|
||||
return cloud_kwargs
|
||||
|
|
|
@ -53,6 +53,10 @@ class Provider(object):
|
|||
def waitForNodeCleanup(self, node_id):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def cleanupLeakedResources(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def listNodes(self):
|
||||
pass
|
||||
|
@ -294,3 +298,49 @@ class NodeLaunchManager(object):
|
|||
@abc.abstractmethod
|
||||
def launch(self, node):
|
||||
pass
|
||||
|
||||
|
||||
class ConfigValue(object):
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, ConfigValue):
|
||||
if other.__dict__ == self.__dict__:
|
||||
return True
|
||||
return False
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
|
||||
class Driver(ConfigValue):
|
||||
pass
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class ProviderConfig(ConfigValue):
|
||||
"""The Provider config interface
|
||||
|
||||
The class or instance attribute **name** must be provided as a string.
|
||||
|
||||
"""
|
||||
def __init__(self, provider):
|
||||
self.name = provider['name']
|
||||
self.provider = provider
|
||||
self.driver = Driver()
|
||||
self.driver.name = provider.get('driver', 'openstack')
|
||||
self.max_concurrency = provider.get('max-concurrency', -1)
|
||||
self.driver.manage_images = False
|
||||
|
||||
def __repr__(self):
|
||||
return "<Provider %s>" % self.name
|
||||
|
||||
@abc.abstractmethod
|
||||
def __eq__(self, other):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def load(self, newconfig):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_schema(self):
|
||||
pass
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
# Copyright 2017 Red Hat
|
||||
#
|
||||
# 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.
|
||||
|
||||
from nodepool.driver.openstack.config import OpenStackProviderConfig
|
||||
|
||||
|
||||
class FakeProviderConfig(OpenStackProviderConfig):
|
||||
def _cloudKwargs(self):
|
||||
cloud_kwargs = super(FakeProviderConfig, self)._cloudKwargs()
|
||||
cloud_kwargs['validate'] = False
|
||||
return cloud_kwargs
|
|
@ -0,0 +1,260 @@
|
|||
# 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 os_client_config
|
||||
import voluptuous as v
|
||||
|
||||
from nodepool.driver import ProviderConfig
|
||||
from nodepool.driver import ConfigValue
|
||||
|
||||
|
||||
class ProviderDiskImage(ConfigValue):
|
||||
def __repr__(self):
|
||||
return "<ProviderDiskImage %s>" % self.name
|
||||
|
||||
|
||||
class ProviderCloudImage(ConfigValue):
|
||||
def __repr__(self):
|
||||
return "<ProviderCloudImage %s>" % self.name
|
||||
|
||||
@property
|
||||
def external(self):
|
||||
'''External identifier to pass to the cloud.'''
|
||||
if self.image_id:
|
||||
return dict(id=self.image_id)
|
||||
else:
|
||||
return self.image_name or self.name
|
||||
|
||||
@property
|
||||
def external_name(self):
|
||||
'''Human readable version of external.'''
|
||||
return self.image_id or self.image_name or self.name
|
||||
|
||||
|
||||
class ProviderLabel(ConfigValue):
|
||||
def __eq__(self, other):
|
||||
if (other.diskimage != self.diskimage or
|
||||
other.cloud_image != self.cloud_image or
|
||||
other.min_ram != self.min_ram or
|
||||
other.flavor_name != self.flavor_name or
|
||||
other.key_name != self.key_name):
|
||||
return False
|
||||
return True
|
||||
|
||||
def __repr__(self):
|
||||
return "<ProviderLabel %s>" % self.name
|
||||
|
||||
|
||||
class ProviderPool(ConfigValue):
|
||||
def __eq__(self, other):
|
||||
if (other.labels != self.labels or
|
||||
other.max_servers != self.max_servers or
|
||||
other.azs != self.azs or
|
||||
other.networks != self.networks):
|
||||
return False
|
||||
return True
|
||||
|
||||
def __repr__(self):
|
||||
return "<ProviderPool %s>" % self.name
|
||||
|
||||
|
||||
class OpenStackProviderConfig(ProviderConfig):
|
||||
os_client_config = None
|
||||
|
||||
def __eq__(self, other):
|
||||
if (other.cloud_config != self.cloud_config or
|
||||
other.pools != self.pools or
|
||||
other.image_type != self.image_type or
|
||||
other.rate != self.rate or
|
||||
other.boot_timeout != self.boot_timeout or
|
||||
other.launch_timeout != self.launch_timeout or
|
||||
other.clean_floating_ips != self.clean_floating_ips or
|
||||
other.max_concurrency != self.max_concurrency or
|
||||
other.diskimages != self.diskimages):
|
||||
return False
|
||||
return True
|
||||
|
||||
def _cloudKwargs(self):
|
||||
cloud_kwargs = {}
|
||||
for arg in ['region-name', 'cloud']:
|
||||
if arg in self.provider:
|
||||
cloud_kwargs[arg] = self.provider[arg]
|
||||
return cloud_kwargs
|
||||
|
||||
def load(self, config):
|
||||
if OpenStackProviderConfig.os_client_config is None:
|
||||
OpenStackProviderConfig.os_client_config = \
|
||||
os_client_config.OpenStackConfig()
|
||||
cloud_kwargs = self._cloudKwargs()
|
||||
self.cloud_config = self.os_client_config.get_one_cloud(**cloud_kwargs)
|
||||
|
||||
self.image_type = self.cloud_config.config['image_format']
|
||||
self.driver.manage_images = True
|
||||
self.region_name = self.provider.get('region-name')
|
||||
self.rate = self.provider.get('rate', 1.0)
|
||||
self.boot_timeout = self.provider.get('boot-timeout', 60)
|
||||
self.launch_timeout = self.provider.get('launch-timeout', 3600)
|
||||
self.launch_retries = self.provider.get('launch-retries', 3)
|
||||
self.clean_floating_ips = self.provider.get('clean-floating-ips')
|
||||
self.hostname_format = self.provider.get(
|
||||
'hostname-format',
|
||||
'{label.name}-{provider.name}-{node.id}'
|
||||
)
|
||||
self.image_name_format = self.provider.get(
|
||||
'image-name-format',
|
||||
'{image_name}-{timestamp}'
|
||||
)
|
||||
self.diskimages = {}
|
||||
for image in self.provider.get('diskimages', []):
|
||||
i = ProviderDiskImage()
|
||||
i.name = image['name']
|
||||
self.diskimages[i.name] = i
|
||||
diskimage = config.diskimages[i.name]
|
||||
diskimage.image_types.add(self.image_type)
|
||||
i.pause = bool(image.get('pause', False))
|
||||
i.config_drive = image.get('config-drive', None)
|
||||
|
||||
# This dict is expanded and used as custom properties when
|
||||
# the image is uploaded.
|
||||
i.meta = image.get('meta', {})
|
||||
# 5 elements, and no key or value can be > 255 chars
|
||||
# per Nova API rules
|
||||
if i.meta:
|
||||
if len(i.meta) > 5 or \
|
||||
any([len(k) > 255 or len(v) > 255
|
||||
for k, v in i.meta.items()]):
|
||||
# soft-fail
|
||||
#self.log.error("Invalid metadata for %s; ignored"
|
||||
# % i.name)
|
||||
i.meta = {}
|
||||
|
||||
self.cloud_images = {}
|
||||
for image in self.provider.get('cloud-images', []):
|
||||
i = ProviderCloudImage()
|
||||
i.name = image['name']
|
||||
i.config_drive = image.get('config-drive', None)
|
||||
i.image_id = image.get('image-id', None)
|
||||
i.image_name = image.get('image-name', None)
|
||||
self.cloud_images[i.name] = i
|
||||
|
||||
self.pools = {}
|
||||
for pool in self.provider.get('pools', []):
|
||||
pp = ProviderPool()
|
||||
pp.name = pool['name']
|
||||
pp.provider = self
|
||||
self.pools[pp.name] = pp
|
||||
pp.max_servers = pool['max-servers']
|
||||
pp.azs = pool.get('availability-zones')
|
||||
pp.networks = pool.get('networks', [])
|
||||
pp.auto_floating_ip = bool(pool.get('auto-floating-ip', True))
|
||||
pp.labels = {}
|
||||
for label in pool.get('labels', []):
|
||||
pl = ProviderLabel()
|
||||
pl.name = label['name']
|
||||
pl.pool = pp
|
||||
pp.labels[pl.name] = pl
|
||||
diskimage = label.get('diskimage', None)
|
||||
if diskimage:
|
||||
pl.diskimage = config.diskimages[diskimage]
|
||||
else:
|
||||
pl.diskimage = None
|
||||
cloud_image_name = label.get('cloud-image', None)
|
||||
if cloud_image_name:
|
||||
cloud_image = self.cloud_images.get(cloud_image_name, None)
|
||||
if not cloud_image:
|
||||
raise ValueError(
|
||||
"cloud-image %s does not exist in provider %s"
|
||||
" but is referenced in label %s" %
|
||||
(cloud_image_name, self.name, pl.name))
|
||||
else:
|
||||
cloud_image = None
|
||||
pl.cloud_image = cloud_image
|
||||
pl.min_ram = label.get('min-ram', 0)
|
||||
pl.flavor_name = label.get('flavor-name', None)
|
||||
pl.key_name = label.get('key-name')
|
||||
pl.console_log = label.get('console-log', False)
|
||||
pl.boot_from_volume = bool(label.get('boot-from-volume',
|
||||
False))
|
||||
pl.volume_size = label.get('volume-size', 50)
|
||||
|
||||
top_label = config.labels[pl.name]
|
||||
top_label.pools.append(pp)
|
||||
|
||||
|
||||
def get_schema(self):
|
||||
provider_diskimage = {
|
||||
'name': str,
|
||||
'pause': bool,
|
||||
'meta': dict,
|
||||
'config-drive': bool,
|
||||
}
|
||||
|
||||
provider_cloud_images = {
|
||||
'name': str,
|
||||
'config-drive': bool,
|
||||
v.Exclusive('image-id', 'cloud-image-name-or-id'): str,
|
||||
v.Exclusive('image-name', 'cloud-image-name-or-id'): str,
|
||||
}
|
||||
|
||||
pool_label_main = {
|
||||
v.Required('name'): str,
|
||||
v.Exclusive('diskimage', 'label-image'): str,
|
||||
v.Exclusive('cloud-image', 'label-image'): str,
|
||||
'min-ram': int,
|
||||
'flavor-name': str,
|
||||
'key-name': str,
|
||||
'console-log': bool,
|
||||
'boot-from-volume': bool,
|
||||
'volume-size': int,
|
||||
}
|
||||
|
||||
label_min_ram = v.Schema({v.Required('min-ram'): int}, extra=True)
|
||||
|
||||
label_flavor_name = v.Schema({v.Required('flavor-name'): str},
|
||||
extra=True)
|
||||
|
||||
label_diskimage = v.Schema({v.Required('diskimage'): str}, extra=True)
|
||||
|
||||
label_cloud_image = v.Schema({v.Required('cloud-image'): str}, extra=True)
|
||||
|
||||
pool_label = v.All(pool_label_main,
|
||||
v.Any(label_min_ram, label_flavor_name),
|
||||
v.Any(label_diskimage, label_cloud_image))
|
||||
|
||||
pool = {
|
||||
'name': str,
|
||||
'networks': [str],
|
||||
'auto-floating-ip': bool,
|
||||
'max-servers': int,
|
||||
'labels': [pool_label],
|
||||
'availability-zones': [str],
|
||||
}
|
||||
|
||||
return v.Schema({
|
||||
'region-name': str,
|
||||
v.Required('cloud'): str,
|
||||
'boot-timeout': int,
|
||||
'launch-timeout': int,
|
||||
'launch-retries': int,
|
||||
'nodepool-id': str,
|
||||
'rate': float,
|
||||
'hostname-format': str,
|
||||
'image-name-format': str,
|
||||
'clean-floating-ips': bool,
|
||||
'pools': [pool],
|
||||
'diskimages': [provider_diskimage],
|
||||
'cloud-images': [provider_cloud_images],
|
||||
})
|
|
@ -340,9 +340,10 @@ class OpenStackProvider(Provider):
|
|||
self.log.debug('Deleting server %s' % server_id)
|
||||
self.deleteServer(server_id)
|
||||
|
||||
def cleanupLeakedFloaters(self):
|
||||
with shade_inner_exceptions():
|
||||
self._client.delete_unattached_floating_ips()
|
||||
def cleanupLeakedResources(self):
|
||||
if self.provider.clean_floating_ips:
|
||||
with shade_inner_exceptions():
|
||||
self._client.delete_unattached_floating_ips()
|
||||
|
||||
def getAZs(self):
|
||||
if self.__azs is None:
|
||||
|
|
|
@ -474,8 +474,7 @@ class CleanupWorker(BaseCleanupWorker):
|
|||
node.provider = provider.name
|
||||
self._deleteInstance(node)
|
||||
|
||||
if provider.clean_floating_ips:
|
||||
manager.cleanupLeakedFloaters()
|
||||
manager.cleanupLeakedResources()
|
||||
|
||||
def _cleanupMaxReadyAge(self):
|
||||
'''
|
||||
|
|
Loading…
Reference in New Issue