diff options
Diffstat (limited to 'tuskar_ui/api/tuskar.py')
-rw-r--r-- | tuskar_ui/api/tuskar.py | 558 |
1 files changed, 0 insertions, 558 deletions
diff --git a/tuskar_ui/api/tuskar.py b/tuskar_ui/api/tuskar.py deleted file mode 100644 index 89244591..00000000 --- a/tuskar_ui/api/tuskar.py +++ /dev/null @@ -1,558 +0,0 @@ -# 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 random -import string - -from django.conf import settings -from django.utils.translation import ugettext_lazy as _ -from glanceclient import exc as glance_exceptions -from horizon.utils import memoized -from openstack_dashboard.api import base -from openstack_dashboard.api import glance -from openstack_dashboard.api import neutron -from os_cloud_config import keystone_pki -from tuskarclient import client as tuskar_client - -from tuskar_ui.api import flavor -from tuskar_ui.cached_property import cached_property # noqa -from tuskar_ui.handle_errors import handle_errors # noqa - -LOG = logging.getLogger(__name__) -MASTER_TEMPLATE_NAME = 'plan.yaml' -ENVIRONMENT_NAME = 'environment.yaml' -TUSKAR_SERVICE = 'management' - -SSL_HIDDEN_PARAMS = ('SSLCertificate', 'SSLKey') -KEYSTONE_CERTIFICATE_PARAMS = ( - 'KeystoneSigningCertificate', 'KeystoneCACertificate', - 'KeystoneSigningKey') - - -@memoized.memoized -def tuskarclient(request, password=None): - api_version = "2" - insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False) - ca_file = getattr(settings, 'OPENSTACK_SSL_CACERT', None) - endpoint = base.url_for(request, TUSKAR_SERVICE) - - LOG.debug('tuskarclient connection created using token "%s" and url "%s"' % - (request.user.token.id, endpoint)) - - client = tuskar_client.get_client(api_version, - tuskar_url=endpoint, - insecure=insecure, - ca_file=ca_file, - username=request.user.username, - password=password, - os_auth_token=request.user.token.id) - return client - - -def password_generator(size=40, chars=(string.ascii_uppercase + - string.ascii_lowercase + - string.digits)): - return ''.join(random.choice(chars) for _ in range(size)) - - -def strip_prefix(parameter_name): - return parameter_name.split('::', 1)[-1] - - -def _is_blank(parameter): - return not parameter['value'] or parameter['value'] == 'unset' - - -def _should_generate_password(parameter): - # TODO(lsmola) Filter out SSL params for now. Once it will be generated - # in TripleO add it here too. Note: this will also affect how endpoints are - # created - key = parameter['name'] - return all([ - parameter['hidden'], - _is_blank(parameter), - strip_prefix(key) not in SSL_HIDDEN_PARAMS, - strip_prefix(key) not in KEYSTONE_CERTIFICATE_PARAMS, - key != 'SnmpdReadonlyUserPassword', - ]) - - -def _should_generate_keystone_cert(parameter): - return all([ - strip_prefix(parameter['name']) in KEYSTONE_CERTIFICATE_PARAMS, - _is_blank(parameter), - ]) - - -def _should_generate_neutron_control_plane(parameter): - return all([ - strip_prefix(parameter['name']) == 'NeutronControlPlaneID', - _is_blank(parameter), - ]) - - -class Plan(base.APIResourceWrapper): - _attrs = ('uuid', 'name', 'description', 'created_at', 'modified_at', - 'roles', 'parameters') - - def __init__(self, apiresource, request=None): - super(Plan, self).__init__(apiresource) - self._request = request - - @classmethod - def create(cls, request, name, description): - """Create a Plan in Tuskar - - :param request: request object - :type request: django.http.HttpRequest - - :param name: plan name - :type name: string - - :param description: plan description - :type description: string - - :return: the created Plan object - :rtype: tuskar_ui.api.tuskar.Plan - """ - plan = tuskarclient(request).plans.create(name=name, - description=description) - return cls(plan, request=request) - - @classmethod - def patch(cls, request, plan_id, parameters): - """Update a Plan in Tuskar - - :param request: request object - :type request: django.http.HttpRequest - - :param plan_id: id of the plan we want to update - :type plan_id: string - - :param parameters: new values for the plan's parameters - :type parameters: dict - - :return: the updated Plan object - :rtype: tuskar_ui.api.tuskar.Plan - """ - parameter_list = [{ - 'name': unicode(name), - 'value': unicode(value), - } for (name, value) in parameters.items()] - plan = tuskarclient(request).plans.patch(plan_id, parameter_list) - return cls(plan, request=request) - - @classmethod - @memoized.memoized - def list(cls, request): - """Return a list of Plans in Tuskar - - :param request: request object - :type request: django.http.HttpRequest - - :return: list of Plans, or an empty list if there are none - :rtype: list of tuskar_ui.api.tuskar.Plan - """ - plans = tuskarclient(request).plans.list() - return [cls(plan, request=request) for plan in plans] - - @classmethod - @handle_errors(_("Unable to retrieve plan")) - def get(cls, request, plan_id): - """Return the Plan that matches the ID - - :param request: request object - :type request: django.http.HttpRequest - - :param plan_id: id of Plan to be retrieved - :type plan_id: int - - :return: matching Plan, or None if no Plan matches - the ID - :rtype: tuskar_ui.api.tuskar.Plan - """ - plan = tuskarclient(request).plans.get(plan_uuid=plan_id) - return cls(plan, request=request) - - # TODO(lsmola) before will will support multiple overclouds, we - # can work only with overcloud that is named overcloud. Delete - # this once we have more overclouds. Till then, this is the overcloud - # that rules them all. - # This is how API supports it now, so we have to have it this way. - # Also till Overcloud workflow is done properly, we have to work - # with situations that overcloud is deleted, but stack is still - # there. So overcloud will pretend to exist when stack exist. - @classmethod - def get_the_plan(cls, request): - plan_list = cls.list(request) - for plan in plan_list: - return plan - # if plan doesn't exist, create it - plan = cls.create(request, 'overcloud', 'overcloud') - return plan - - @classmethod - def delete(cls, request, plan_id): - """Delete a Plan - - :param request: request object - :type request: django.http.HttpRequest - - :param plan_id: plan id - :type plan_id: int - """ - tuskarclient(request).plans.delete(plan_uuid=plan_id) - - @cached_property - def role_list(self): - return [Role.get(self._request, role.uuid) - for role in self.roles] - - @cached_property - def _roles_by_name(self): - return dict((role.name, role) for role in self.role_list) - - def get_role_by_name(self, role_name): - """Get the role with the given name.""" - return self._roles_by_name[role_name] - - def get_role_node_count(self, role): - """Get the node count for the given role.""" - return int(self.parameter_value(role.node_count_parameter_name, - 0) or 0) - - @cached_property - def templates(self): - return tuskarclient(self._request).plans.templates(self.uuid) - - @cached_property - def master_template(self): - return self.templates.get(MASTER_TEMPLATE_NAME, '') - - @cached_property - def environment(self): - return self.templates.get(ENVIRONMENT_NAME, '') - - @cached_property - def provider_resource_templates(self): - template_dict = dict(self.templates) - del template_dict[MASTER_TEMPLATE_NAME] - del template_dict[ENVIRONMENT_NAME] - return template_dict - - def parameter_list(self, include_key_parameters=True): - params = self.parameters - if not include_key_parameters: - key_params = [] - for role in self.role_list: - key_params.extend([role.node_count_parameter_name, - role.image_parameter_name, - role.flavor_parameter_name]) - params = [p for p in params if p['name'] not in key_params] - return [Parameter(p, plan=self) for p in params] - - def parameter(self, param_name): - for parameter in self.parameters: - if parameter['name'] == param_name: - return Parameter(parameter, plan=self) - - def parameter_value(self, param_name, default=None): - parameter = self.parameter(param_name) - if parameter is not None: - return parameter.value - return default - - def list_generated_parameters(self, with_prefix=True): - if with_prefix: - key_format = lambda key: key - else: - key_format = strip_prefix - - # Get all password like parameters - return dict( - (key_format(parameter['name']), parameter) - for parameter in self.parameter_list() - if any([ - _should_generate_password(parameter), - _should_generate_keystone_cert(parameter), - _should_generate_neutron_control_plane(parameter), - ]) - ) - - def _make_keystone_certificates(self, wanted_generated_params): - generated_params = {} - for cert_param in KEYSTONE_CERTIFICATE_PARAMS: - if cert_param in wanted_generated_params.keys(): - # If one of the keystone certificates is not set, we have - # to generate all of them. - generate_certificates = True - break - else: - generate_certificates = False - - # Generate keystone certificates - if generate_certificates: - ca_key_pem, ca_cert_pem = keystone_pki.create_ca_pair() - signing_key_pem, signing_cert_pem = ( - keystone_pki.create_signing_pair(ca_key_pem, ca_cert_pem)) - generated_params['KeystoneSigningCertificate'] = ( - signing_cert_pem) - generated_params['KeystoneCACertificate'] = ca_cert_pem - generated_params['KeystoneSigningKey'] = signing_key_pem - return generated_params - - def make_generated_parameters(self): - wanted_generated_params = self.list_generated_parameters( - with_prefix=False) - - # Generate keystone certificates - generated_params = self._make_keystone_certificates( - wanted_generated_params) - - # Generate passwords and control plane id - for (key, param) in wanted_generated_params.items(): - if _should_generate_password(param): - generated_params[key] = password_generator() - elif _should_generate_neutron_control_plane(param): - generated_params[key] = neutron.network_list( - self._request, name='ctlplane')[0].id - - # Fill all the Tuskar parameters with generated content. There are - # parameters that has just different prefix, such parameters should - # have the same values. - wanted_prefixed_params = self.list_generated_parameters( - with_prefix=True) - tuskar_params = {} - - for (key, param) in wanted_prefixed_params.items(): - tuskar_params[key] = generated_params[strip_prefix(key)] - - return tuskar_params - - @property - def id(self): - return self.uuid - - -class Role(base.APIResourceWrapper): - _attrs = ('uuid', 'name', 'version', 'description', 'created') - - def __init__(self, apiresource, request=None): - super(Role, self).__init__(apiresource) - self._request = request - - @classmethod - @memoized.memoized - @handle_errors(_("Unable to retrieve overcloud roles"), []) - def list(cls, request): - """Return a list of Overcloud Roles in Tuskar - - :param request: request object - :type request: django.http.HttpRequest - - :return: list of Overcloud Roles, or an empty list if there - are none - :rtype: list of tuskar_ui.api.tuskar.Role - """ - roles = tuskarclient(request).roles.list() - return [cls(role, request=request) for role in roles] - - @classmethod - @memoized.memoized - @handle_errors(_("Unable to retrieve overcloud role")) - def get(cls, request, role_id): - """Return the Tuskar Role that matches the ID - - :param request: request object - :type request: django.http.HttpRequest - - :param role_id: ID of Role to be retrieved - :type role_id: int - - :return: matching Role, or None if no matching - Role can be found - :rtype: tuskar_ui.api.tuskar.Role - """ - for role in Role.list(request): - if role.uuid == role_id: - return role - - @classmethod - @memoized.memoized - def _roles_by_image(cls, request, plan): - roles_by_image = {} - - for role in Role.list(request): - image = plan.parameter_value(role.image_parameter_name) - if image in roles_by_image: - roles_by_image[image].append(role) - else: - roles_by_image[image] = [role] - - return roles_by_image - - @classmethod - @handle_errors(_("Unable to retrieve overcloud role")) - def get_by_image(cls, request, plan, image): - """Return the Role whose ImageID parameter matches the image. - - :param request: request object - :type request: django.http.HttpRequest - - :param plan: associated plan to check against - :type plan: Plan - - :param image: image to be matched - :type image: Image - - :return: matching Role, or None if no matching - Role can be found - :rtype: tuskar_ui.api.tuskar.Role - """ - roles = cls._roles_by_image(request, plan) - try: - return roles[image.name] - except KeyError: - return [] - - @classmethod - @memoized.memoized - def _roles_by_resource_type(cls, request): - return {role.provider_resource_type: role - for role in Role.list(request)} - - @classmethod - @handle_errors(_("Unable to retrieve overcloud role")) - def get_by_resource_type(cls, request, resource_type): - roles = cls._roles_by_resource_type(request) - try: - return roles[resource_type] - except KeyError: - return None - - @property - def provider_resource_type(self): - return "Tuskar::{0}-{1}".format(self.name, self.version) - - @property - def parameter_prefix(self): - return "{0}-{1}::".format(self.name, self.version) - - @property - def node_count_parameter_name(self): - return self.parameter_prefix + 'count' - - @property - def image_parameter_name(self): - return self.parameter_prefix + 'Image' - - @property - def flavor_parameter_name(self): - return self.parameter_prefix + 'Flavor' - - def image(self, plan): - image_name = plan.parameter_value(self.image_parameter_name) - if image_name: - try: - return glance.image_list_detailed( - self._request, filters={'name': image_name})[0][0] - except (glance_exceptions.HTTPNotFound, IndexError): - LOG.error("Couldn't obtain image with name %s" % image_name) - return None - - def flavor(self, plan): - flavor_name = plan.parameter_value( - self.flavor_parameter_name) - if flavor_name: - return flavor.Flavor.get_by_name(self._request, flavor_name) - - def parameter_list(self, plan): - return [p for p in plan.parameter_list() if self == p.role] - - def is_valid_for_deployment(self, plan): - node_count = plan.get_role_node_count(self) - pending_required_params = list(Parameter.pending_parameters( - Parameter.required_parameters(self.parameter_list(plan)))) - return not ( - self.image(plan) is None or - (node_count and self.flavor(plan) is None) or - pending_required_params - ) - - @property - def id(self): - return self.uuid - - -class Parameter(base.APIDictWrapper): - - _attrs = ['name', 'value', 'default', 'description', 'hidden', 'label', - 'parameter_type', 'constraints'] - - def __init__(self, apidict, plan=None): - super(Parameter, self).__init__(apidict) - self._plan = plan - - @property - def stripped_name(self): - return strip_prefix(self.name) - - @property - def plan(self): - return self._plan - - @property - def role(self): - if self.plan: - for role in self.plan.role_list: - if self.name.startswith(role.parameter_prefix): - return role - - def is_required(self): - """Boolean: True if parameter is required, False otherwise.""" - return self.default is None - - def get_constraint_by_type(self, constraint_type): - """Returns parameter constraint by it's type. - - For available constraint types see HOT Spec: - http://docs.openstack.org/developer/heat/template_guide/hot_spec.html - """ - - constraints_of_type = [c for c in self.constraints - if c['constraint_type'] == constraint_type] - if constraints_of_type: - return constraints_of_type[0] - else: - return None - - @staticmethod - def required_parameters(parameters): - """Yields parameters which are required.""" - for parameter in parameters: - if parameter.is_required(): - yield parameter - - @staticmethod - def pending_parameters(parameters): - """Yields parameters which don't have value set.""" - for parameter in parameters: - if not parameter.value: - yield parameter - - @staticmethod - def global_parameters(parameters): - """Yields parameters with name without role prefix.""" - for parameter in parameters: - if '::' not in parameter.name: - yield parameter |