# 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 random
import six
import string

from oslo_context import context
from oslo_log import log as logging

from senlin.common import exception
from senlin.common.i18n import _
from senlin.common import schema
from senlin.drivers.openstack import heat_v1 as heatclient
from senlin.engine import scheduler
from senlin.profiles import base

LOG = logging.getLogger(__name__)


class StackProfile(base.Profile):
    '''Profile for an OpenStack Heat stack.

    When this profile is used, the whole cluster is a collection of Heat
    stacks.
    '''

    KEYS = (
        TEMPLATE, CONTEXT, PARAMETERS, FILES,
        TIMEOUT, DISABLE_ROLLBACK, ENVIRONMENT,
    ) = (
        'template', 'context', 'parameters', 'files',
        'timeout', 'disable_rollback', 'environment',
    )

    spec_schema = {
        CONTEXT: schema.Map(
            _('A dictionary for specifying the customized context for '
              'stack operations'),
            default={},
        ),
        TEMPLATE: schema.Map(
            _('Heat stack template.'),
            required=True,
        ),
        PARAMETERS: schema.Map(
            _('Parameters to be passed to Heat for stack operations.'),
            default={},
        ),
        FILES: schema.Map(
            _('Contents of files referenced by the template, if any.'),
            default={},
        ),
        TIMEOUT: schema.Integer(
            _('A integer that specifies the number of minutes that a '
              'stack operation times out.'),
        ),
        DISABLE_ROLLBACK: schema.Boolean(
            _('A boolean specifying whether a stack operation can be '
              'rolled back.'),
            default=True,
        ),
        ENVIRONMENT: schema.Map(
            _('A map that specifies the environment used for stack '
              'operations.'),
            default={},
        )
    }

    def __init__(self, type_name, name, **kwargs):
        super(StackProfile, self).__init__(type_name, name, **kwargs)

        self.hc = None
        self.stack_id = None

    def heat(self, obj):
        '''Construct heat client using the combined context.'''

        if self.hc:
            return self.hc
        params = self._get_connection_params(context.get_current(), obj)
        self.hc = heatclient.HeatClient(params)
        return self.hc

    def do_validate(self, obj):
        '''Validate if the spec has provided info for stack creation.'''

        kwargs = {
            'stack_name': obj.name,
            'template': self.spec_data[self.TEMPLATE],
            'timeout_mins': self.spec_data[self.TIMEOUT],
            'disable_rollback': self.spec_data[self.DISABLE_ROLLBACK],
            'parameters': self.spec_data[self.PARAMETERS],
            'files': self.spec_data[self.FILES],
            'environment': self.spec_data[self.ENVIRONMENT],
        }
        try:
            self.heat(obj).stacks.validate(**kwargs)
        except Exception as ex:
            msg = _('Failed validate stack template due to '
                    '"%s"') % six.text_type(ex)
            raise exception.ProfileValidationFailed(message=msg)

        return True

    def _check_action_complete(self, obj, action):
        stack = self.heat(obj).stack_get(self.stack_id)
        status = stack.status.split('_', 1)

        if status[0] == action:
            if status[1] == 'IN_PROGRESS':
                return False

            if status[1] == 'COMPLETE':
                return True

            raise exception.ResourceStatusError(resource_id=self.stack_id,
                                                status=stack.status,
                                                reason=stack.status_reason)
        else:
            return False

    def do_create(self, obj):
        '''Create a stack using the given profile.'''

        random_str = '-' + ''.join(random.choice(string.ascii_lowercase)
                                   for i in range(8))
        kwargs = {
            'stack_name': obj.name + random_str,
            'template': self.spec_data[self.TEMPLATE],
            'timeout_mins': self.spec_data[self.TIMEOUT],
            'disable_rollback': self.spec_data[self.DISABLE_ROLLBACK],
            'parameters': self.spec_data[self.PARAMETERS],
            'files': self.spec_data[self.FILES],
            'environment': self.spec_data[self.ENVIRONMENT],
        }

        LOG.info('Creating stack: %s' % kwargs)
        stack = self.heat(obj).stack_create(**kwargs)
        self.stack_id = stack.id

        # Wait for action to complete/fail
        while not self._check_action_complete(obj, 'CREATE'):
            scheduler.sleep(1)

        return stack.id

    def do_delete(self, obj):
        self.stack_id = obj.physical_id

        try:
            self.heat(obj).stack_delete(self.stack_id, True)
        except Exception as ex:
            raise ex

        # Wait for action to complete/fail
        while not self._check_action_complete(obj, 'DELETE'):
            scheduler.sleep(1)

        return True

    def do_update(self, obj, new_profile):
        '''Perform update on object.

        :param obj: the node object to operate on
        :param new_profile: the new profile used for updating
        '''
        self.stack_id = obj.physical_id
        if not self.stack_id:
            return True

        # TODO(anyone): Check if template differs
        # TODO(anyone): Check if params differs
        fields = {
            'stack_id': self.stack_id,
            'parameters': new_profile.spec_data[new_profile.PARAMETERS],
            'template': new_profile.spec_data[new_profile.TEMPLATE],
            'timeout_mins': new_profile.spec_data[new_profile.TIMEOUT],
            'disable_rollback': new_profile.spec_data[
                new_profile.DISABLE_ROLLBACK],
            'files': new_profile.spec_data[new_profile.FILES],
            'environment': new_profile.spec_data[new_profile.ENVIRONMENT],
        }

        self.heat(obj).stack_update(**fields)

        # Wait for action to complete/fail
        while not self._check_action_complete(obj, 'UPDATE'):
            scheduler.sleep(1)

        return True

    def do_check(self, obj):
        """Check stack status."""
        hc = self.heat(obj)
        try:
            stack = hc.stack_get(obj.physical_id)
        except Exception as ex:
            raise ex
        # When the stack is in a status which can't be checked(
        # CREATE_IN_PROGRESS, DELETE_IN_PROGRESS, etc), return False.
        try:
            stack.check(hc.session)
        except Exception:
            return False

        status = stack.status
        while status == 'CHECK_IN_PROGRESS':
            status = hc.stack_get(obj.physical_id).status
        if status == 'CHECK_COMPLETE':
            return True
        else:
            return False

    def get_template(self):
        return {}

    def get_schema(self):
        return {}

    def do_get_details(self, obj):
        if obj.physical_id is None or obj.physical_id == '':
            return {}

        return self.heat(obj).stack_get(obj.physical_id)

    def do_join(self, obj, cluster_id):
        # Metadata is not supported by Heat stacks
        return {}

    def do_leave(self, obj):
        # Metadata is not supported by Heat stacks
        return

@@ -553,39 +553,11 @@
 ort 
-random%0Aimport six%0Aimport string
+six
 %0A%0Afr
@@ -726,16 +726,48 @@
  schema%0A
+from senlin.common import utils%0A
 from sen
@@ -4363,135 +4363,8 @@
 ''%0A%0A
-        random_str = '-' + ''.join(random.choice(string.ascii_lowercase)%0A                                   for i in range(8))%0A
     
@@ -4419,18 +4419,34 @@
 e + 
-random_str
+'-' + utils.random_name(8)
 ,%0A  

Refactor random heat stack name