# 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 voluptuous as v from nodepool.driver import ProviderConfig from nodepool.driver import ConfigValue from nodepool.driver import ConfigPool class ProviderDiskImage(ConfigValue): def __init__(self): self.name = None self.pause = False self.config_drive = None self.connection_type = None self.connection_port = None self.meta = None def __eq__(self, other): if isinstance(other, ProviderDiskImage): return (self.name == other.name and self.pause == other.pause and self.config_drive == other.config_drive and self.connection_type == other.connection_type and self.connection_port == other.connection_port and self.meta == other.meta) return False def __repr__(self): return "" % self.name class ProviderCloudImage(ConfigValue): def __init__(self): self.name = None self.config_drive = None self.image_id = None self.image_name = None self.username = None self.connection_type = None self.connection_port = None def __eq__(self, other): if isinstance(other, ProviderCloudImage): return (self.name == other.name and self.config_drive == other.config_drive and self.image_id == other.image_id and self.image_name == other.image_name and self.username == other.username and self.connection_type == other.connection_type and self.connection_port == other.connection_port) return False def __repr__(self): return "" % 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 __init__(self): self.name = None self.diskimage = None self.cloud_image = None self.min_ram = None self.flavor_name = None self.key_name = None self.console_log = False self.boot_from_volume = False self.volume_size = None self.instance_properties = None self.userdata = None # The ProviderPool object that owns this label. self.pool = None def __eq__(self, other): if isinstance(other, ProviderLabel): # NOTE(Shrews): We intentionally do not compare 'pool' here # since this causes recursive checks with ProviderPool. return (other.diskimage == self.diskimage and other.cloud_image == self.cloud_image and other.min_ram == self.min_ram and other.flavor_name == self.flavor_name and other.key_name == self.key_name and other.name == self.name and other.console_log == self.console_log and other.boot_from_volume == self.boot_from_volume and other.volume_size == self.volume_size and other.instance_properties == self.instance_properties and other.userdata == self.userdata) return False def __repr__(self): return "" % self.name class ProviderPool(ConfigPool): def __init__(self): self.name = None self.max_cores = None self.max_ram = None self.ignore_provider_quota = False self.azs = None self.networks = None self.security_groups = None self.auto_floating_ip = True self.host_key_checking = True self.labels = None # The OpenStackProviderConfig object that owns this pool. self.provider = None # Initialize base class attributes super().__init__() def __eq__(self, other): if isinstance(other, ProviderPool): # NOTE(Shrews): We intentionally do not compare 'provider' here # since this causes recursive checks with OpenStackProviderConfig. return (super().__eq__(other) and other.name == self.name and other.max_cores == self.max_cores and other.max_ram == self.max_ram and other.ignore_provider_quota == ( self.ignore_provider_quota) and other.azs == self.azs and other.networks == self.networks and other.security_groups == self.security_groups and other.auto_floating_ip == self.auto_floating_ip and other.host_key_checking == self.host_key_checking and other.labels == self.labels) return False def __repr__(self): return "" % self.name def load(self, pool_config, full_config, provider): ''' Load pool configuration options. :param dict pool_config: A single pool config section from which we will load the values. :param dict full_config: The full nodepool config. :param OpenStackProviderConfig: The calling provider object. ''' super().load(pool_config) self.provider = provider self.name = pool_config['name'] self.max_cores = pool_config.get('max-cores', math.inf) self.max_ram = pool_config.get('max-ram', math.inf) self.ignore_provider_quota = pool_config.get('ignore-provider-quota', False) self.azs = pool_config.get('availability-zones') self.networks = pool_config.get('networks', []) self.security_groups = pool_config.get('security-groups', []) self.auto_floating_ip = bool(pool_config.get('auto-floating-ip', True)) self.host_key_checking = bool(pool_config.get('host-key-checking', True)) for label in pool_config.get('labels', []): pl = ProviderLabel() pl.name = label['name'] pl.pool = self self.labels[pl.name] = pl diskimage = label.get('diskimage', None) if diskimage: pl.diskimage = full_config.diskimages[diskimage] else: pl.diskimage = None cloud_image_name = label.get('cloud-image', None) if cloud_image_name: cloud_image = provider.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) pl.instance_properties = label.get('instance-properties', None) pl.userdata = label.get('userdata', None) top_label = full_config.labels[pl.name] top_label.pools.append(self) class OpenStackProviderConfig(ProviderConfig): def __init__(self, driver, provider): self.driver_object = driver self.__pools = {} self.cloud_config = None self.image_type = None self.rate = None self.boot_timeout = None self.launch_timeout = None self.clean_floating_ips = None self.diskimages = {} self.cloud_images = {} self.hostname_format = None self.image_name_format = None super().__init__(provider) def __eq__(self, other): if isinstance(other, OpenStackProviderConfig): return (super().__eq__(other) and other.cloud_config == self.cloud_config and other.pools == self.pools and other.image_type == self.image_type and other.rate == self.rate and other.boot_timeout == self.boot_timeout and other.launch_timeout == self.launch_timeout and other.clean_floating_ips == self.clean_floating_ips and other.diskimages == self.diskimages and other.cloud_images == self.cloud_images) return False 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 @property def pools(self): return self.__pools @property def manage_images(self): return True def load(self, config): cloud_kwargs = self._cloudKwargs() openstack_config = self.driver_object.openstack_config self.cloud_config = openstack_config.get_one(**cloud_kwargs) self.image_type = self.cloud_config.config['image_format'] self.region_name = self.provider.get('region-name') self.rate = float(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}' ) default_port_mapping = { 'ssh': 22, 'winrm': 5986, } 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) i.connection_type = image.get('connection-type', 'ssh') i.connection_port = image.get( 'connection-port', default_port_mapping.get(i.connection_type, 22)) # 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 = {} 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) i.username = image.get('username', None) i.connection_type = image.get('connection-type', 'ssh') i.connection_port = image.get( 'connection-port', default_port_mapping.get(i.connection_type, 22)) self.cloud_images[i.name] = i for pool in self.provider.get('pools', []): pp = ProviderPool() pp.load(pool, config, self) self.pools[pp.name] = pp def getSchema(self): provider_diskimage = { 'name': str, 'pause': bool, 'meta': dict, 'config-drive': bool, 'connection-type': str, 'connection-port': int, } provider_cloud_images = { 'name': str, 'config-drive': bool, 'connection-type': str, 'connection-port': int, v.Exclusive('image-id', 'cloud-image-name-or-id'): str, v.Exclusive('image-name', 'cloud-image-name-or-id'): str, 'username': 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, 'instance-properties': dict, 'userdata': str, } 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 = ConfigPool.getCommonSchemaDict() pool.update({ 'name': str, 'networks': [str], 'auto-floating-ip': bool, 'host-key-checking': bool, 'ignore-provider-quota': bool, 'max-cores': int, 'max-ram': int, 'labels': [pool_label], 'availability-zones': [str], 'security-groups': [str] }) schema = ProviderConfig.getCommonSchemaDict() schema.update({ 'region-name': str, v.Required('cloud'): str, 'boot-timeout': int, 'launch-timeout': int, 'launch-retries': int, 'nodepool-id': str, 'rate': v.Coerce(float), 'hostname-format': str, 'image-name-format': str, 'clean-floating-ips': bool, 'pools': [pool], 'diskimages': [provider_diskimage], 'cloud-images': [provider_cloud_images], }) return v.Schema(schema) def getSupportedLabels(self, pool_name=None): labels = set() for pool in self.pools.values(): if not pool_name or (pool.name == pool_name): labels.update(pool.labels.keys()) return labels