Making init keystone work
Calling os-cloud-config to init keystone. Unmocking part of Heat calls, relation to plan will not work unless we actually deploy a template created by Tuskar-API. So mocking a relation and returning always the first plan from plan-list. Depends on https://review.openstack.org/#/c/113883/ Change-Id: I0a17eada05265d2bc4cc1ef6a611936522e9c6e9
This commit is contained in:
parent
f430370d8d
commit
4301fee899
@ -105,7 +105,7 @@ class Stack(base.APIResourceWrapper):
|
||||
are none
|
||||
:rtype: list of tuskar_ui.api.heat.Stack
|
||||
"""
|
||||
stacks = mock_heat.Stack.list()
|
||||
stacks, has_more_data, has_prev_data = heat.stacks_list(request)
|
||||
return [cls(stack, request=request) for stack in stacks]
|
||||
|
||||
@classmethod
|
||||
@ -118,7 +118,7 @@ class Stack(base.APIResourceWrapper):
|
||||
found
|
||||
:rtype: tuskar_ui.api.heat.Stack or None
|
||||
"""
|
||||
return cls(mock_heat.Stack.get(stack_id))
|
||||
return cls(heat.stack_get(request, stack_id), request=request)
|
||||
|
||||
@classmethod
|
||||
@handle_errors(_("Unable to retrieve stack"))
|
||||
@ -130,9 +130,18 @@ class Stack(base.APIResourceWrapper):
|
||||
found
|
||||
:rtype: tuskar_ui.api.heat.Stack or None
|
||||
"""
|
||||
for stack in Stack.list(request):
|
||||
if stack.plan and (stack.plan.id == plan.id):
|
||||
return stack
|
||||
# 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"), [])
|
||||
@ -221,9 +230,27 @@ class Stack(base.APIResourceWrapper):
|
||||
exists as well; None otherwise
|
||||
:rtype: tuskar_ui.api.tuskar.OvercloudPlan
|
||||
"""
|
||||
if 'plan_id' in self.parameters:
|
||||
return tuskar.OvercloudPlan.get(self._request,
|
||||
self.parameters['plan_id'])
|
||||
# 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.OvercloudPlan.get(self._request,
|
||||
# self.parameters['plan_id'])
|
||||
try:
|
||||
plan = tuskar.OvercloudPlan.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):
|
||||
@ -290,22 +317,22 @@ class Stack(base.APIResourceWrapper):
|
||||
return getattr(self, 'outputs', [])
|
||||
|
||||
@cached_property
|
||||
def keystone_ip(self):
|
||||
def keystone_auth_url(self):
|
||||
for output in self.stack_outputs:
|
||||
if output['output_key'] == 'KeystoneURL':
|
||||
return urlparse.urlparse(output['output_value']).hostname
|
||||
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):
|
||||
for output in self.stack_outputs:
|
||||
if output['output_key'] == 'KeystoneURL':
|
||||
break
|
||||
else:
|
||||
return None
|
||||
try:
|
||||
return overcloud_keystoneclient(
|
||||
self._request,
|
||||
output['output_value'],
|
||||
self.keystone_auth_url,
|
||||
self.plan.parameter_value('AdminPassword'))
|
||||
except Exception:
|
||||
LOG.debug('Unable to connect to overcloud keystone.')
|
||||
|
@ -19,6 +19,7 @@ from django.core import urlresolvers
|
||||
|
||||
from mock import patch, call # noqa
|
||||
|
||||
from horizon import exceptions as horizon_exceptions
|
||||
from openstack_dashboard.test import helpers
|
||||
from openstack_dashboard.test.test_data import utils
|
||||
from tuskar_ui import api
|
||||
@ -44,6 +45,10 @@ class NodesTests(test.BaseAdminViewTests, helpers.APITestCase):
|
||||
def _raise_tuskar_exception(self, request, *args, **kwargs):
|
||||
raise self.exceptions.tuskar
|
||||
|
||||
@handle_errors("Error!", [])
|
||||
def _raise_horizon_exception_not_found(self, request, *args, **kwargs):
|
||||
raise horizon_exceptions.NotFound
|
||||
|
||||
def test_index_get(self):
|
||||
|
||||
with patch('tuskar_ui.api.node.Node', **{
|
||||
@ -90,7 +95,12 @@ class NodesTests(test.BaseAdminViewTests, helpers.APITestCase):
|
||||
'spec_set': ['image_get'],
|
||||
'image_get.return_value': image,
|
||||
}),
|
||||
) as (_OvercloudRole, Node, _nova, _glance):
|
||||
patch('tuskar_ui.api.heat.Resource', **{
|
||||
'spec_set': ['get_by_node'], # Only allow these attributes
|
||||
'get_by_node.side_effect': (
|
||||
self._raise_horizon_exception_not_found),
|
||||
}),
|
||||
) as (_OvercloudRole, Node, _nova, _glance, _resource):
|
||||
res = self.client.get(INDEX_URL + '?tab=nodes__registered')
|
||||
# FIXME(lsmola) horrible count, optimize
|
||||
self.assertEqual(Node.list.call_count, 6)
|
||||
@ -240,14 +250,21 @@ class NodesTests(test.BaseAdminViewTests, helpers.APITestCase):
|
||||
def test_node_detail(self):
|
||||
node = api.node.Node(self.ironicclient_nodes.list()[0])
|
||||
|
||||
with patch('tuskar_ui.api.node.Node', **{
|
||||
'spec_set': ['get'], # Only allow these attributes
|
||||
'get.return_value': node,
|
||||
}) as mock:
|
||||
with contextlib.nested(
|
||||
patch('tuskar_ui.api.node.Node', **{
|
||||
'spec_set': ['get'], # Only allow these attributes
|
||||
'get.return_value': node,
|
||||
}),
|
||||
patch('tuskar_ui.api.heat.Resource', **{
|
||||
'spec_set': ['get_by_node'], # Only allow these attributes
|
||||
'get_by_node.side_effect': (
|
||||
self._raise_horizon_exception_not_found),
|
||||
}),
|
||||
) as (mock_node, mock_heat):
|
||||
res = self.client.get(
|
||||
urlresolvers.reverse(DETAIL_VIEW, args=(node.uuid,))
|
||||
)
|
||||
self.assertEqual(mock.get.call_count, 1)
|
||||
self.assertEqual(mock_node.get.call_count, 1)
|
||||
|
||||
self.assertTemplateUsed(res, 'infrastructure/nodes/details.html')
|
||||
self.assertEqual(res.context['node'], node)
|
||||
|
@ -19,6 +19,7 @@ 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_setup
|
||||
|
||||
from tuskar_ui import api
|
||||
import tuskar_ui.api.heat
|
||||
@ -92,3 +93,64 @@ class UndeployOvercloud(horizon.forms.SelfHandlingForm):
|
||||
msg = _('Undeployment in progress.')
|
||||
horizon.messages.success(request, msg)
|
||||
return True
|
||||
|
||||
|
||||
class PostDeployInit(horizon.forms.SelfHandlingForm):
|
||||
def build_endpoints(self, plan):
|
||||
return {
|
||||
"ceilometer": {
|
||||
"password": plan.parameter_value('CeilometerPassword')},
|
||||
"cinder": {
|
||||
"password": plan.parameter_value('CinderPassword')},
|
||||
"ec2": {
|
||||
"password": plan.parameter_value('GlancePassword')},
|
||||
"glance": {
|
||||
"password": plan.parameter_value('GlancePassword')},
|
||||
"heat": {
|
||||
"password": plan.parameter_value('HeatPassword')},
|
||||
"neutron": {
|
||||
"password": plan.parameter_value('NeutronPassword')},
|
||||
"nova": {
|
||||
"password": plan.parameter_value('NovaPassword')},
|
||||
"novav3": {
|
||||
"password": plan.parameter_value('NovaPassword')},
|
||||
"swift": {
|
||||
"password": plan.parameter_value('SwiftPassword')},
|
||||
"horizon": {}}
|
||||
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
plan = api.tuskar.OvercloudPlan.get_the_plan(request)
|
||||
stack = api.heat.Stack.get_by_plan(self.request, plan)
|
||||
|
||||
admin_token = plan.parameter_value('AdminToken')
|
||||
admin_password = plan.parameter_value('AdminPassword')
|
||||
admin_email = 'example@example.org'
|
||||
auth_ip = stack.keystone_ip
|
||||
auth_url = stack.keystone_auth_url
|
||||
auth_tenant = 'admin'
|
||||
auth_user = 'admin'
|
||||
|
||||
# do the keystone init
|
||||
keystone_setup.initialize(
|
||||
auth_ip, admin_token, admin_email, admin_password,
|
||||
region='regionOne', ssl=None, public=None, user='heat-admin')
|
||||
|
||||
# do the setup endpoints
|
||||
keystone_setup.setup_endpoints(
|
||||
self.build_endpoints(plan), public_host=None, region=None,
|
||||
os_username=auth_user, os_password=admin_password,
|
||||
os_tenant_name=auth_tenant, os_auth_url=auth_url)
|
||||
|
||||
# do the neutron init
|
||||
# TODO(lsmola) neutron needs to be prepared in os-cloud-config
|
||||
|
||||
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
|
||||
|
@ -49,8 +49,27 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if not dashboard_urls and not stack.is_deploying and not stack.is_failed %}
|
||||
Your OpenStack cloud is deployed, but it needs to be initialized.
|
||||
{% if stack.is_deployed and not stack.is_initialized %}
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div class="alert alert-info">
|
||||
<div class="row">
|
||||
<div class="col-xs-2">
|
||||
<span class="text-success" style="font-size: x-large; vertical-align:middle">
|
||||
<i class="glyphicon glyphicon-warning-sign"></i>
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-xs-10">
|
||||
<p>{% trans "Your OpenStack cloud is deployed but it needs to get initialized in order to get live." %}</p>
|
||||
<a href="{% url 'horizon:infrastructure:overview:post_deploy_init' %}"
|
||||
class="btn btn-primary ajax-modal">
|
||||
{% trans "Initialize" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
Deployment is live
|
||||
<div class="widget">
|
||||
@ -65,6 +84,8 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
<hr>
|
||||
|
||||
|
||||
<a href="{% url 'horizon:infrastructure:overview:undeploy_confirmation' %}"
|
||||
class="btn btn-danger ajax-modal">
|
||||
<i class="glyphicon glyphicon-fire"></i>
|
||||
|
@ -0,0 +1,26 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
{% load url from future %}
|
||||
|
||||
{% block form_id %}post_deploy_init_form{% endblock %}
|
||||
{% block form_action %}{% url 'horizon:infrastructure:overview:post_deploy_init' %}{% endblock %}
|
||||
|
||||
{% block modal_id %}provision_modal{% endblock %}
|
||||
{% block modal-header %}{% trans "Initialize Overcloud" %}{% endblock %}
|
||||
|
||||
{% block modal-body %}
|
||||
<div>
|
||||
<p>{% trans "Your OpenStack cloud is deployed but it needs to get initialized in order to get live." %}
|
||||
</p>
|
||||
<p>{% trans "This operation can't be undone, are you sure?" %}
|
||||
</p>
|
||||
<fieldset>
|
||||
{% include "horizon/common/_form_fields.html" %}
|
||||
</fieldset>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<input class="btn btn-primary" type="submit" value="{% trans "Initialize" %}" />
|
||||
<a href="{% url 'horizon:infrastructure:overview:index' %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
|
||||
{% endblock %}
|
@ -0,0 +1,11 @@
|
||||
{% extends 'infrastructure/base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Initialize" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Initialize Overcloud") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block infrastructure_main %}
|
||||
{% include "infrastructure/overview/_post_deploy_init.html" %}
|
||||
{% endblock %}
|
@ -30,6 +30,8 @@ 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)
|
||||
@ -162,3 +164,20 @@ class OverviewTests(test.BaseAdminViewTests):
|
||||
):
|
||||
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.assertTemplateUsed(
|
||||
res, 'infrastructure/overview/post_deploy_init.html')
|
||||
|
||||
def test_post_deploy_init_post(self):
|
||||
# TODO(lsmola) add this test once os-cloud-config changes are
|
||||
# released, otherwise it will throw error in the Gate
|
||||
pass
|
||||
|
@ -26,4 +26,7 @@ urlpatterns = urls.patterns(
|
||||
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'),
|
||||
)
|
||||
|
@ -173,3 +173,22 @@ class UndeployConfirmationView(horizon.forms.ModalFormView, StackMixin):
|
||||
initial = super(UndeployConfirmationView, self).get_initial(**kwargs)
|
||||
initial['stack_id'] = self.get_stack().id
|
||||
return initial
|
||||
|
||||
|
||||
class PostDeployInitView(horizon.forms.ModalFormView, StackMixin):
|
||||
form_class = forms.PostDeployInit
|
||||
template_name = 'infrastructure/overview/post_deploy_init.html'
|
||||
|
||||
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
|
||||
return initial
|
||||
|
@ -27,16 +27,20 @@ class HeatAPITests(test.APITestCase):
|
||||
def test_stack_list(self):
|
||||
stacks = self.heatclient_stacks.list()
|
||||
|
||||
with patch('tuskar_ui.test.test_driver.heat_driver.Stack.list',
|
||||
return_value=stacks):
|
||||
ret_val = api.heat.Stack.list(self.request)
|
||||
for stack in ret_val:
|
||||
with patch('openstack_dashboard.api.heat.stacks_list',
|
||||
return_value=(stacks, None, None)):
|
||||
stacks = api.heat.Stack.list(self.request)
|
||||
for stack in stacks:
|
||||
self.assertIsInstance(stack, api.heat.Stack)
|
||||
self.assertEqual(1, len(ret_val))
|
||||
self.assertEqual(1, len(stacks))
|
||||
|
||||
def test_stack_get(self):
|
||||
stack = self.heatclient_stacks.first()
|
||||
ret_val = api.heat.Stack.get(self.request, stack.id)
|
||||
|
||||
with patch('openstack_dashboard.api.heat.stack_get',
|
||||
return_value=stack):
|
||||
ret_val = api.heat.Stack.get(self.request, stack.id)
|
||||
|
||||
self.assertIsInstance(ret_val, api.heat.Stack)
|
||||
|
||||
def test_stack_plan(self):
|
||||
@ -44,8 +48,8 @@ class HeatAPITests(test.APITestCase):
|
||||
self.request)
|
||||
plan = self.tuskarclient_plans.first()
|
||||
|
||||
with patch('tuskarclient.v2.plans.PlanManager.get',
|
||||
return_value=plan):
|
||||
with patch('tuskarclient.v2.plans.PlanManager.list',
|
||||
return_value=[plan]):
|
||||
ret_val = stack.plan
|
||||
self.assertIsInstance(ret_val, api.tuskar.OvercloudPlan)
|
||||
|
||||
|
@ -141,7 +141,25 @@ def data(TEST):
|
||||
'label': 'Admin Password',
|
||||
'description': 'Admin password',
|
||||
'hidden': 'false',
|
||||
'value': 'unset',
|
||||
'value': '5ba3a69c95c668daf84c2f103ebec82d273a4897',
|
||||
}, {
|
||||
'name': 'AdminToken',
|
||||
'label': 'Admin Token',
|
||||
'description': 'Admin Token',
|
||||
'hidden': 'false',
|
||||
'value': 'aa61677c0a270880e99293c148cefee4000b2259',
|
||||
}, {
|
||||
'name': 'GlancePassword',
|
||||
'label': 'Glance Password',
|
||||
'description': 'Glance Password',
|
||||
'hidden': 'false',
|
||||
'value': '16b4aaa3e056d07f796a93afb6010487b7b617e7',
|
||||
}, {
|
||||
'name': 'NovaPassword',
|
||||
'label': 'Nova Password',
|
||||
'description': 'Nova Password',
|
||||
'hidden': 'false',
|
||||
'value': '67d8090ff40c0c400b08ff558233091402afc9c5',
|
||||
}],
|
||||
})
|
||||
TEST.tuskarclient_plans.add(plan_1)
|
||||
|
Loading…
x
Reference in New Issue
Block a user