summaryrefslogtreecommitdiff
path: root/tuskar_ui/api/heat.py
diff options
context:
space:
mode:
Diffstat (limited to 'tuskar_ui/api/heat.py')
-rw-r--r--tuskar_ui/api/heat.py553
1 files changed, 0 insertions, 553 deletions
diff --git a/tuskar_ui/api/heat.py b/tuskar_ui/api/heat.py
deleted file mode 100644
index 18056de5..00000000
--- a/tuskar_ui/api/heat.py
+++ /dev/null
@@ -1,553 +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 os
-import tempfile
-import urlparse
-
-from django.conf import settings
-from django.utils.translation import ugettext_lazy as _
-from heatclient.common import template_utils
-from heatclient.exc import HTTPNotFound
-from horizon.utils import memoized
-from openstack_dashboard.api import base
-from openstack_dashboard.api import heat
-from openstack_dashboard.api import keystone
-
-from tuskar_ui.api import node
-from tuskar_ui.api import tuskar
-from tuskar_ui.cached_property import cached_property # noqa
-from tuskar_ui.handle_errors import handle_errors # noqa
-from tuskar_ui.utils import utils
-
-
-LOG = logging.getLogger(__name__)
-
-
-@memoized.memoized
-def overcloud_keystoneclient(request, endpoint, password):
- """Returns a client connected to the Keystone backend.
-
- Several forms of authentication are supported:
-
- * Username + password -> Unscoped authentication
- * Username + password + tenant id -> Scoped authentication
- * Unscoped token -> Unscoped authentication
- * Unscoped token + tenant id -> Scoped authentication
- * Scoped token -> Scoped authentication
-
- Available services and data from the backend will vary depending on
- whether the authentication was scoped or unscoped.
-
- Lazy authentication if an ``endpoint`` parameter is provided.
-
- Calls requiring the admin endpoint should have ``admin=True`` passed in
- as a keyword argument.
-
- The client is cached so that subsequent API calls during the same
- request/response cycle don't have to be re-authenticated.
- """
- api_version = keystone.VERSIONS.get_active_version()
-
- # TODO(lsmola) add support of certificates and secured http and rest of
- # parameters according to horizon and add configuration to local settings
- # (somehow plugin based, we should not maintain a copy of settings)
- LOG.debug("Creating a new keystoneclient connection to %s." % endpoint)
-
- # TODO(lsmola) we should create tripleo-admin user for this purpose
- # this needs to be done first on tripleo side
- conn = api_version['client'].Client(username="admin",
- password=password,
- tenant_name="admin",
- auth_url=endpoint)
-
- return conn
-
-
-def _save_templates(templates):
- """Saves templates into tmpdir on server
-
- This should go away and get replaced by libutils.save_templates from
- tripleo-common https://github.com/openstack/tripleo-common/
- """
- output_dir = tempfile.mkdtemp()
-
- for template_name, template_content in templates.items():
-
- # It's possible to organize the role templates and their dependent
- # files into directories, in which case the template_name will carry
- # the directory information. If that's the case, first create the
- # directory structure (if it hasn't already been created by another
- # file in the templates list).
- template_dir = os.path.dirname(template_name)
- output_template_dir = os.path.join(output_dir, template_dir)
- if template_dir and not os.path.exists(output_template_dir):
- os.makedirs(output_template_dir)
-
- filename = os.path.join(output_dir, template_name)
- with open(filename, 'w+') as template_file:
- template_file.write(template_content)
- return output_dir
-
-
-def _process_templates(templates):
- """Process templates
-
- Due to bug in heat api
- https://bugzilla.redhat.com/show_bug.cgi?id=1212740, we need to
- save the templates in tmpdir, reprocess them with template_utils
- from heatclient and then we can use them in creating/updating stack.
-
- This should be replaced by the same code that is in tripleo-common and
- eventually it will not be needed at all.
- """
-
- tpl_dir = _save_templates(templates)
-
- tpl_files, template = template_utils.get_template_contents(
- template_file=os.path.join(tpl_dir, tuskar.MASTER_TEMPLATE_NAME))
- env_files, env = (
- template_utils.process_multiple_environments_and_files(
- env_paths=[os.path.join(tpl_dir, tuskar.ENVIRONMENT_NAME)]))
-
- files = dict(list(tpl_files.items()) + list(env_files.items()))
-
- return template, env, files
-
-
-class Stack(base.APIResourceWrapper):
- _attrs = ('id', 'stack_name', 'outputs', 'stack_status', 'parameters')
-
- def __init__(self, apiresource, request=None):
- super(Stack, self).__init__(apiresource)
- self._request = request
-
- @classmethod
- def create(cls, request, stack_name, templates):
- template, environment, files = _process_templates(templates)
-
- fields = {
- 'stack_name': stack_name,
- 'template': template,
- 'environment': environment,
- 'files': files,
- 'timeout_mins': 240,
- }
- password = getattr(settings, 'UNDERCLOUD_ADMIN_PASSWORD', None)
- stack = heat.stack_create(request, password, **fields)
- return cls(stack, request=request)
-
- def update(self, request, stack_name, templates):
- template, environment, files = _process_templates(templates)
-
- fields = {
- 'stack_name': stack_name,
- 'template': template,
- 'environment': environment,
- 'files': files,
- }
- password = getattr(settings, 'UNDERCLOUD_ADMIN_PASSWORD', None)
- heat.stack_update(request, self.id, password, **fields)
-
- @classmethod
- @handle_errors(_("Unable to retrieve heat stacks"), [])
- def list(cls, request):
- """Return a list of stacks in Heat
-
- :param request: request object
- :type request: django.http.HttpRequest
-
- :return: list of Heat stacks, or an empty list if there
- are none
- :rtype: list of tuskar_ui.api.heat.Stack
- """
- stacks, has_more_data, has_prev_data = heat.stacks_list(request)
- return [cls(stack, request=request) for stack in stacks]
-
- @classmethod
- @handle_errors(_("Unable to retrieve stack"))
- def get(cls, request, stack_id):
- """Return the Heat Stack associated with this Overcloud
-
- :return: Heat Stack associated with the stack_id; or None
- if no Stack is associated, or no Stack can be
- found
- :rtype: tuskar_ui.api.heat.Stack or None
- """
- return cls(heat.stack_get(request, stack_id), request=request)
-
- @classmethod
- @handle_errors(_("Unable to retrieve stack"))
- def get_by_plan(cls, request, plan):
- """Return the Heat Stack associated with a Plan
-
- :return: Heat Stack associated with the plan; or None
- if no Stack is associated, or no Stack can be
- found
- :rtype: tuskar_ui.api.heat.Stack or None
- """
- # TODO(lsmola) until we have working deployment through Tuskar-API,
- # this will not work
- # for stack in Stack.list(request):
- # if stack.plan and (stack.plan.id == plan.id):
- # return stack
- try:
- stack = Stack.list(request)[0]
- except IndexError:
- return None
- # TODO(lsmola) stack list actually does not contain all the detail
- # info, there should be call for that, investigate
- return Stack.get(request, stack.id)
-
- @classmethod
- @handle_errors(_("Unable to delete Heat stack"), [])
- def delete(cls, request, stack_id):
- heat.stack_delete(request, stack_id)
-
- @memoized.memoized
- def resources(self, with_joins=True, role=None):
- """Return list of OS::Nova::Server Resources
-
- Return list of OS::Nova::Server Resources associated with the Stack
- and which are associated with a Role
-
- :param with_joins: should we also retrieve objects associated with each
- retrieved Resource?
- :type with_joins: bool
-
- :return: list of all Resources or an empty list if there are none
- :rtype: list of tuskar_ui.api.heat.Resource
- """
-
- if role:
- roles = [role]
- else:
- roles = self.plan.role_list
- resource_dicts = []
-
- # A provider resource is deployed as a nested stack, so we have to
- # drill down and retrieve those that match a tuskar role
- for role in roles:
- resource_group_name = role.name
- try:
- resource_group = heat.resource_get(self._request,
- self.id,
- resource_group_name)
-
- group_resources = heat.resources_list(
- self._request, resource_group.physical_resource_id)
- for group_resource in group_resources:
- if not group_resource.physical_resource_id:
- # Skip groups who has no physical resource.
- continue
- nova_resources = heat.resources_list(
- self._request,
- group_resource.physical_resource_id)
- resource_dicts.extend([{"resource": resource,
- "role": role}
- for resource in nova_resources])
-
- except HTTPNotFound:
- pass
-
- if not with_joins:
- return [Resource(rd['resource'], request=self._request,
- stack=self, role=rd['role'])
- for rd in resource_dicts]
-
- nodes_dict = utils.list_to_dict(node.Node.list(self._request,
- associated=True),
- key_attribute='instance_uuid')
- joined_resources = []
- for rd in resource_dicts:
- resource = rd['resource']
- joined_resources.append(
- Resource(resource,
- node=nodes_dict.get(resource.physical_resource_id,
- None),
- request=self._request, stack=self, role=rd['role']))
- # TODO(lsmola) I want just resources with nova instance
- # this could be probably filtered a better way, investigate
- return [r for r in joined_resources if r.node is not None]
-
- @memoized.memoized
- def resources_count(self, overcloud_role=None):
- """Return count of associated Resources
-
- :param overcloud_role: role of resources to be counted; None means all
- :type overcloud_role: tuskar_ui.api.tuskar.Role
-
- :return: Number of matching resources
- :rtype: int
- """
- # TODO(dtantsur): there should be better way to do it, rather than
- # fetching and calling len()
- # FIXME(dtantsur): should also be able to use with_joins=False
- # but unable due to bug #1289505
- if overcloud_role is None:
- resources = self.resources()
- else:
- resources = self.resources(role=overcloud_role)
- return len(resources)
-
- @cached_property
- def plan(self):
- """return associated Plan if a plan_id exists within stack parameters.
-
- :return: associated Plan if plan_id exists and a matching plan
- exists as well; None otherwise
- :rtype: tuskar_ui.api.tuskar.Plan
- """
- # TODO(lsmola) replace this by actual reference, I am pretty sure
- # the relation won't be stored in parameters, that would mean putting
- # that into template, which doesn't make sense
- # if 'plan_id' in self.parameters:
- # return tuskar.Plan.get(self._request,
- # self.parameters['plan_id'])
- try:
- plan = tuskar.Plan.list(self._request)[0]
- except IndexError:
- return None
- return plan
-
- @cached_property
- def is_initialized(self):
- """Check if this Stack is successfully initialized.
-
- :return: True if this Stack is successfully initialized, False
- otherwise
- :rtype: bool
- """
- return len(self.dashboard_urls) > 0
-
- @cached_property
- def is_deployed(self):
- """Check if this Stack is successfully deployed.
-
- :return: True if this Stack is successfully deployed, False otherwise
- :rtype: bool
- """
- return self.stack_status in ('CREATE_COMPLETE',
- 'UPDATE_COMPLETE')
-
- @cached_property
- def is_deploying(self):
- """Check if this Stack is currently deploying.
-
- :return: True if deployment is in progress, False otherwise.
- :rtype: bool
- """
- return self.stack_status in ('CREATE_IN_PROGRESS',)
-
- @cached_property
- def is_updating(self):
- """Check if this Stack is currently updating.
-
- :return: True if updating is in progress, False otherwise.
- :rtype: bool
- """
- return self.stack_status in ('UPDATE_IN_PROGRESS',)
-
- @cached_property
- def is_failed(self):
- """Check if this Stack failed to update or deploy.
-
- :return: True if deployment there was an error, False otherwise.
- :rtype: bool
- """
- return self.stack_status in ('CREATE_FAILED',
- 'UPDATE_FAILED',)
-
- @cached_property
- def is_deleting(self):
- """Check if this Stack is deleting.
-
- :return: True if Stack is deleting, False otherwise.
- :rtype: bool
- """
- return self.stack_status in ('DELETE_IN_PROGRESS', )
-
- @cached_property
- def is_delete_failed(self):
- """Check if Stack deleting has failed.
-
- :return: True if Stack deleting has failed, False otherwise.
- :rtype: bool
- """
- return self.stack_status in ('DELETE_FAILED', )
-
- @cached_property
- def events(self):
- """Return the Heat Events associated with this Stack
-
- :return: list of Heat Events associated with this Stack;
- or an empty list if there is no Stack associated with
- this Stack, or there are no Events
- :rtype: list of heatclient.v1.events.Event
- """
- return heat.events_list(self._request,
- self.stack_name)
-
- @property
- def stack_outputs(self):
- return getattr(self, 'outputs', [])
-
- @cached_property
- def keystone_auth_url(self):
- for output in self.stack_outputs:
- if output['output_key'] == 'KeystoneURL':
- return output['output_value']
-
- @cached_property
- def keystone_ip(self):
- if self.keystone_auth_url:
- return urlparse.urlparse(self.keystone_auth_url).hostname
-
- @cached_property
- def overcloud_keystone(self):
- try:
- return overcloud_keystoneclient(
- self._request,
- self.keystone_auth_url,
- self.plan.parameter_value('Controller-1::AdminPassword'))
- except Exception:
- LOG.debug('Unable to connect to overcloud keystone.')
- return None
-
- @cached_property
- def dashboard_urls(self):
- client = self.overcloud_keystone
- if not client:
- return []
-
- try:
- services = client.services.list()
- for service in services:
- if service.name == 'horizon':
- break
- else:
- return []
- except Exception:
- return []
-
- admin_urls = [endpoint.adminurl for endpoint
- in client.endpoints.list()
- if endpoint.service_id == service.id]
-
- return admin_urls
-
-
-class Resource(base.APIResourceWrapper):
- _attrs = ('resource_name', 'resource_type', 'resource_status',
- 'physical_resource_id')
-
- def __init__(self, apiresource, request=None, **kwargs):
- """Initialize a resource
-
- :param apiresource: apiresource we want to wrap
- :type apiresource: heatclient.v1.resources.Resource
-
- :param request: request
- :type request: django.core.handlers.wsgi.WSGIRequest
-
- :param node: node relation we want to cache
- :type node: tuskar_ui.api.node.Node
-
- :return: Resource object
- :rtype: Resource
- """
- super(Resource, self).__init__(apiresource)
- self._request = request
- if 'node' in kwargs:
- self._node = kwargs['node']
- if 'stack' in kwargs:
- self._stack = kwargs['stack']
- if 'role' in kwargs:
- self._role = kwargs['role']
-
- @classmethod
- @memoized.memoized
- def _resources_by_nodes(cls, request):
- return {resource.physical_resource_id: resource
- for resource in cls.list_all_resources(request)}
-
- @classmethod
- def get_by_node(cls, request, node):
- """Return the specified Heat Resource given a Node
-
- :param request: request object
- :type request: django.http.HttpRequest
-
- :param node: node to match
- :type node: tuskar_ui.api.node.Node
-
- :return: matching Resource, or raises LookupError if no
- resource matches the node
- :rtype: tuskar_ui.api.heat.Resource
- """
- return cls._resources_by_nodes(request)[node.instance_uuid]
-
- @classmethod
- def list_all_resources(cls, request):
- """Iterate through all the stacks and return all relevant resources
-
- :param request: request object
- :type request: django.http.HttpRequest
-
- :return: list of resources
- :rtype: list of tuskar_ui.api.heat.Resource
- """
- all_resources = []
- for stack in Stack.list(request):
- all_resources.extend(stack.resources(with_joins=False))
- return all_resources
-
- @cached_property
- def role(self):
- """Return the Role associated with this Resource
-
- :return: Role associated with this Resource, or None if no
- Role is associated
- :rtype: tuskar_ui.api.tuskar.Role
- """
- if hasattr(self, '_role'):
- return self._role
-
- @cached_property
- def node(self):
- """Return the Ironic Node associated with this Resource
-
- :return: Ironic Node associated with this Resource, or None if no
- Node is associated
- :rtype: tuskar_ui.api.node.Node
-
- :raises: ironicclient.exc.HTTPNotFound if there is no Node with the
- matching instance UUID
- """
- if hasattr(self, '_node'):
- return self._node
- if self.physical_resource_id:
- return node.Node.get_by_instance_uuid(self._request,
- self.physical_resource_id)
- return None
-
- @cached_property
- def stack(self):
- """Return the Stack associated with this Resource
-
- :return: Stack associated with this Resource, or None if no
- Stack is associated
- :rtype: tuskar_ui.api.heat.Stack
- """
- if hasattr(self, '_stack'):
- return self._stack