Container Infrastructure Management Service for OpenStack
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

351 lines
12 KiB

# Copyright 2016 Rackspace Inc. All rights reserved.
#
# 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 abc
import ast
from oslo_log import log as logging
import requests
import six
from magnum.common import clients
from magnum.common import exception
import magnum.conf
from requests import exceptions as req_exceptions
LOG = logging.getLogger(__name__)
COMMON_TEMPLATES_PATH = "../../common/templates/"
COMMON_ENV_PATH = COMMON_TEMPLATES_PATH + "environments/"
CONF = magnum.conf.CONF
class ParameterMapping(object):
"""A mapping associating heat param and cluster_template attr.
A ParameterMapping is an association of a Heat parameter name with
an attribute on a Cluster, ClusterTemplate, or both.
In the case of both cluster_template_attr and cluster_attr being set, the
ClusterTemplate will be checked first and then Cluster if the attribute
isn't set on the ClusterTemplate.
Parameters can also be set as 'required'. If a required parameter
isn't set, a RequiredArgumentNotProvided exception will be raised.
"""
def __init__(self, heat_param, cluster_template_attr=None,
cluster_attr=None, required=False,
param_type=lambda x: x):
self.heat_param = heat_param
self.cluster_template_attr = cluster_template_attr
self.cluster_attr = cluster_attr
self.required = required
self.param_type = param_type
def set_param(self, params, cluster_template, cluster):
value = None
if (self.cluster_template_attr and
getattr(cluster_template, self.cluster_template_attr, None)
is not None):
value = getattr(cluster_template, self.cluster_template_attr)
elif (self.cluster_attr and
getattr(cluster, self.cluster_attr, None) is not None):
value = getattr(cluster, self.cluster_attr)
elif self.required:
kwargs = dict(heat_param=self.heat_param)
raise exception.RequiredParameterNotProvided(**kwargs)
if value is not None:
value = self.param_type(value)
params[self.heat_param] = value
class OutputMapping(object):
"""A mapping associating heat outputs and cluster attr.
An OutputMapping is an association of a Heat output with a key
Magnum understands.
"""
def __init__(self, heat_output, cluster_attr=None):
self.cluster_attr = cluster_attr
self.heat_output = heat_output
def set_output(self, stack, cluster_template, cluster):
if self.cluster_attr is None:
return
output_value = self.get_output_value(stack)
if output_value is not None:
setattr(cluster, self.cluster_attr, output_value)
def matched(self, output_key):
return self.heat_output == output_key
def get_output_value(self, stack):
for output in stack.to_dict().get('outputs', []):
if output['output_key'] == self.heat_output:
return output['output_value']
LOG.warning('stack does not have output_key %s' % self.heat_output)
return None
@six.add_metaclass(abc.ABCMeta)
class TemplateDefinition(object):
'''A mapping between Magnum objects and Heat templates.
A TemplateDefinition is essentially a mapping between Magnum objects
and Heat templates. Each TemplateDefinition has a mapping of Heat
parameters.
'''
def __init__(self):
self.param_mappings = list()
self.output_mappings = list()
def add_parameter(self, *args, **kwargs):
param = ParameterMapping(*args, **kwargs)
self.param_mappings.append(param)
def add_output(self, *args, **kwargs):
mapping_type = kwargs.pop('mapping_type', OutputMapping)
output = mapping_type(*args, **kwargs)
self.output_mappings.append(output)
def get_output(self, *args, **kwargs):
for output in self.output_mappings:
if output.matched(*args, **kwargs):
return output
return None
def get_params(self, context, cluster_template, cluster, **kwargs):
"""Pulls template parameters from ClusterTemplate.
:param context: Context to pull template parameters for
:param cluster_template: ClusterTemplate to pull template parameters
from
:param cluster: Cluster to pull template parameters from
:param extra_params: Any extra params to be provided to the template
:return: dict of template parameters
"""
template_params = dict()
for mapping in self.param_mappings:
mapping.set_param(template_params, cluster_template, cluster)
if 'extra_params' in kwargs:
template_params.update(kwargs.get('extra_params'))
return template_params
def get_env_files(self, cluster_template):
"""Gets stack environment files based upon ClusterTemplate attributes.
Base implementation returns no files (empty list). Meant to be
overridden by subclasses.
:param cluster_template: ClusterTemplate to grab environment files for
:return: list of relative paths to environment files
"""
return []
def get_heat_param(self, cluster_attr=None, cluster_template_attr=None):
"""Returns stack param name.
Return stack param name using cluster and cluster_template attributes
:param cluster_attr cluster attribute from which it maps to stack
attribute
:param cluster_template_attr cluster_template attribute from which it
maps to stack attribute
:return stack parameter name or None
"""
for mapping in self.param_mappings:
if (mapping.cluster_attr == cluster_attr and
mapping.cluster_template_attr == cluster_template_attr):
return mapping.heat_param
return None
def update_outputs(self, stack, cluster_template, cluster):
for output in self.output_mappings:
output.set_output(stack, cluster_template, cluster)
@abc.abstractproperty
def driver_module_path(self):
pass
@abc.abstractproperty
def template_path(self):
pass
def extract_definition(self, context, cluster_template, cluster, **kwargs):
return (self.template_path,
self.get_params(context, cluster_template, cluster, **kwargs),
self.get_env_files(cluster_template))
class BaseTemplateDefinition(TemplateDefinition):
def __init__(self):
super(BaseTemplateDefinition, self).__init__()
self._osc = None
self.add_parameter('ssh_key_name',
cluster_attr='keypair',
required=True)
self.add_parameter('server_image',
cluster_template_attr='image_id')
self.add_parameter('dns_nameserver',
cluster_template_attr='dns_nameserver')
self.add_parameter('http_proxy',
cluster_template_attr='http_proxy')
self.add_parameter('https_proxy',
cluster_template_attr='https_proxy')
self.add_parameter('no_proxy',
cluster_template_attr='no_proxy')
self.add_parameter('number_of_masters',
cluster_attr='master_count')
@property
def driver_module_path(self):
pass
@abc.abstractproperty
def template_path(self):
pass
def get_osc(self, context):
if not self._osc:
self._osc = clients.OpenStackClients(context)
return self._osc
def get_params(self, context, cluster_template, cluster, **kwargs):
osc = self.get_osc(context)
extra_params = kwargs.pop('extra_params', {})
extra_params['trustee_domain_id'] = osc.keystone().trustee_domain_id
extra_params['trustee_user_id'] = cluster.trustee_user_id
extra_params['trustee_username'] = cluster.trustee_username
extra_params['trustee_password'] = cluster.trustee_password
# Only pass trust ID into the template if allowed by the config file
if CONF.trust.cluster_user_trust:
extra_params['trust_id'] = cluster.trust_id
else:
extra_params['trust_id'] = ""
extra_params['auth_url'] = osc.url_for(
service_type='identity',
interface=CONF.trust.trustee_keystone_interface,
version=3)
return super(BaseTemplateDefinition,
self).get_params(context, cluster_template, cluster,
extra_params=extra_params,
**kwargs)
def validate_discovery_url(self, discovery_url, expect_size):
url = str(discovery_url)
if url[len(url)-1] == '/':
url += '_config/size'
else:
url += '/_config/size'
try:
result = requests.get(url).text
except req_exceptions.RequestException as err:
LOG.error(six.text_type(err))
raise exception.GetClusterSizeFailed(
discovery_url=discovery_url)
try:
result = ast.literal_eval(result)
except (ValueError, SyntaxError):
raise exception.InvalidClusterDiscoveryURL(
discovery_url=discovery_url)
node_value = result.get('node', None)
if node_value is None:
raise exception.InvalidClusterDiscoveryURL(
discovery_url=discovery_url)
value = node_value.get('value', None)
if value is None:
raise exception.InvalidClusterDiscoveryURL(
discovery_url=discovery_url)
elif int(value) != expect_size:
raise exception.InvalidClusterSize(
expect_size=expect_size,
size=int(value),
discovery_url=discovery_url)
def get_discovery_url(self, cluster):
if hasattr(cluster, 'discovery_url') and cluster.discovery_url:
if getattr(cluster, 'master_count', None) is not None:
self.validate_discovery_url(cluster.discovery_url,
cluster.master_count)
else:
self.validate_discovery_url(cluster.discovery_url, 1)
discovery_url = cluster.discovery_url
else:
discovery_endpoint = (
CONF.cluster.etcd_discovery_service_endpoint_format %
{'size': cluster.master_count})
try:
discovery_url = requests.get(discovery_endpoint).text
except req_exceptions.RequestException as err:
LOG.error(six.text_type(err))
raise exception.GetDiscoveryUrlFailed(
discovery_endpoint=discovery_endpoint)
if not discovery_url:
raise exception.InvalidDiscoveryURL(
discovery_url=discovery_url,
discovery_endpoint=discovery_endpoint)
else:
cluster.discovery_url = discovery_url
return discovery_url
def add_lb_env_file(env_files, cluster_template):
if cluster_template.master_lb_enabled:
env_files.append(COMMON_ENV_PATH + 'with_master_lb.yaml')
else:
env_files.append(COMMON_ENV_PATH + 'no_master_lb.yaml')
def add_volume_env_file(env_files, cluster_template):
if cluster_template.docker_volume_size is None:
env_files.append(COMMON_ENV_PATH + 'no_volume.yaml')
else:
env_files.append(COMMON_ENV_PATH + 'with_volume.yaml')
def add_fip_env_file(env_files, cluster_template):
if cluster_template.floating_ip_enabled:
env_files.append(COMMON_ENV_PATH + 'enable_floating_ip.yaml')
else:
env_files.append(COMMON_ENV_PATH + 'disable_floating_ip.yaml')
def add_priv_net_env_file(env_files, cluster_template):
if cluster_template.fixed_network:
env_files.append(COMMON_ENV_PATH + 'no_private_network.yaml')
else:
env_files.append(COMMON_ENV_PATH + 'with_private_network.yaml')