summaryrefslogtreecommitdiff
path: root/tuskar_ui/infrastructure/overview
diff options
context:
space:
mode:
Diffstat (limited to 'tuskar_ui/infrastructure/overview')
-rw-r--r--tuskar_ui/infrastructure/overview/__init__.py0
-rw-r--r--tuskar_ui/infrastructure/overview/forms.py488
-rw-r--r--tuskar_ui/infrastructure/overview/panel.py26
-rw-r--r--tuskar_ui/infrastructure/overview/tests.py364
-rw-r--r--tuskar_ui/infrastructure/overview/urls.py38
-rw-r--r--tuskar_ui/infrastructure/overview/views.py390
6 files changed, 0 insertions, 1306 deletions
diff --git a/tuskar_ui/infrastructure/overview/__init__.py b/tuskar_ui/infrastructure/overview/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/tuskar_ui/infrastructure/overview/__init__.py
+++ /dev/null
diff --git a/tuskar_ui/infrastructure/overview/forms.py b/tuskar_ui/infrastructure/overview/forms.py
deleted file mode 100644
index 77ff8c37..00000000
--- a/tuskar_ui/infrastructure/overview/forms.py
+++ /dev/null
@@ -1,488 +0,0 @@
-# -*- coding: utf8 -*-
-#
-# 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 six
-import uuid
-
-from django.conf import settings
-import django.forms
-from django.utils.translation import ugettext_lazy as _
-import horizon.exceptions
-import horizon.forms
-import horizon.messages
-from os_cloud_config import keystone as keystone_config
-from os_cloud_config.utils import clients
-
-from tuskar_ui import api
-import tuskar_ui.api.heat
-import tuskar_ui.api.tuskar
-import tuskar_ui.forms
-import tuskar_ui.infrastructure.flavors.utils as flavors_utils
-import tuskar_ui.utils.utils as tuskar_utils
-
-MATCHING_DEPLOYMENT_MODE = flavors_utils.matching_deployment_mode()
-LOG = logging.getLogger(__name__)
-MESSAGE_ICONS = {
- 'ok': 'fa-check-square-o text-success',
- 'pending': 'fa-square-o text-info',
- 'error': 'fa-exclamation-circle text-danger',
- 'warning': 'fa-exclamation-triangle text-warning',
- None: 'fa-exclamation-triangle text-warning',
-}
-WEBROOT = getattr(settings, 'WEBROOT', '/')
-
-
-def validate_roles(request, plan):
- """Validates the roles in plan and returns dict describing the issues"""
- for role in plan.role_list:
- if (
- plan.get_role_node_count(role) and
- not role.is_valid_for_deployment(plan)
- ):
- message = {
- 'text': _(u"Configure Roles."),
- 'is_critical': True,
- 'status': 'pending',
- }
- break
- else:
- message = {
- 'text': _(u"Configure Roles."),
- 'status': 'ok',
- }
- return message
-
-
-def validate_global_parameters(request, plan):
- pending_required_global_params = list(
- api.tuskar.Parameter.pending_parameters(
- api.tuskar.Parameter.required_parameters(
- api.tuskar.Parameter.global_parameters(
- plan.parameter_list()))))
- if pending_required_global_params:
- message = {
- 'text': _(u"Global Service Configuration."),
- 'is_critical': True,
- 'status': 'pending',
- }
- else:
- message = {
- 'text': _(u"Global Service Configuration."),
- 'status': 'ok',
- }
- return message
-
-
-def validate_plan(request, plan):
- """Validates the plan and returns a list of dicts describing the issues."""
- messages = []
- requested_nodes = 0
- for role in plan.role_list:
- node_count = plan.get_role_node_count(role)
- requested_nodes += node_count
- available_flavors = len(api.flavor.Flavor.list(request))
- if available_flavors == 0:
- messages.append({
- 'text': _(u"Define Flavors."),
- 'is_critical': True,
- 'status': 'pending',
- })
- else:
- messages.append({
- 'text': _(u"Define Flavors."),
- 'status': 'ok',
- })
- available_nodes = len(api.node.Node.list(request, associated=False,
- maintenance=False))
- if available_nodes == 0:
- messages.append({
- 'text': _(u"Register Nodes."),
- 'is_critical': True,
- 'status': 'pending',
- })
- elif requested_nodes > available_nodes:
- messages.append({
- 'text': _(u"Not enough registered nodes for this plan. "
- u"You need {0} more.").format(
- requested_nodes - available_nodes),
- 'is_critical': True,
- 'status': 'error',
- })
- else:
- messages.append({
- 'text': _(u"Register Nodes."),
- 'status': 'ok',
- })
- messages.append(validate_roles(request, plan))
- messages.append(validate_global_parameters(request, plan))
- if not MATCHING_DEPLOYMENT_MODE:
- # All roles have to have the same flavor.
- default_flavor_name = api.flavor.Flavor.list(request)[0].name
- for role in plan.role_list:
- if role.flavor(plan).name != default_flavor_name:
- messages.append({
- 'text': _(u"Role {0} doesn't use default flavor.").format(
- role.name,
- ),
- 'is_critical': False,
- 'statis': 'error',
- })
- roles_assigned = True
- messages.append({
- 'text': _(u"Assign roles."),
- 'status': lambda: 'ok' if roles_assigned else 'pending',
- })
- try:
- controller_role = plan.get_role_by_name("Controller")
- except KeyError:
- messages.append({
- 'text': _(u"Controller Role Needed."),
- 'is_critical': True,
- 'status': 'error',
- 'indent': 1,
- })
- roles_assigned = False
- else:
- if plan.get_role_node_count(controller_role) not in (1, 3):
- messages.append({
- 'text': _(u"1 or 3 Controllers Needed."),
- 'is_critical': True,
- 'status': 'pending',
- 'indent': 1,
- })
- roles_assigned = False
- else:
- messages.append({
- 'text': _(u"1 or 3 Controllers Needed."),
- 'status': 'ok',
- 'indent': 1,
- })
-
- try:
- compute_role = plan.get_role_by_name("Compute")
- except KeyError:
- messages.append({
- 'text': _(u"Compute Role Needed."),
- 'is_critical': True,
- 'status': 'error',
- 'indent': 1,
- })
- roles_assigned = False
- else:
- if plan.get_role_node_count(compute_role) < 1:
- messages.append({
- 'text': _(u"1 Compute Needed."),
- 'is_critical': True,
- 'status': 'pending',
- 'indent': 1,
- })
- roles_assigned = False
- else:
- messages.append({
- 'text': _(u"1 Compute Needed."),
- 'status': 'ok',
- 'indent': 1,
- })
- for message in messages:
- status = message.get('status')
- if callable(status):
- message['status'] = status = status()
- message['classes'] = MESSAGE_ICONS.get(status, MESSAGE_ICONS[None])
- return messages
-
-
-class EditPlan(horizon.forms.SelfHandlingForm):
- def __init__(self, *args, **kwargs):
- super(EditPlan, self).__init__(*args, **kwargs)
- self.plan = api.tuskar.Plan.get_the_plan(self.request)
- self.fields.update(self._role_count_fields(self.plan))
-
- def _role_count_fields(self, plan):
- fields = {}
- for role in plan.role_list:
- field = django.forms.IntegerField(
- label=role.name,
- widget=tuskar_ui.forms.NumberPickerInput(attrs={
- 'min': 1 if role.name in ('Controller', 'Compute') else 0,
- 'step': 2 if role.name == 'Controller' else 1,
- }),
- initial=plan.get_role_node_count(role),
- required=False
- )
- field.role = role
- fields['%s-count' % role.id] = field
- return fields
-
- def handle(self, request, data):
- parameters = dict(
- (field.role.node_count_parameter_name, data[name])
- for (name, field) in self.fields.items() if name.endswith('-count')
- )
- # NOTE(gfidente): this is a bad hack meant to magically add the
- # parameter which enables Neutron L3 HA when the number of
- # Controllers is > 1
- try:
- controller_role = self.plan.get_role_by_name('Controller')
- compute_role = self.plan.get_role_by_name('Compute')
- except Exception as e:
- LOG.warning('Unable to find a required role: %s', e.message)
- else:
- number_controllers = parameters[
- controller_role.node_count_parameter_name]
- if number_controllers > 1:
- for role in [controller_role, compute_role]:
- l3ha_param = role.parameter_prefix + 'NeutronL3HA'
- parameters[l3ha_param] = 'True'
- l3agent_param = (role.parameter_prefix +
- 'NeutronAllowL3AgentFailover')
- parameters[l3agent_param] = 'True'
- dhcp_agents_per_net = (number_controllers if number_controllers and
- number_controllers > 3 else 3)
- dhcp_agents_param = (controller_role.parameter_prefix +
- 'NeutronDhcpAgentsPerNetwork')
- parameters[dhcp_agents_param] = dhcp_agents_per_net
-
- try:
- ceph_storage_role = self.plan.get_role_by_name('Ceph-Storage')
- except Exception as e:
- LOG.warning('Unable to find role: %s', 'Ceph-Storage')
- else:
- if parameters[ceph_storage_role.node_count_parameter_name] > 0:
- parameters.update({
- 'CephClusterFSID': six.text_type(uuid.uuid4()),
- 'CephMonKey': tuskar_utils.create_cephx_key(),
- 'CephAdminKey': tuskar_utils.create_cephx_key()
- })
-
- cinder_enable_rbd_param = (controller_role.parameter_prefix
- + 'CinderEnableRbdBackend')
- glance_backend_param = (controller_role.parameter_prefix +
- 'GlanceBackend')
- nova_enable_rbd_param = (compute_role.parameter_prefix +
- 'NovaEnableRbdBackend')
- cinder_enable_iscsi_param = (
- controller_role.parameter_prefix +
- 'CinderEnableIscsiBackend')
-
- parameters.update({
- cinder_enable_rbd_param: True,
- glance_backend_param: 'rbd',
- nova_enable_rbd_param: True,
- cinder_enable_iscsi_param: False
- })
-
- try:
- self.plan = self.plan.patch(request, self.plan.uuid, parameters)
- except Exception as e:
- horizon.exceptions.handle(request, _("Unable to update the plan."))
- LOG.exception(e)
- return False
- return True
-
-
-class ScaleOut(EditPlan):
- def __init__(self, *args, **kwargs):
- super(ScaleOut, self).__init__(*args, **kwargs)
- for name, field in self.fields.items():
- if name.endswith('-count'):
- field.widget.attrs['min'] = field.initial
-
- def handle(self, request, data):
- if not super(ScaleOut, self).handle(request, data):
- return False
- plan = self.plan
- try:
- stack = api.heat.Stack.get_by_plan(self.request, plan)
- stack.update(request, plan.name, plan.templates)
- except Exception as e:
- LOG.exception(e)
- if hasattr(e, 'error'):
- horizon.exceptions.handle(
- request,
- _(
- "Unable to deploy overcloud. Reason: {0}"
- ).format(e.error['error']['message']),
- )
- return False
- else:
- raise
- else:
- msg = _('Deployment in progress.')
- horizon.messages.success(request, msg)
- return True
-
-
-class DeployOvercloud(horizon.forms.SelfHandlingForm):
- network_isolation = horizon.forms.BooleanField(
- label=_("Enable Network Isolation"),
- required=False)
-
- def handle(self, request, data):
- try:
- plan = api.tuskar.Plan.get_the_plan(request)
- except Exception as e:
- LOG.exception(e)
- horizon.exceptions.handle(request,
- _("Unable to deploy overcloud."))
- return False
-
- # If network isolation selected, read environment file data
- # and add to plan
- env_temp = '/usr/share/openstack-tripleo-heat-templates/environments'
- try:
- if self.cleaned_data['network_isolation']:
- with open(env_temp, 'r') as env_file:
- env_contents = ''.join(
- [line for line in
- env_file.readlines() if '#' not in line]
- )
- plan.environment += env_contents
- except Exception as e:
- LOG.exception(e)
- pass
-
- # Auto-generate missing passwords and certificates
- if plan.list_generated_parameters():
- generated_params = plan.make_generated_parameters()
- plan = plan.patch(request, plan.uuid, generated_params)
-
- # Validate plan and create stack
- for message in validate_plan(request, plan):
- if message.get('is_critical'):
- horizon.messages.success(request, message.text)
- return False
- try:
- stack = api.heat.Stack.get_by_plan(self.request, plan)
- if not stack:
- api.heat.Stack.create(request, plan.name, plan.templates)
- except Exception as e:
- LOG.exception(e)
- horizon.exceptions.handle(
- request, _("Unable to deploy overcloud. Reason: {0}").format(
- e.error['error']['message']))
- return False
- else:
- msg = _('Deployment in progress.')
- horizon.messages.success(request, msg)
- return True
-
-
-class UndeployOvercloud(horizon.forms.SelfHandlingForm):
- def handle(self, request, data):
- try:
- plan = api.tuskar.Plan.get_the_plan(request)
- stack = api.heat.Stack.get_by_plan(self.request, plan)
- if stack:
- api.heat.Stack.delete(request, stack.id)
- except Exception as e:
- LOG.exception(e)
- horizon.exceptions.handle(request,
- _("Unable to undeploy overcloud."))
- return False
- else:
- msg = _('Undeployment in progress.')
- horizon.messages.success(request, msg)
- return True
-
-
-class PostDeployInit(horizon.forms.SelfHandlingForm):
- admin_email = horizon.forms.CharField(label=_("Admin Email"))
- public_host = horizon.forms.CharField(
- label=_("Public Host"), initial="", required=False)
- region = horizon.forms.CharField(
- label=_("Region"), initial="regionOne")
-
- def build_endpoints(self, plan, controller_role):
- return {
- "ceilometer": {
- "password": plan.parameter_value(
- controller_role.parameter_prefix + 'CeilometerPassword')},
- "cinder": {
- "password": plan.parameter_value(
- controller_role.parameter_prefix + 'CinderPassword')},
- "cinderv2": {
- "password": plan.parameter_value(
- controller_role.parameter_prefix + 'CinderPassword')},
- "ec2": {
- "password": plan.parameter_value(
- controller_role.parameter_prefix + 'GlancePassword')},
- "glance": {
- "password": plan.parameter_value(
- controller_role.parameter_prefix + 'GlancePassword')},
- "heat": {
- "password": plan.parameter_value(
- controller_role.parameter_prefix + 'HeatPassword')},
- "neutron": {
- "password": plan.parameter_value(
- controller_role.parameter_prefix + 'NeutronPassword')},
- "nova": {
- "password": plan.parameter_value(
- controller_role.parameter_prefix + 'NovaPassword')},
- "novav3": {
- "password": plan.parameter_value(
- controller_role.parameter_prefix + 'NovaPassword')},
- "swift": {
- "password": plan.parameter_value(
- controller_role.parameter_prefix + 'SwiftPassword'),
- 'path': '/v1/AUTH_%(tenant_id)s',
- 'admin_path': '/v1'},
- "horizon": {
- 'port': '80',
- 'path': WEBROOT,
- 'admin_path': '%sadmin' % WEBROOT}}
-
- def handle(self, request, data):
- try:
- plan = api.tuskar.Plan.get_the_plan(request)
- controller_role = plan.get_role_by_name("Controller")
- stack = api.heat.Stack.get_by_plan(self.request, plan)
-
- admin_token = plan.parameter_value(
- controller_role.parameter_prefix + 'AdminToken')
- admin_password = plan.parameter_value(
- controller_role.parameter_prefix + 'AdminPassword')
- admin_email = data['admin_email']
- auth_ip = stack.keystone_ip
- auth_url = stack.keystone_auth_url
- auth_tenant = 'admin'
- auth_user = 'admin'
-
- # do the keystone init
- keystone_config.initialize(
- auth_ip, admin_token, admin_email, admin_password,
- region='regionOne', ssl=None, public=None, user='heat-admin',
- pki_setup=False)
-
- # retrieve needed Overcloud clients
- keystone_client = clients.get_keystone_client(
- auth_user, admin_password, auth_tenant, auth_url)
-
- # do the setup endpoints
- keystone_config.setup_endpoints(
- self.build_endpoints(plan, controller_role),
- public_host=data['public_host'],
- region=data['region'],
- os_auth_url=auth_url,
- client=keystone_client)
-
- except Exception as e:
- LOG.exception(e)
- horizon.exceptions.handle(request,
- _("Unable to initialize Overcloud."))
- return False
- else:
- msg = _('Overcloud has been initialized.')
- horizon.messages.success(request, msg)
- return True
diff --git a/tuskar_ui/infrastructure/overview/panel.py b/tuskar_ui/infrastructure/overview/panel.py
deleted file mode 100644
index 535cd13b..00000000
--- a/tuskar_ui/infrastructure/overview/panel.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# -*- coding: utf8 -*-
-#
-# 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.
-
-from django.utils.translation import ugettext_lazy as _
-import horizon
-
-from tuskar_ui.infrastructure import dashboard
-
-
-class Overview(horizon.Panel):
- name = _("Overview")
- slug = "overview"
-
-
-dashboard.Infrastructure.register(Overview)
diff --git a/tuskar_ui/infrastructure/overview/tests.py b/tuskar_ui/infrastructure/overview/tests.py
deleted file mode 100644
index 20155c86..00000000
--- a/tuskar_ui/infrastructure/overview/tests.py
+++ /dev/null
@@ -1,364 +0,0 @@
-# -*- coding: utf8 -*-
-#
-# 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 contextlib
-
-from django.core import urlresolvers
-from mock import patch, call # noqa
-from openstack_dashboard.test.test_data import utils
-
-from tuskar_ui import api
-from tuskar_ui.infrastructure.overview import forms
-from tuskar_ui.infrastructure.overview import views
-from tuskar_ui.test import helpers as test
-from tuskar_ui.test.test_data import heat_data
-from tuskar_ui.test.test_data import tuskar_data
-
-
-INDEX_URL = urlresolvers.reverse(
- 'horizon:infrastructure:overview:index')
-DEPLOY_URL = urlresolvers.reverse(
- 'horizon:infrastructure:overview:deploy_confirmation')
-DELETE_URL = urlresolvers.reverse(
- 'horizon:infrastructure:overview:undeploy_confirmation')
-POST_DEPLOY_INIT_URL = urlresolvers.reverse(
- 'horizon:infrastructure:overview:post_deploy_init')
-TEST_DATA = utils.TestDataContainer()
-heat_data.data(TEST_DATA)
-tuskar_data.data(TEST_DATA)
-
-
-@contextlib.contextmanager
-def _mock_plan(**kwargs):
- plan = None
-
- params = {
- 'spec_set': [
- 'create',
- 'delete',
- 'get',
- 'get_the_plan',
- 'id',
- 'uuid',
- 'patch',
- 'parameters',
- 'role_list',
- 'parameter_value',
- 'get_role_by_name',
- 'get_role_node_count',
- 'list_generated_parameters',
- 'make_generated_parameters',
- 'parameter_list',
- ],
- 'create.side_effect': lambda *args, **kwargs: plan,
- 'delete.return_value': None,
- 'get.side_effect': lambda *args, **kwargs: plan,
- 'get_the_plan.side_effect': lambda *args, **kwargs: plan,
- 'id': 'plan-1',
- 'uuid': 'plan-1',
- 'patch.side_effect': lambda *args, **kwargs: plan,
- 'role_list': [],
- 'parameter_list.return_value': [],
- 'parameter_value.return_value': None,
- 'get_role_by_name.side_effect': KeyError,
- 'get_role_node_count.return_value': 0,
- 'list_generated_parameters.return_value': {},
- 'make_generated_parameters.return_value': {},
- }
- params.update(kwargs)
- with patch(
- 'tuskar_ui.api.tuskar.Plan', **params) as Plan:
- plan = Plan
- yield Plan
-
-
-class OverviewTests(test.BaseAdminViewTests):
- def test_index_stack_not_created(self):
- with contextlib.nested(
- _mock_plan(),
- patch('tuskar_ui.api.heat.Stack.list', return_value=[]),
- patch('tuskar_ui.api.node.Node.list', return_value=[]),
- patch('tuskar_ui.api.flavor.Flavor.list', return_value=[]),
- ):
- res = self.client.get(INDEX_URL)
- get_the_plan = api.tuskar.Plan.get_the_plan
- request = get_the_plan.call_args_list[0][0][0]
- self.assertListEqual(get_the_plan.call_args_list, [
- call(request),
- call(request),
- call(request),
- ])
- self.assertListEqual(api.heat.Stack.list.call_args_list, [
- call(request),
- ])
- self.assertListEqual(api.node.Node.list.call_args_list, [
- call(request, associated=False, maintenance=False),
- ])
- self.assertListEqual(api.flavor.Flavor.list.call_args_list, [
- call(request),
- ])
- self.assertTemplateUsed(
- res, 'infrastructure/overview/index.html')
- self.assertTemplateUsed(
- res, 'infrastructure/overview/role_nodes_edit.html')
-
- def test_index_stack_not_created_post(self):
- with contextlib.nested(
- _mock_plan(),
- patch('tuskar_ui.api.heat.Stack.list', return_value=[]),
- patch('tuskar_ui.api.node.Node.list', return_value=[]),
- patch('tuskar_ui.api.flavor.Flavor.list', return_value=[]),
- ) as (plan, _stack_list, _node_list, _flavor_list):
- data = {
- 'role-1-count': 1,
- 'role-2-count': 0,
- 'role-3-count': 0,
- 'role-4-count': 0,
- }
- res = self.client.post(INDEX_URL, data)
- self.assertNoFormErrors(res)
- self.assertRedirectsNoFollow(res, INDEX_URL)
- get_the_plan = api.tuskar.Plan.get_the_plan
- request = get_the_plan.call_args_list[0][0][0]
- self.assertListEqual(get_the_plan.call_args_list, [
- call(request),
- ])
- self.assertListEqual(
- api.tuskar.Plan.patch.call_args_list,
- [call(request, plan.id, {})],
- )
-
- def test_index_stack_deployed(self):
- stack = api.heat.Stack(TEST_DATA.heatclient_stacks.first())
- roles = [api.tuskar.Role(role)
- for role in self.tuskarclient_roles.list()]
-
- with contextlib.nested(
- _mock_plan(**{'get_role_by_name.side_effect': None,
- 'get_role_by_name.return_value': roles[0]}),
- patch('tuskar_ui.api.heat.Stack.get_by_plan',
- return_value=stack),
- patch('tuskar_ui.api.heat.Stack.events',
- return_value=[]),
- ) as (Plan, stack_get_mock, stack_events_mock):
- res = self.client.get(INDEX_URL)
- request = Plan.get_the_plan.call_args_list[0][0][0]
- self.assertListEqual(
- Plan.get_the_plan.call_args_list,
- [
- call(request),
- call(request),
- call(request),
- ])
-
- self.assertTemplateUsed(
- res, 'infrastructure/overview/index.html')
- self.assertTemplateUsed(
- res, 'infrastructure/overview/deployment_live.html')
-
- def test_index_stack_undeploy_in_progress(self):
- stack = api.heat.Stack(TEST_DATA.heatclient_stacks.first())
-
- with contextlib.nested(
- _mock_plan(),
- patch('tuskar_ui.api.heat.Stack.get_by_plan',
- return_value=stack),
- patch('tuskar_ui.api.heat.Stack.is_deleting',
- return_value=True),
- patch('tuskar_ui.api.heat.Stack.is_deployed',
- return_value=False),
- patch('tuskar_ui.api.heat.Stack.resources',
- return_value=[]),
- patch('tuskar_ui.api.heat.Stack.events',
- return_value=[]),
- ):
- res = self.client.get(INDEX_URL)
-
- self.assertTemplateUsed(
- res, 'infrastructure/overview/index.html')
- self.assertTemplateUsed(
- res, 'infrastructure/overview/deployment_progress.html')
-
- def test_deploy_get(self):
- with _mock_plan():
- res = self.client.get(DEPLOY_URL)
- self.assertTemplateUsed(
- res, 'infrastructure/overview/deploy_confirmation.html')
-
- def test_delete_get(self):
- stack = api.heat.Stack(TEST_DATA.heatclient_stacks.first())
-
- with contextlib.nested(
- _mock_plan(),
- patch('tuskar_ui.api.heat.Stack.get_by_plan',
- return_value=stack),
- ):
- res = self.client.get(DELETE_URL)
- self.assertTemplateUsed(
- res, 'infrastructure/overview/undeploy_confirmation.html')
-
- def test_delete_post(self):
- stack = api.heat.Stack(TEST_DATA.heatclient_stacks.first())
-
- with contextlib.nested(
- _mock_plan(),
- patch('tuskar_ui.api.heat.Stack.get_by_plan',
- return_value=stack),
- patch('tuskar_ui.api.heat.Stack.delete',
- return_value=None),
- ):
- res = self.client.post(DELETE_URL)
- self.assertRedirectsNoFollow(res, INDEX_URL)
-
- def test_post_deploy_init_get(self):
- stack = api.heat.Stack(TEST_DATA.heatclient_stacks.first())
-
- with contextlib.nested(
- _mock_plan(),
- patch('tuskar_ui.api.heat.Stack.get_by_plan',
- return_value=stack),
- ):
- res = self.client.get(POST_DEPLOY_INIT_URL)
- self.assertEqual(res.context['form']['admin_email'].value(), '')
- self.assertTemplateUsed(
- res, 'infrastructure/overview/post_deploy_init.html')
-
- def test_post_deploy_init_post(self):
- stack = api.heat.Stack(TEST_DATA.heatclient_stacks.first())
- roles = [api.tuskar.Role(role)
- for role in self.tuskarclient_roles.list()]
-
- data = {
- 'admin_email': "example@example.org",
- 'public_host': '',
- 'region': 'regionOne',
- }
-
- with contextlib.nested(
- _mock_plan(**{'get_role_by_name.side_effect': None,
- 'get_role_by_name.return_value': roles[0]}),
- patch('tuskar_ui.api.heat.Stack.get_by_plan',
- return_value=stack),
- patch('os_cloud_config.keystone.initialize',
- return_value=None),
- patch('os_cloud_config.keystone.setup_endpoints',
- return_value=None),
- patch('os_cloud_config.utils.clients.get_keystone_client',
- return_value='keystone_client'),
- ) as (mock_plan, mock_get_by_plan, mock_initialize,
- mock_setup_endpoints, mock_get_keystone_client):
- res = self.client.post(POST_DEPLOY_INIT_URL, data)
-
- self.assertNoFormErrors(res)
- self.assertEqual(res.status_code, 302)
- self.assertRedirectsNoFollow(res, INDEX_URL)
-
- mock_initialize.assert_called_once_with(
- '192.0.2.23', None, 'example@example.org', None, ssl=None,
- region='regionOne', user='heat-admin', public=None,
- pki_setup=False)
- mock_setup_endpoints.assert_called_once_with(
- {'nova': {'password': None},
- 'heat': {'password': None},
- 'ceilometer': {'password': None},
- 'ec2': {'password': None},
- "horizon": {
- 'port': '80',
- 'path': '/',
- 'admin_path': '/admin'},
- 'cinder': {'password': None},
- 'cinderv2': {'password': None},
- 'glance': {'password': None},
- 'swift': {'password': None,
- 'path': '/v1/AUTH_%(tenant_id)s',
- 'admin_path': '/v1'},
- 'novav3': {'password': None},
- 'neutron': {'password': None}},
- os_auth_url=stack.keystone_auth_url,
- client='keystone_client',
- region='regionOne',
- public_host='')
- mock_get_keystone_client.assert_called_once_with(
- 'admin', None, 'admin', stack.keystone_auth_url)
-
- def test_get_role_data(self):
- plan = api.tuskar.Plan(self.tuskarclient_plans.first())
- stack = api.heat.Stack(self.heatclient_stacks.first())
- role = api.tuskar.Role(self.tuskarclient_roles.first())
- stack.resources = lambda *args, **kwargs: []
- ret = views._get_role_data(plan, stack, None, role)
- self.assertEqual(ret, {
- 'deployed_node_count': 0,
- 'deploying_node_count': 0,
- 'error_node_count': 0,
- 'field': '',
- 'finished': False,
- 'icon': 'fa-exclamation',
- 'id': 'role-1',
- 'name': 'Controller',
- 'planned_node_count': 1,
- 'role': role,
- 'status': 'warning',
- 'total_node_count': 0,
- 'waiting_node_count': 0,
- })
-
- def test_validate_plan_empty(self):
- with (
- _mock_plan()
- ) as plan, (
- patch('tuskar_ui.api.node.Node.list', return_value=[])
- ), (
- patch('tuskar_ui.api.flavor.Flavor.list', return_value=[])
- ):
- ret = forms.validate_plan(None, plan)
- for m in ret:
- m['text'] = unicode(m['text'])
- self.assertEqual(ret, [
- {
- 'is_critical': True,
- 'text': u'Define Flavors.',
- 'status': 'pending',
- 'classes': 'fa-square-o text-info',
- }, {
- 'is_critical': True,
- 'text': u'Register Nodes.',
- 'status': 'pending',
- 'classes': 'fa-square-o text-info',
- }, {
- 'status': 'ok',
- 'text': u'Configure Roles.',
- 'classes': 'fa-check-square-o text-success',
- }, {
- 'status': 'ok',
- 'text': u'Global Service Configuration.',
- 'classes': 'fa-check-square-o text-success',
- }, {
- 'status': 'pending',
- 'text': u'Assign roles.',
- 'classes': 'fa-square-o text-info',
- }, {
- 'is_critical': True,
- 'text': u'Controller Role Needed.',
- 'status': 'error',
- 'indent': 1,
- 'classes': 'fa-exclamation-circle text-danger',
- }, {
- 'is_critical': True,
- 'text': u'Compute Role Needed.',
- 'status': 'error',
- 'indent': 1,
- 'classes': 'fa-exclamation-circle text-danger',
- },
- ])
diff --git a/tuskar_ui/infrastructure/overview/urls.py b/tuskar_ui/infrastructure/overview/urls.py
deleted file mode 100644
index 35410d5d..00000000
--- a/tuskar_ui/infrastructure/overview/urls.py
+++ /dev/null
@@ -1,38 +0,0 @@
-# -*- coding: utf8 -*-
-#
-# 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.
-
-from django.conf import urls
-
-from tuskar_ui.infrastructure.overview import views
-
-
-urlpatterns = urls.patterns(
- '',
- urls.url(r'^$', views.IndexView.as_view(), name='index'),
- urls.url(r'^deploy-confirmation$',
- views.DeployConfirmationView.as_view(),
- name='deploy_confirmation'),
- urls.url(r'^undeploy-confirmation$',
- views.UndeployConfirmationView.as_view(),
- name='undeploy_confirmation'),
- urls.url(r'^post-deploy-init$',
- views.PostDeployInitView.as_view(),
- name='post_deploy_init'),
- urls.url(r'^scale-out$',
- views.ScaleOutView.as_view(),
- name='scale_out'),
- urls.url(r'^download-overcloudrc$',
- views.download_overcloudrc_file,
- name='download_overcloudrc'),
-)
diff --git a/tuskar_ui/infrastructure/overview/views.py b/tuskar_ui/infrastructure/overview/views.py
deleted file mode 100644
index 90f9fa37..00000000
--- a/tuskar_ui/infrastructure/overview/views.py
+++ /dev/null
@@ -1,390 +0,0 @@
-# -*- coding: utf8 -*-
-#
-# 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 json
-import logging
-import urlparse
-
-from django.core.urlresolvers import reverse
-from django.core.urlresolvers import reverse_lazy
-from django import http
-from django import shortcuts
-import django.utils.text
-from django.utils.translation import ugettext_lazy as _
-import heatclient
-import horizon.forms
-from horizon import messages
-
-from tuskar_ui import api
-from tuskar_ui.infrastructure.overview import forms
-from tuskar_ui.infrastructure import views
-
-
-INDEX_URL = 'horizon:infrastructure:overview:index'
-
-LOG = logging.getLogger(__name__)
-
-
-def _steps_message(messages):
- total_steps = len(messages)
- completed_steps = len([m for m in messages if not m.get('is_critical')])
- return _("{0} of {1} Steps Completed").format(completed_steps, total_steps)
-
-
-def _get_role_data(plan, stack, form, role):
- """Gathers data about a single deployment role.
-
- Gathers data about a single deployment role from the related Overcloud
- and Role objects, and presents it in the form convenient for use
- from the template.
-
- """
- data = {
- 'id': role.id,
- 'role': role,
- 'name': role.name,
- 'planned_node_count': plan.get_role_node_count(role),
- 'field': form['%s-count' % role.id] if form else '',
- }
-
- if stack:
- resources = stack.resources(role=role, with_joins=True)
- nodes = [r.node for r in resources]
- node_count = len(nodes)
-
- deployed_node_count = 0
- deploying_node_count = 0
- error_node_count = 0
- waiting_node_count = node_count
-
- status = 'warning'
- if nodes:
- deployed_node_count = sum(1 for node in nodes
- if node.instance.status == 'ACTIVE')
- deploying_node_count = sum(1 for node in nodes
- if node.instance.status == 'BUILD')
- error_node_count = sum(1 for node in nodes
- if node.instance.status == 'ERROR')
- waiting_node_count = (node_count - deployed_node_count -
- deploying_node_count - error_node_count)
-
- if error_node_count or 'FAILED' in stack.stack_status:
- status = 'danger'
- elif deployed_node_count == data['planned_node_count']:
- status = 'success'
- else:
- status = 'info'
-
- finished = deployed_node_count == data['planned_node_count']
- if finished:
- icon = 'fa-check'
- elif status in ('danger', 'warning'):
- icon = 'fa-exclamation'
- else:
- icon = 'fa-spinner fa-spin'
-
- data.update({
- 'status': status,
- 'finished': finished,
- 'total_node_count': node_count,
- 'deployed_node_count': deployed_node_count,
- 'deploying_node_count': deploying_node_count,
- 'waiting_node_count': waiting_node_count,
- 'error_node_count': error_node_count,
- 'icon': icon,
- })
-
- # TODO(rdopieralski) get this from ceilometer
- # data['capacity'] = 20
- return data
-
-
-class IndexView(horizon.forms.ModalFormView, views.StackMixin):
- template_name = 'infrastructure/overview/index.html'
- form_class = forms.EditPlan
- success_url = reverse_lazy(INDEX_URL)
-
- def get_progress_update(self, request, data):
- return {
- 'progress': data.get('progress'),
- 'show_last_events': data.get('show_last_events'),
- 'last_events_title': unicode(data.get('last_events_title')),
- 'last_events': [{
- 'event_time': event.event_time,
- 'resource_name': event.resource_name,
- 'resource_status': event.resource_status,
- 'resource_status_reason': event.resource_status_reason,
- } for event in data.get('last_events', [])],
- 'roles': [{
- 'status': role.get('status', 'warning'),
- 'finished': role.get('finished', False),
- 'name': role.get('name', ''),
- 'slug': django.utils.text.slugify(role.get('name', '')),
- 'id': role.get('id', ''),
- 'total_node_count': role.get('node_count', 0),
- 'deployed_node_count': role.get('deployed_node_count', 0),
- 'deploying_node_count': role.get('deploying_node_count', 0),
- 'waiting_node_count': role.get('waiting_node_count', 0),
- 'error_node_count': role.get('error_node_count', 0),
- 'planned_node_count': role.get('planned_node_count', 0),
- 'icon': role.get('icon', ''),
- } for role in data.get('roles', [])],
- }
-
- def get(self, request, *args, **kwargs):
- if request.META.get('HTTP_X_HORIZON_PROGRESS', ''):
- # If it's an AJAX call for progress update, send it.
- data = self.get_data(request, {})
- return http.HttpResponse(
- json.dumps(self.get_progress_update(request, data)),
- content_type='application/json',
- )
- return super(IndexView, self).get(request, *args, **kwargs)
-
- def get_form(self, form_class):
- return form_class(self.request, **self.get_form_kwargs())
-
- def get_context_data(self, *args, **kwargs):
- context = super(IndexView, self).get_context_data(*args, **kwargs)
- context.update(self.get_data(self.request, context))
- return context
-
- def get_data(self, request, context, *args, **kwargs):
- plan = api.tuskar.Plan.get_the_plan(request)
- stack = self.get_stack()
- form = context.get('form')
-
- context['plan'] = plan
- context['stack'] = stack
-
- roles = [_get_role_data(plan, stack, form, role)
- for role in plan.role_list]
- context['roles'] = roles
-
- if stack:
- context['show_last_events'] = True
- failed_events = [e for e in stack.events
- if 'FAILED' in e.resource_status and
- 'aborted' not in e.resource_status_reason][-3:]
-
- if failed_events:
- context['last_events_title'] = _('Last failed events')
- context['last_events'] = failed_events
- else:
- context['last_events_title'] = _('Last event')
- context['last_events'] = [stack.events[0]]
-
- if stack.is_deleting or stack.is_delete_failed:
- # TODO(lsmola) since at this point we don't have total number
- # of nodes we will hack this around, till API can show this
- # information. So it will actually show progress like the total
- # number is 10, or it will show progress of 5%. Ugly, but
- # workable.
- total_num_nodes_count = 10
-
- try:
- resources_count = len(
- stack.resources(with_joins=False))
- except heatclient.exc.HTTPNotFound:
- # Immediately after undeploying has started, heat returns
- # this exception so we can take it as kind of init of
- # undeploying.
- resources_count = total_num_nodes_count
-
- # TODO(lsmola) same as hack above
- total_num_nodes_count = max(
- resources_count, total_num_nodes_count)
-
- context['progress'] = min(95, max(
- 5, 100 * float(resources_count) / total_num_nodes_count))
- elif stack.is_deploying or stack.is_updating:
- total = sum(d['total_node_count'] for d in roles)
- context['progress'] = min(95, max(
- 5, 100 * sum(float(d.get('deployed_node_count', 0))
- for d in roles) / (total or 1)
- ))
- else:
- # stack is active
- if not stack.is_failed:
- context['show_last_events'] = False
- context['progress'] = 100
- controller_role = plan.get_role_by_name("Controller")
- context['admin_password'] = plan.parameter_value(
- controller_role.parameter_prefix + 'AdminPassword')
-
- context['dashboard_urls'] = stack.dashboard_urls
- no_proxy = [urlparse.urlparse(url).hostname
- for url in stack.dashboard_urls]
- context['no_proxy'] = ",".join(no_proxy)
- context['auth_url'] = stack.keystone_auth_url
- else:
- messages = forms.validate_plan(request, plan)
- context['plan_messages'] = messages
- context['plan_invalid'] = any(message.get('is_critical')
- for message in messages)
- context['steps_message'] = _steps_message(messages)
- return context
-
- def post(self, request, *args, **kwargs):
- """If the post comes from ajax, return validation results as json."""
-
- if not request.META.get('HTTP_X_HORIZON_VALIDATE', ''):
- return super(IndexView, self).post(request, *args, **kwargs)
- form_class = self.get_form_class()
- form = self.get_form(form_class)
- if form.is_valid():
- handled = form.handle(self.request, form.cleaned_data)
- else:
- handled = False
- if handled:
- messages = forms.validate_plan(request, form.plan)
- else:
- messages = [{
- 'text': _(u"Error saving the plan."),
- 'is_critical': True,
- }]
- messages.extend({
- 'text': repr(error),
- } for error in form.non_field_errors)
- messages.extend({
- 'text': repr(error),
- } for field in form.fields for error in field.errors)
- # We need to unlazify all the lazy urls and translations.
- return http.HttpResponse(json.dumps({
- 'plan_invalid': any(m.get('is_critical') for m in messages),
- 'steps_message': _steps_message(messages),
- 'messages': [{
- 'text': unicode(m.get('text', '')),
- 'is_critical': m.get('is_critical', False),
- 'indent': m.get('indent', 0),
- 'classes': m['classes'],
- } for m in messages],
- }), content_type='application/json')
-
-
-class DeployConfirmationView(horizon.forms.ModalFormView, views.StackMixin):
- form_class = forms.DeployOvercloud
- template_name = 'infrastructure/overview/deploy_confirmation.html'
- submit_label = _("Deploy")
-
- def get_context_data(self, **kwargs):
- context = super(DeployConfirmationView,
- self).get_context_data(**kwargs)
- plan = api.tuskar.Plan.get_the_plan(self.request)
-
- context['autogenerated_parameters'] = (
- plan.list_generated_parameters(with_prefix=False).keys())
- return context
-
- def get_success_url(self):
- return reverse(INDEX_URL)
-
-
-class UndeployConfirmationView(horizon.forms.ModalFormView, views.StackMixin):
- form_class = forms.UndeployOvercloud
- template_name = 'infrastructure/overview/undeploy_confirmation.html'
- submit_label = _("Undeploy")
-
- def get_success_url(self):
- return reverse(INDEX_URL)
-
- def get_context_data(self, **kwargs):
- context = super(UndeployConfirmationView,
- self).get_context_data(**kwargs)
- context['stack_id'] = self.get_stack().id
- return context
-
- def get_initial(self, **kwargs):
- initial = super(UndeployConfirmationView, self).get_initial(**kwargs)
- initial['stack_id'] = self.get_stack().id
- return initial
-
-
-class PostDeployInitView(horizon.forms.ModalFormView, views.StackMixin):
- form_class = forms.PostDeployInit
- template_name = 'infrastructure/overview/post_deploy_init.html'
- submit_label = _("Initialize")
-
- def get_success_url(self):
- return reverse(INDEX_URL)
-
- def get_context_data(self, **kwargs):
- context = super(PostDeployInitView,
- self).get_context_data(**kwargs)
- context['stack_id'] = self.get_stack().id
- return context
-
- def get_initial(self, **kwargs):
- initial = super(PostDeployInitView, self).get_initial(**kwargs)
- initial['stack_id'] = self.get_stack().id
- initial['admin_email'] = getattr(self.request.user, 'email', '')
- return initial
-
-
-class ScaleOutView(horizon.forms.ModalFormView, views.StackMixin):
- form_class = forms.ScaleOut
- template_name = "infrastructure/overview/scale_out.html"
- submit_label = _("Deploy Changes")
-
- def get_success_url(self):
- return reverse(INDEX_URL)
-
- def get_form(self, form_class):
- return form_class(self.request, **self.get_form_kwargs())
-
- def get_context_data(self, *args, **kwargs):
- context = super(ScaleOutView, self).get_context_data(*args, **kwargs)
- plan = api.tuskar.Plan.get_the_plan(self.request)
- form = context.get('form')
- roles = [_get_role_data(plan, None, form, role)
- for role in plan.role_list]
- context.update({
- 'roles': roles,
- 'plan': plan,
- })
- return context
-
-
-def _get_openrc_credentials(request):
- plan = api.tuskar.Plan.get_the_plan(request)
- stack = api.heat.Stack.get_by_plan(request, plan)
- no_proxy = [urlparse.urlparse(url).hostname
- for url in stack.dashboard_urls]
- controller_role = plan.get_role_by_name("Controller")
- credentials = dict(tenant_name='admin',
- auth_url=stack.keystone_auth_url,
- admin_password=plan.parameter_value(
- controller_role.parameter_prefix + 'AdminPassword'),
- no_proxy=",".join(no_proxy))
- return credentials
-
-
-def download_overcloudrc_file(request):
- template = 'infrastructure/overview/overcloudrc.sh.template'
- try:
- context = _get_openrc_credentials(request)
-
- response = shortcuts.render(request,
- template,
- context,
- content_type="text/plain")
- response['Content-Disposition'] = ('attachment; '
- 'filename="overcloudrc"')
- response['Content-Length'] = str(len(response.content))
- return response
-
- except Exception as e:
- LOG.exception("Exception in DownloadOvercloudrcForm.")
- messages.error(request, _('Error Downloading RC File: %s') % e)
- return shortcuts.redirect(request.build_absolute_uri())