<commit_before>#    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 urlparse

from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
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__)


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


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, template, environment,
               provider_resource_templates):
        fields = {
            'stack_name': stack_name,
            'template': template,
            'environment': environment,
            'files': provider_resource_templates,
            'password': getattr(settings, 'UNDERCLOUD_ADMIN_PASSWORD', None),
        }
        stack = heat.stack_create(request, **fields)
        return cls(stack, request=request)

    @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.provider_resource_group_name
            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])

        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
<commit_msg>Catch missing resource error when deploying<commit_after>#    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 urlparse

from django.conf import settings
from django.utils.translation import ugettext_lazy as _
import heatclient
from horizon import exceptions
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__)


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


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, template, environment,
               provider_resource_templates):
        fields = {
            'stack_name': stack_name,
            'template': template,
            'environment': environment,
            'files': provider_resource_templates,
            'password': getattr(settings, 'UNDERCLOUD_ADMIN_PASSWORD', None),
        }
        stack = heat.stack_create(request, **fields)
        return cls(stack, request=request)

    @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.provider_resource_group_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 heatclient.exc.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

{'fraction_non_alphanumeric': 0.06816091954022989, 'fraction_numerical': 0.0005747126436781609, 'mean_word_length': 2.155785273848386, 'pattern_counts': {'":': 4, '<': 3, '<?xml version=': 0, '>': 13, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 31, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4701029', 'n_tokens_mistral': 4345, 'n_tokens_neox': 4228, 'n_words': 1527}