Implementing multi-region support.
* Multiple regions are optionally setup in local_settings.py * fixes bug 920761 Change-Id: I7a5ef182a0fd00369414491a88446f7dd94ae679
This commit is contained in:
parent
fe0a70ead2
commit
ff7c1ceb85
@ -34,6 +34,11 @@ from horizon.api.deprecated import check_openstackx
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _get_endpoint_url(request):
|
||||
return request.session.get('region_endpoint',
|
||||
getattr(settings, 'OPENSTACK_KEYSTONE_URL'))
|
||||
|
||||
|
||||
class Tenant(APIResourceWrapper):
|
||||
"""Simple wrapper around keystoneclient.tenants.Tenant"""
|
||||
_attrs = ['id', 'description', 'enabled', 'name']
|
||||
@ -93,7 +98,7 @@ def keystoneclient(request, username=None, password=None, tenant_id=None,
|
||||
password=password,
|
||||
tenant_id=tenant_id or user.tenant_id,
|
||||
token=token_id or user.token,
|
||||
auth_url=settings.OPENSTACK_KEYSTONE_URL,
|
||||
auth_url=_get_endpoint_url(request),
|
||||
endpoint=endpoint)
|
||||
request._keystone = conn
|
||||
|
||||
@ -110,7 +115,7 @@ def keystoneclient(request, username=None, password=None, tenant_id=None,
|
||||
endpoint = catalog.url_for(service_type='identity',
|
||||
endpoint_type='publicURL')
|
||||
else:
|
||||
endpoint = settings.OPENSTACK_KEYSTONE_URL
|
||||
endpoint = _get_endpoint_url(request)
|
||||
conn.management_url = endpoint
|
||||
|
||||
return conn
|
||||
@ -144,7 +149,7 @@ def tenant_update(request, tenant_id, tenant_name, description, enabled):
|
||||
def tenant_list_for_token(request, token, endpoint_type=None):
|
||||
c = keystoneclient(request,
|
||||
token_id=token,
|
||||
endpoint=settings.OPENSTACK_KEYSTONE_URL,
|
||||
endpoint=_get_endpoint_url(request),
|
||||
endpoint_type=endpoint_type)
|
||||
return [Tenant(t) for t in c.tenants.list()]
|
||||
|
||||
@ -160,7 +165,7 @@ def token_create(request, tenant, username, password):
|
||||
username=username,
|
||||
password=password,
|
||||
tenant_id=tenant,
|
||||
endpoint=settings.OPENSTACK_KEYSTONE_URL)
|
||||
endpoint=_get_endpoint_url(request))
|
||||
token = c.tokens.authenticate(username=username,
|
||||
password=password,
|
||||
tenant_id=tenant)
|
||||
@ -174,8 +179,10 @@ def token_create_scoped(request, tenant, token):
|
||||
'''
|
||||
if hasattr(request, '_keystone'):
|
||||
del request._keystone
|
||||
c = keystoneclient(request, tenant_id=tenant, token_id=token,
|
||||
endpoint=settings.OPENSTACK_KEYSTONE_URL)
|
||||
c = keystoneclient(request,
|
||||
tenant_id=tenant,
|
||||
token_id=token,
|
||||
endpoint=_get_endpoint_url(request))
|
||||
raw_token = c.tokens.authenticate(tenant_id=tenant,
|
||||
token=token,
|
||||
return_raw=True)
|
||||
@ -247,8 +254,8 @@ def _get_roleref(request, user_id, tenant_id, role):
|
||||
def role_add_for_tenant_user(request, tenant_id, user_id, role):
|
||||
role = _get_role(request, role)
|
||||
return keystoneclient(request).roles.add_user_to_tenant(tenant_id,
|
||||
user_id,
|
||||
role.id)
|
||||
user_id,
|
||||
role.id)
|
||||
|
||||
|
||||
def role_delete_for_tenant_user(request, tenant_id, user_id, role):
|
||||
|
@ -74,4 +74,11 @@ def horizon(request):
|
||||
# supporting keystone integration.
|
||||
context['network_configured'] = getattr(settings, 'QUANTUM_ENABLED', None)
|
||||
|
||||
# Region context/support
|
||||
available_regions = getattr(settings, 'AVAILABLE_REGIONS', None)
|
||||
regions = {'support': available_regions > 1,
|
||||
'endpoint': request.session.get('region_endpoint'),
|
||||
'name': request.session.get('region_name')}
|
||||
context['region'] = regions
|
||||
|
||||
return context
|
||||
|
0
horizon/horizon/dashboards/regions/__init__.py
Normal file
0
horizon/horizon/dashboards/regions/__init__.py
Normal file
@ -142,6 +142,7 @@ class TestCase(django_test.TestCase):
|
||||
service_catalog=self.TEST_SERVICE_CATALOG,
|
||||
authorized_tenants=tenants)
|
||||
self.request = http.HttpRequest()
|
||||
self.request.session = self.client._session()
|
||||
middleware.HorizonMiddleware().process_request(self.request)
|
||||
|
||||
def tearDown(self):
|
||||
|
@ -55,6 +55,7 @@ class AuthViewTests(test.BaseViewTests):
|
||||
TOKEN_ID = 1
|
||||
|
||||
form_data = {'method': 'Login',
|
||||
'region': 'http://localhost:5000/v2.0,local',
|
||||
'password': self.PASSWORD,
|
||||
'username': self.TEST_USER}
|
||||
|
||||
@ -94,6 +95,7 @@ class AuthViewTests(test.BaseViewTests):
|
||||
TOKEN_ID = 1
|
||||
|
||||
form_data = {'method': 'Login',
|
||||
'region': 'http://localhost:5000/v2.0,local',
|
||||
'password': self.PASSWORD,
|
||||
'username': self.TEST_USER}
|
||||
|
||||
@ -125,9 +127,7 @@ class AuthViewTests(test.BaseViewTests):
|
||||
aToken.id).AndReturn(bToken)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.post(reverse('horizon:auth_login'), form_data)
|
||||
|
||||
self.assertRedirectsNoFollow(res, DASH_INDEX_URL)
|
||||
|
||||
def test_login_invalid_credentials(self):
|
||||
@ -139,6 +139,7 @@ class AuthViewTests(test.BaseViewTests):
|
||||
self.mox.ReplayAll()
|
||||
|
||||
form_data = {'method': 'Login',
|
||||
'region': 'http://localhost:5000/v2.0,local',
|
||||
'password': self.PASSWORD,
|
||||
'username': self.TEST_USER}
|
||||
res = self.client.post(reverse('horizon:auth_login'),
|
||||
@ -158,6 +159,7 @@ class AuthViewTests(test.BaseViewTests):
|
||||
self.mox.ReplayAll()
|
||||
|
||||
form_data = {'method': 'Login',
|
||||
'region': 'http://localhost:5000/v2.0,local',
|
||||
'password': self.PASSWORD,
|
||||
'username': self.TEST_USER}
|
||||
res = self.client.post(reverse('horizon:auth_login'), form_data)
|
||||
@ -205,6 +207,7 @@ class AuthViewTests(test.BaseViewTests):
|
||||
self.mox.ReplayAll()
|
||||
|
||||
form_data = {'method': 'LoginWithTenant',
|
||||
'region': 'http://localhost:5000/v2.0,local',
|
||||
'password': self.PASSWORD,
|
||||
'tenant': NEW_TENANT_ID,
|
||||
'username': self.TEST_USER}
|
||||
|
@ -100,6 +100,11 @@ SWIFT_USER = 'tester'
|
||||
SWIFT_PASS = 'testing'
|
||||
SWIFT_AUTHURL = 'http://swift/swiftapi/v1.0'
|
||||
|
||||
AVAILABLE_REGIONS = [
|
||||
('local', 'http://localhost:5000/v2.0'),
|
||||
('remote', 'http://remote:5000/v2.0'),
|
||||
]
|
||||
|
||||
OPENSTACK_ADDRESS = "localhost"
|
||||
OPENSTACK_ADMIN_TOKEN = "openstack"
|
||||
OPENSTACK_KEYSTONE_URL = "http://%s:5000/v2.0" % OPENSTACK_ADDRESS
|
||||
|
@ -21,6 +21,7 @@
|
||||
import logging
|
||||
|
||||
from django import shortcuts
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
import horizon
|
||||
|
@ -25,6 +25,7 @@ Forms used for Horizon's auth mechanisms.
|
||||
import logging
|
||||
|
||||
from django import shortcuts
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.utils.translation import ugettext as _
|
||||
from keystoneclient import exceptions as keystone_exceptions
|
||||
@ -49,6 +50,16 @@ def _set_session_data(request, token):
|
||||
request.session['roles'] = token.user['roles']
|
||||
|
||||
|
||||
def _regions_supported():
|
||||
if len(getattr(settings, 'AVAILABLE_REGIONS', [])) > 1:
|
||||
return True
|
||||
|
||||
|
||||
region_field = forms.ChoiceField(widget=forms.Select,
|
||||
choices=[('%s,%s' % (region[1], region[0]), region[0])
|
||||
for region in getattr(settings, 'AVAILABLE_REGIONS', [])])
|
||||
|
||||
|
||||
class Login(forms.SelfHandlingForm):
|
||||
""" Form used for logging in a user.
|
||||
|
||||
@ -58,11 +69,18 @@ class Login(forms.SelfHandlingForm):
|
||||
|
||||
Subclass of :class:`~horizon.forms.SelfHandlingForm`.
|
||||
"""
|
||||
if _regions_supported():
|
||||
region = region_field
|
||||
username = forms.CharField(max_length="20", label=_("User Name"))
|
||||
password = forms.CharField(max_length="20", label=_("Password"),
|
||||
widget=forms.PasswordInput(render_value=False))
|
||||
|
||||
def handle(self, request, data):
|
||||
region = data.get('region', '').split(',')
|
||||
if len(region) > 1:
|
||||
request.session['region_endpoint'] = region[0]
|
||||
request.session['region_name'] = region[1]
|
||||
|
||||
if data.get('tenant', None):
|
||||
try:
|
||||
token = api.token_create(request,
|
||||
@ -137,7 +155,7 @@ class Login(forms.SelfHandlingForm):
|
||||
|
||||
_set_session_data(request, token)
|
||||
user = users.get_user_from_request(request)
|
||||
return shortcuts.redirect(base.Horizon.get_user_home(user))
|
||||
return shortcuts.redirect(base.Horizon.get_user_home(user))
|
||||
|
||||
|
||||
class LoginWithTenant(Login):
|
||||
@ -145,6 +163,8 @@ class LoginWithTenant(Login):
|
||||
Exactly like :class:`.Login` but includes the tenant id as a field
|
||||
so that the process of choosing a default tenant is bypassed.
|
||||
"""
|
||||
if _regions_supported():
|
||||
region = region_field
|
||||
username = forms.CharField(max_length="20",
|
||||
widget=forms.TextInput(attrs={'readonly': 'readonly'}))
|
||||
tenant = forms.CharField(widget=forms.HiddenInput())
|
||||
|
@ -276,6 +276,7 @@ small {
|
||||
padding-right: 25px;
|
||||
padding-top: 25px;
|
||||
color: #888;
|
||||
right: 145px;
|
||||
}
|
||||
|
||||
#user_info a {
|
||||
@ -901,7 +902,41 @@ a.view_full {
|
||||
border-bottom: solid 1px #E1E1E1;
|
||||
background-image: url(/static/dashboard/images/drop_arrow.png);
|
||||
}
|
||||
|
||||
form div.clearfix.error {
|
||||
width: 330px;
|
||||
padding: 10px 10px;
|
||||
}
|
||||
|
||||
/* Region selector in header */
|
||||
|
||||
#region_selector {
|
||||
position: absolute;
|
||||
z-index: 9999;
|
||||
right: 0;
|
||||
top: 24px;
|
||||
}
|
||||
|
||||
#region_selector a { margin-left: 0; }
|
||||
|
||||
#region_selector ul{
|
||||
float: left;
|
||||
margin-left: 5px;
|
||||
padding-right: 21px;
|
||||
width: 125px;
|
||||
}
|
||||
#region_selector ul:hover a { display: block; }
|
||||
|
||||
#region_selector li a{
|
||||
padding: 3px 3px 3px 5px;
|
||||
display: none;
|
||||
background: #E1E1E1;
|
||||
margin-top: -10px;
|
||||
}
|
||||
|
||||
#region_selector li:first-child p{
|
||||
background: #EDEDED url(/static/dashboard/images/drop_arrow.png) no-repeat 106px 9px !important;
|
||||
display: block;
|
||||
border: 1px solid #e1e1e1;
|
||||
padding: 5px;
|
||||
}
|
||||
|
@ -37,7 +37,14 @@
|
||||
<a href="{% url horizon:settings:user:index %}">{% trans "Settings" %}</a>
|
||||
<a href="{% url horizon:auth_logout %}">{% trans "Sign Out" %}</a>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
<div id="region_selector">
|
||||
{% if region.support %}
|
||||
<ul>
|
||||
<li class="odd"><p>{{ region.name }}</p></li>
|
||||
<li><a href="{% url horizon:auth_logout %}">Change Regions</a></li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
|
@ -36,6 +36,14 @@ HORIZON_CONFIG = {
|
||||
'user_home': 'dashboard.views.user_home',
|
||||
}
|
||||
|
||||
# For multiple regions uncomment this configuration, and add (title, endpoint).
|
||||
# AVAILABLE_REGIONS = [
|
||||
# ('cluster1', 'http://cluster1.com:5000/v2.0'),
|
||||
# ('cluster2', 'http://cluster2.com:5000/v2.0'),
|
||||
# ]
|
||||
|
||||
# FIXME: This is only here so quantum still works. It must be refactored to
|
||||
# work with multiple regions.
|
||||
OPENSTACK_HOST = "127.0.0.1"
|
||||
OPENSTACK_KEYSTONE_URL = "http://%s:5000/v2.0" % OPENSTACK_HOST
|
||||
# FIXME: this is only needed until keystone fixes its GET /tenants call
|
||||
|
Loading…
x
Reference in New Issue
Block a user