Adds API Access information to Access & Security panel.
What this does: * Makes the Access & Security panel use tabs for each of the tables instead of trying to shove them all inline. * Adds an "API Access" tab to the above set of tabs. * Combines the features of the API Endpoints table, the EC2 Credentials download and the OpenRC file download into the API Access tab mentioned above. * Uses the service "type" instead of "name" in the Endpoints table to be nicer about service API abstraction. Fixes bug 1065671 and fixes bug 1120627. Change-Id: Iccc65b32d37dc97a96538443cf8c5c08fcea7250
This commit is contained in:
parent
216d566c9c
commit
cd0a959523
@ -1,42 +0,0 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Copyright 2012 Nebula, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Methods and interface objects used to interact with external APIs.
|
||||
|
||||
API method calls return objects that are in many cases objects with
|
||||
attributes that are direct maps to the data returned from the API http call.
|
||||
Unfortunately, these objects are also often constructed dynamically, making
|
||||
it difficult to know what data is available from the API object. Because of
|
||||
this, all API calls should wrap their returned object in one defined here,
|
||||
using only explicitly defined atributes and/or methods.
|
||||
|
||||
In other words, Horizon developers not working on openstack_dashboard.api
|
||||
shouldn't need to understand the finer details of APIs for
|
||||
Keystone/Nova/Glance/Swift et. al.
|
||||
"""
|
||||
from openstack_dashboard.api import base
|
||||
from openstack_dashboard.api import cinder
|
||||
from openstack_dashboard.api import glance
|
||||
from openstack_dashboard.api import keystone
|
||||
from openstack_dashboard.api import network
|
||||
from openstack_dashboard.api import nova
|
||||
from openstack_dashboard.api import quantum
|
||||
from openstack_dashboard.api import swift
|
@ -15,6 +15,7 @@
|
||||
# under the License.
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.template.defaultfilters import title
|
||||
|
||||
from horizon import tables
|
||||
|
||||
@ -23,11 +24,40 @@ def get_endpoint(service):
|
||||
return service.endpoints[0]['publicURL']
|
||||
|
||||
|
||||
def pretty_service_names(name):
|
||||
name = name.replace('-', ' ')
|
||||
if name in ['ec2', 's3']:
|
||||
name = name.upper()
|
||||
else:
|
||||
name = title(name)
|
||||
return name
|
||||
|
||||
|
||||
class DownloadEC2(tables.LinkAction):
|
||||
name = "download_ec2"
|
||||
verbose_name = _("Download EC2 Credentials")
|
||||
verbose_name_plural = _("Download EC2 Credentials")
|
||||
classes = ("btn-download",)
|
||||
url = "horizon:project:access_and_security:api_access:ec2"
|
||||
|
||||
|
||||
class DownloadOpenRC(tables.LinkAction):
|
||||
name = "download_openrc"
|
||||
verbose_name = _("Download OpenStack RC File")
|
||||
verbose_name_plural = _("Download OpenStack RC File")
|
||||
classes = ("btn-download",)
|
||||
url = "horizon:project:access_and_security:api_access:openrc"
|
||||
|
||||
|
||||
class EndpointsTable(tables.DataTable):
|
||||
api_name = tables.Column('name', verbose_name=_("Service Name"))
|
||||
api_name = tables.Column('type',
|
||||
verbose_name=_("Service"),
|
||||
filters=(pretty_service_names,))
|
||||
api_endpoint = tables.Column(get_endpoint,
|
||||
verbose_name=_("Service Endpoint"))
|
||||
|
||||
class Meta:
|
||||
name = "endpoints"
|
||||
verbose_name = _("API Endpoints")
|
||||
multi_select = False
|
||||
table_actions = (DownloadOpenRC, DownloadEC2,)
|
@ -21,35 +21,21 @@ from mox import IsA
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.test import helpers as test
|
||||
from .forms import DownloadX509Credentials
|
||||
|
||||
|
||||
INDEX_URL = reverse("horizon:settings:ec2:index")
|
||||
EC2_URL = reverse("horizon:project:access_and_security:api_access:ec2")
|
||||
|
||||
|
||||
class EC2SettingsTest(test.TestCase):
|
||||
class APIAccessTests(test.TestCase):
|
||||
def test_ec2_download_view(self):
|
||||
creds = self.ec2.first()
|
||||
cert = self.certs.first()
|
||||
|
||||
self.mox.StubOutWithMock(api.keystone, "tenant_list")
|
||||
self.mox.StubOutWithMock(api.keystone, "token_create_scoped")
|
||||
self.mox.StubOutWithMock(api.keystone, "list_ec2_credentials")
|
||||
self.mox.StubOutWithMock(api.nova, "get_x509_credentials")
|
||||
self.mox.StubOutWithMock(api.nova, "get_x509_root_certificate")
|
||||
self.mox.StubOutWithMock(api.keystone, "create_ec2_credentials")
|
||||
|
||||
# GET request
|
||||
api.keystone.tenant_list(IsA(HttpRequest)) \
|
||||
.AndReturn(self.tenants.list())
|
||||
|
||||
# POST request
|
||||
api.keystone.token_create_scoped(IsA(HttpRequest),
|
||||
self.tenant.id,
|
||||
IsA(str)) \
|
||||
.AndReturn(self.tokens.scoped_token)
|
||||
api.keystone.tenant_list(IsA(HttpRequest)) \
|
||||
.AndReturn(self.tenants.list())
|
||||
api.keystone.list_ec2_credentials(IsA(HttpRequest), self.user.id) \
|
||||
.AndReturn([])
|
||||
api.nova.get_x509_credentials(IsA(HttpRequest)).AndReturn(cert)
|
||||
@ -60,11 +46,6 @@ class EC2SettingsTest(test.TestCase):
|
||||
self.tenant.id).AndReturn(creds)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(INDEX_URL)
|
||||
self.assertNoMessages()
|
||||
res = self.client.get(EC2_URL)
|
||||
self.assertEqual(res.status_code, 200)
|
||||
|
||||
data = {'method': DownloadX509Credentials.__name__,
|
||||
'tenant': self.tenant.id}
|
||||
res = self.client.post(INDEX_URL, data)
|
||||
self.assertEqual(res['content-type'], 'application/zip')
|
@ -1,5 +1,9 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Copyright 2012 Nebula, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
@ -16,8 +20,10 @@
|
||||
|
||||
from django.conf.urls.defaults import patterns, url
|
||||
|
||||
from .views import OpenRCView
|
||||
from .views import download_ec2_bundle, download_rc_file
|
||||
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^$', OpenRCView.as_view(), name='index'))
|
||||
url(r'^ec2/$', download_ec2_bundle, name='ec2'),
|
||||
url(r'^openrc/$', download_rc_file, name='openrc'),
|
||||
)
|
@ -0,0 +1,135 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 Openstack, LLC
|
||||
#
|
||||
# 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 tempfile
|
||||
import zipfile
|
||||
from contextlib import closing
|
||||
|
||||
from django import http
|
||||
from django import shortcuts
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import messages
|
||||
|
||||
from openstack_dashboard import api
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def download_ec2_bundle(request):
|
||||
tenant_id = request.user.tenant_id
|
||||
tenant_name = request.user.tenant_name
|
||||
|
||||
# Gather or create our EC2 credentials
|
||||
try:
|
||||
credentials = api.nova.get_x509_credentials(request)
|
||||
cacert = api.nova.get_x509_root_certificate(request)
|
||||
|
||||
all_keys = api.keystone.list_ec2_credentials(request,
|
||||
request.user.id)
|
||||
keys = None
|
||||
for key in all_keys:
|
||||
if key.tenant_id == tenant_id:
|
||||
keys = key
|
||||
if keys is None:
|
||||
keys = api.keystone.create_ec2_credentials(request,
|
||||
request.user.id,
|
||||
tenant_id)
|
||||
except:
|
||||
exceptions.handle(request,
|
||||
_('Unable to fetch EC2 credentials.'),
|
||||
redirect=request.build_absolute_uri())
|
||||
|
||||
# Get our S3 endpoint if it exists
|
||||
try:
|
||||
s3_endpoint = api.base.url_for(request,
|
||||
's3',
|
||||
endpoint_type='publicURL')
|
||||
except exceptions.ServiceCatalogException:
|
||||
s3_endpoint = None
|
||||
|
||||
# Get our EC2 endpoint (it should exist since we just got creds for it)
|
||||
try:
|
||||
ec2_endpoint = api.base.url_for(request,
|
||||
'ec2',
|
||||
endpoint_type='publicURL')
|
||||
except exceptions.ServiceCatalogException:
|
||||
ec2_endpoint = None
|
||||
|
||||
# Build the context
|
||||
context = {'ec2_access_key': keys.access,
|
||||
'ec2_secret_key': keys.secret,
|
||||
'ec2_endpoint': ec2_endpoint,
|
||||
's3_endpoint': s3_endpoint}
|
||||
|
||||
# Create our file bundle
|
||||
template = 'project/access_and_security/api_access/ec2rc.sh.template'
|
||||
try:
|
||||
temp_zip = tempfile.NamedTemporaryFile(delete=True)
|
||||
with closing(zipfile.ZipFile(temp_zip.name, mode='w')) as archive:
|
||||
archive.writestr('pk.pem', credentials.private_key)
|
||||
archive.writestr('cert.pem', credentials.data)
|
||||
archive.writestr('cacert.pem', cacert.data)
|
||||
archive.writestr('ec2rc.sh', render_to_string(template, context))
|
||||
except:
|
||||
exceptions.handle(request,
|
||||
_('Error writing zipfile: %(exc)s'),
|
||||
redirect=request.build_absolute_uri())
|
||||
|
||||
# Send it back
|
||||
response = http.HttpResponse(mimetype='application/zip')
|
||||
response.write(temp_zip.read())
|
||||
response['Content-Disposition'] = ('attachment; '
|
||||
'filename=%s-x509.zip'
|
||||
% tenant_name)
|
||||
response['Content-Length'] = temp_zip.tell()
|
||||
return response
|
||||
|
||||
|
||||
def download_rc_file(request):
|
||||
tenant_id = request.user.tenant_id
|
||||
tenant_name = request.user.tenant_name
|
||||
|
||||
template = 'project/access_and_security/api_access/openrc.sh.template'
|
||||
|
||||
try:
|
||||
keystone_url = api.base.url_for(request,
|
||||
'identity',
|
||||
endpoint_type='publicURL')
|
||||
|
||||
context = {'user': request.user,
|
||||
'auth_url': keystone_url,
|
||||
'tenant_id': tenant_id,
|
||||
'tenant_name': tenant_name}
|
||||
|
||||
response = shortcuts.render(request,
|
||||
template,
|
||||
context,
|
||||
content_type="text/plain")
|
||||
response['Content-Disposition'] = ('attachment; '
|
||||
'filename=%s-openrc.sh'
|
||||
% tenant_name)
|
||||
response['Content-Length'] = str(len(response.content))
|
||||
return response
|
||||
|
||||
except Exception, e:
|
||||
LOG.exception("Exception in DownloadOpenRCForm.")
|
||||
messages.error(request, _('Error Downloading RC File: %s') % e)
|
||||
return shortcuts.redirect(request.build_absolute_uri())
|
@ -126,8 +126,6 @@ class FloatingIpViewTests(test.TestCase):
|
||||
def test_disassociate_post(self):
|
||||
floating_ip = self.floating_ips.first()
|
||||
server = self.servers.first()
|
||||
self.mox.StubOutWithMock(api.nova, 'keypair_list')
|
||||
self.mox.StubOutWithMock(api.nova, 'security_group_list')
|
||||
self.mox.StubOutWithMock(api.network, 'tenant_floating_ip_list')
|
||||
self.mox.StubOutWithMock(api.network, 'tenant_floating_ip_get')
|
||||
self.mox.StubOutWithMock(api.network, 'floating_ip_disassociate')
|
||||
@ -135,10 +133,6 @@ class FloatingIpViewTests(test.TestCase):
|
||||
|
||||
api.nova.server_list(IsA(http.HttpRequest),
|
||||
all_tenants=True).AndReturn(self.servers.list())
|
||||
api.nova.keypair_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.keypairs.list())
|
||||
api.nova.security_group_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.security_groups.list())
|
||||
api.network.tenant_floating_ip_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.floating_ips.list())
|
||||
api.network.floating_ip_disassociate(IsA(http.HttpRequest),
|
||||
@ -154,8 +148,6 @@ class FloatingIpViewTests(test.TestCase):
|
||||
def test_disassociate_post_with_exception(self):
|
||||
floating_ip = self.floating_ips.first()
|
||||
server = self.servers.first()
|
||||
self.mox.StubOutWithMock(api.nova, 'keypair_list')
|
||||
self.mox.StubOutWithMock(api.nova, 'security_group_list')
|
||||
self.mox.StubOutWithMock(api.network, 'tenant_floating_ip_list')
|
||||
self.mox.StubOutWithMock(api.network, 'tenant_floating_ip_get')
|
||||
self.mox.StubOutWithMock(api.network, 'floating_ip_disassociate')
|
||||
@ -163,10 +155,6 @@ class FloatingIpViewTests(test.TestCase):
|
||||
|
||||
api.nova.server_list(IsA(http.HttpRequest),
|
||||
all_tenants=True).AndReturn(self.servers.list())
|
||||
api.nova.keypair_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.keypairs.list())
|
||||
api.nova.security_group_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.security_groups.list())
|
||||
api.network.tenant_floating_ip_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.floating_ips.list())
|
||||
|
||||
|
@ -36,16 +36,7 @@ class KeyPairViewTests(test.TestCase):
|
||||
|
||||
self.mox.StubOutWithMock(api.nova, 'keypair_list')
|
||||
self.mox.StubOutWithMock(api.nova, 'keypair_delete')
|
||||
self.mox.StubOutWithMock(api.nova, 'security_group_list')
|
||||
self.mox.StubOutWithMock(api.network, 'tenant_floating_ip_list')
|
||||
self.mox.StubOutWithMock(api.nova, 'server_list')
|
||||
|
||||
api.nova.server_list(IsA(http.HttpRequest),
|
||||
all_tenants=True).AndReturn(self.servers.list())
|
||||
api.nova.security_group_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.security_groups.list())
|
||||
api.network.tenant_floating_ip_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.floating_ips.list())
|
||||
api.nova.keypair_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.keypairs.list())
|
||||
api.nova.keypair_delete(IsA(http.HttpRequest), keypair.name)
|
||||
@ -59,16 +50,7 @@ class KeyPairViewTests(test.TestCase):
|
||||
keypair = self.keypairs.first()
|
||||
self.mox.StubOutWithMock(api.nova, 'keypair_list')
|
||||
self.mox.StubOutWithMock(api.nova, 'keypair_delete')
|
||||
self.mox.StubOutWithMock(api.nova, 'security_group_list')
|
||||
self.mox.StubOutWithMock(api.network, 'tenant_floating_ip_list')
|
||||
self.mox.StubOutWithMock(api.nova, 'server_list')
|
||||
|
||||
api.nova.server_list(IsA(http.HttpRequest),
|
||||
all_tenants=True).AndReturn(self.servers.list())
|
||||
api.nova.security_group_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.security_groups.list())
|
||||
api.network.tenant_floating_ip_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.floating_ips.list())
|
||||
api.nova.keypair_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.keypairs.list())
|
||||
api.nova.keypair_delete(IsA(http.HttpRequest), keypair.name) \
|
||||
|
@ -0,0 +1,126 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Copyright 2012 Nebula, Inc.
|
||||
# Copyright 2012 OpenStack LLC
|
||||
#
|
||||
# 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 _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import messages
|
||||
from horizon import tabs
|
||||
|
||||
from openstack_dashboard.api import keystone
|
||||
from openstack_dashboard.api import network
|
||||
from openstack_dashboard.api import nova
|
||||
|
||||
from .keypairs.tables import KeypairsTable
|
||||
from .floating_ips.tables import FloatingIPsTable
|
||||
from .security_groups.tables import SecurityGroupsTable
|
||||
from .api_access.tables import EndpointsTable
|
||||
|
||||
|
||||
class SecurityGroupsTab(tabs.TableTab):
|
||||
table_classes = (SecurityGroupsTable,)
|
||||
name = _("Security Groups")
|
||||
slug = "security_groups_tab"
|
||||
template_name = "horizon/common/_detail_table.html"
|
||||
|
||||
def get_security_groups_data(self):
|
||||
try:
|
||||
security_groups = nova.security_group_list(self.request)
|
||||
except:
|
||||
security_groups = []
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve security groups.'))
|
||||
return security_groups
|
||||
|
||||
|
||||
class KeypairsTab(tabs.TableTab):
|
||||
table_classes = (KeypairsTable,)
|
||||
name = _("Keypairs")
|
||||
slug = "keypairs_tab"
|
||||
template_name = "horizon/common/_detail_table.html"
|
||||
|
||||
def get_keypairs_data(self):
|
||||
try:
|
||||
keypairs = nova.keypair_list(self.request)
|
||||
except:
|
||||
keypairs = []
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve keypair list.'))
|
||||
return keypairs
|
||||
|
||||
|
||||
class FloatingIPsTab(tabs.TableTab):
|
||||
table_classes = (FloatingIPsTable,)
|
||||
name = _("Floating IPs")
|
||||
slug = "floating_ips_tab"
|
||||
template_name = "horizon/common/_detail_table.html"
|
||||
|
||||
def get_floating_ips_data(self):
|
||||
try:
|
||||
floating_ips = network.tenant_floating_ip_list(self.request)
|
||||
except:
|
||||
floating_ips = []
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve floating IP addresses.'))
|
||||
|
||||
try:
|
||||
floating_ip_pools = network.floating_ip_pools_list(self.request)
|
||||
except:
|
||||
floating_ip_pools = []
|
||||
messages.warning(self.request,
|
||||
_('Unable to retrieve floating IP pools.'))
|
||||
pool_dict = dict([(obj.id, obj.name) for obj in floating_ip_pools])
|
||||
|
||||
instances = []
|
||||
try:
|
||||
instances = nova.server_list(self.request, all_tenants=True)
|
||||
except:
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve instance list.'))
|
||||
|
||||
instances_dict = dict([(obj.id, obj) for obj in instances])
|
||||
|
||||
for ip in floating_ips:
|
||||
ip.instance_name = instances_dict[ip.instance_id].name \
|
||||
if ip.instance_id in instances_dict else None
|
||||
ip.pool_name = pool_dict.get(ip.pool, ip.pool)
|
||||
|
||||
return floating_ips
|
||||
|
||||
|
||||
class APIAccessTab(tabs.TableTab):
|
||||
table_classes = (EndpointsTable,)
|
||||
name = _("API Access")
|
||||
slug = "api_access_tab"
|
||||
template_name = "horizon/common/_detail_table.html"
|
||||
|
||||
def get_endpoints_data(self):
|
||||
services = []
|
||||
for i, service in enumerate(self.request.user.service_catalog):
|
||||
service['id'] = i
|
||||
services.append(keystone.Service(service))
|
||||
return services
|
||||
|
||||
|
||||
class AccessAndSecurityTabs(tabs.TabGroup):
|
||||
slug = "access_security_tabs"
|
||||
tabs = (SecurityGroupsTab, KeypairsTab, FloatingIPsTab, APIAccessTab)
|
||||
sticky = True
|
@ -1,21 +1,15 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}Access & Security{% endblock %}
|
||||
{% block title %}{% trans "Access & Security" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Access & Security") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
<div id="security_groups">
|
||||
{{ security_groups_table.render }}
|
||||
</div>
|
||||
|
||||
<div id="keypairs">
|
||||
{{ keypairs_table.render }}
|
||||
</div>
|
||||
|
||||
<div id="floating_ips">
|
||||
{{ floating_ips_table.render }}
|
||||
<div class="row-fluid">
|
||||
<div class="span12">
|
||||
{{ tab_group.render }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -20,6 +20,7 @@
|
||||
|
||||
from django.conf.urls.defaults import url, patterns, include
|
||||
|
||||
from .api_access import urls as api_access_urls
|
||||
from .floating_ips import urls as fip_urls
|
||||
from .keypairs import urls as keypair_urls
|
||||
from .security_groups import urls as sec_group_urls
|
||||
@ -28,6 +29,7 @@ from .views import IndexView
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^$', IndexView.as_view(), name='index'),
|
||||
url(r'api_access/', include(api_access_urls, namespace='api_access')),
|
||||
url(r'keypairs/', include(keypair_urls, namespace='keypairs')),
|
||||
url(r'floating_ips/', include(fip_urls, namespace='floating_ips')),
|
||||
url(r'security_groups/',
|
||||
|
@ -22,74 +22,12 @@
|
||||
"""
|
||||
Views for Instances and Volumes.
|
||||
"""
|
||||
import logging
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from horizon import tabs
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import messages
|
||||
from horizon import tables
|
||||
|
||||
from openstack_dashboard.api import network
|
||||
from openstack_dashboard.api import nova
|
||||
from .keypairs.tables import KeypairsTable
|
||||
from .floating_ips.tables import FloatingIPsTable
|
||||
from .security_groups.tables import SecurityGroupsTable
|
||||
from .tabs import AccessAndSecurityTabs
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class IndexView(tables.MultiTableView):
|
||||
table_classes = (KeypairsTable, SecurityGroupsTable, FloatingIPsTable)
|
||||
class IndexView(tabs.TabbedTableView):
|
||||
tab_group_class = AccessAndSecurityTabs
|
||||
template_name = 'project/access_and_security/index.html'
|
||||
|
||||
def get_keypairs_data(self):
|
||||
try:
|
||||
keypairs = nova.keypair_list(self.request)
|
||||
except:
|
||||
keypairs = []
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve keypair list.'))
|
||||
return keypairs
|
||||
|
||||
def get_security_groups_data(self):
|
||||
try:
|
||||
security_groups = nova.security_group_list(self.request)
|
||||
except:
|
||||
security_groups = []
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve security groups.'))
|
||||
return security_groups
|
||||
|
||||
def get_floating_ips_data(self):
|
||||
try:
|
||||
floating_ips = network.tenant_floating_ip_list(self.request)
|
||||
except:
|
||||
floating_ips = []
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve floating IP addresses.'))
|
||||
|
||||
try:
|
||||
floating_ip_pools = network.floating_ip_pools_list(self.request)
|
||||
except:
|
||||
floating_ip_pools = []
|
||||
messages.warning(self.request,
|
||||
_('Unable to retrieve floating IP pools.'))
|
||||
pool_dict = dict([(obj.id, obj.name) for obj in floating_ip_pools])
|
||||
|
||||
instances = []
|
||||
try:
|
||||
instances = nova.server_list(self.request, all_tenants=True)
|
||||
except:
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve instance list.'))
|
||||
|
||||
instances_dict = dict([(obj.id, obj) for obj in instances])
|
||||
|
||||
for ip in floating_ips:
|
||||
ip.instance_name = instances_dict[ip.instance_id].name \
|
||||
if ip.instance_id in instances_dict else None
|
||||
ip.pool_name = pool_dict.get(ip.pool, ip.pool)
|
||||
|
||||
return floating_ips
|
||||
|
@ -7,7 +7,7 @@
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'project/containers/_copy.html' %}
|
||||
{% include 'project/containers/_copy.html' %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}Containers{% endblock %}
|
||||
{% block title %}{% trans "Containers" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
<div class='page-header'>
|
||||
<h2>{% trans "Containers" %}
|
||||
<h2>{% trans "Containers" %}</h2>
|
||||
</div>
|
||||
{% endblock page_header %}
|
||||
|
||||
|
@ -23,7 +23,7 @@ import horizon
|
||||
class Settings(horizon.Dashboard):
|
||||
name = _("Settings")
|
||||
slug = "settings"
|
||||
panels = ('user', 'project', 'ec2')
|
||||
panels = ('user',)
|
||||
default_panel = 'user'
|
||||
nav = False
|
||||
|
||||
|
@ -1,111 +0,0 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 Openstack, LLC
|
||||
#
|
||||
# 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 tempfile
|
||||
import zipfile
|
||||
from contextlib import closing
|
||||
|
||||
from django import http
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
|
||||
from openstack_dashboard import api
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DownloadX509Credentials(forms.SelfHandlingForm):
|
||||
tenant = forms.ChoiceField(label=_("Select a Project"))
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(DownloadX509Credentials, self).__init__(request, *args, **kwargs)
|
||||
# Populate tenant choices
|
||||
tenant_choices = []
|
||||
try:
|
||||
tenant_list = api.keystone.tenant_list(request)
|
||||
except:
|
||||
tenant_list = []
|
||||
exceptions.handle(request, _("Unable to retrieve tenant list."))
|
||||
|
||||
for tenant in tenant_list:
|
||||
if tenant.enabled:
|
||||
tenant_choices.append((tenant.id, tenant.name))
|
||||
if not tenant_choices:
|
||||
self.fields['tenant'].choices = [('', 'No Available Tenants')]
|
||||
else:
|
||||
self.fields['tenant'].choices = tenant_choices
|
||||
|
||||
def handle(self, request, data):
|
||||
def find_or_create_access_keys(request, tenant_id):
|
||||
keys = api.keystone.list_ec2_credentials(request, request.user.id)
|
||||
for key in keys:
|
||||
if key.tenant_id == tenant_id:
|
||||
return key
|
||||
return api.keystone.create_ec2_credentials(request,
|
||||
request.user.id,
|
||||
tenant_id)
|
||||
try:
|
||||
# NOTE(jakedahn): Keystone errors unless we specifically scope
|
||||
# the token to tenant before making the call.
|
||||
api.keystone.token_create_scoped(request,
|
||||
data.get('tenant'),
|
||||
request.user.token.id)
|
||||
credentials = api.nova.get_x509_credentials(request)
|
||||
cacert = api.nova.get_x509_root_certificate(request)
|
||||
keys = find_or_create_access_keys(request, data.get('tenant'))
|
||||
context = {'ec2_access_key': keys.access,
|
||||
'ec2_secret_key': keys.secret,
|
||||
'ec2_endpoint': api.base.url_for(
|
||||
request,
|
||||
'ec2',
|
||||
endpoint_type='publicURL')}
|
||||
try:
|
||||
s3_endpoint = api.base.url_for(request,
|
||||
's3',
|
||||
endpoint_type='publicURL')
|
||||
except exceptions.ServiceCatalogException:
|
||||
s3_endpoint = None
|
||||
context['s3_endpoint'] = s3_endpoint
|
||||
except:
|
||||
exceptions.handle(request,
|
||||
_('Unable to fetch EC2 credentials.'),
|
||||
redirect=request.build_absolute_uri())
|
||||
|
||||
try:
|
||||
temp_zip = tempfile.NamedTemporaryFile(delete=True)
|
||||
with closing(zipfile.ZipFile(temp_zip.name, mode='w')) as archive:
|
||||
archive.writestr('pk.pem', credentials.private_key)
|
||||
archive.writestr('cert.pem', credentials.data)
|
||||
archive.writestr('cacert.pem', cacert.data)
|
||||
archive.writestr('ec2rc.sh', render_to_string(
|
||||
'settings/ec2/ec2rc.sh.template', context))
|
||||
except:
|
||||
exceptions.handle(request,
|
||||
_('Error writing zipfile: %(exc)s'),
|
||||
redirect=request.build_absolute_uri())
|
||||
|
||||
response = http.HttpResponse(mimetype='application/zip')
|
||||
response.write(temp_zip.read())
|
||||
response['Content-Disposition'] = 'attachment; \
|
||||
filename=%s-x509.zip' \
|
||||
% data.get('tenant')
|
||||
response['Content-Length'] = temp_zip.tell()
|
||||
return response
|
@ -1,30 +0,0 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 Openstack, LLC
|
||||
#
|
||||
# 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 openstack_dashboard.dashboards.settings import dashboard
|
||||
|
||||
|
||||
class EC2Panel(horizon.Panel):
|
||||
name = _("EC2 Credentials")
|
||||
slug = 'ec2'
|
||||
permissions = ('openstack.services.ec2',)
|
||||
|
||||
|
||||
dashboard.Settings.register(EC2Panel)
|
@ -1,25 +0,0 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block form_id %}x509_download_form{% endblock %}
|
||||
{% block form_action %}{% url horizon:settings:ec2:index %}{% endblock %}
|
||||
|
||||
{% block modal_id %}x509_download_modal{% endblock %}
|
||||
{% block modal-header %}{% trans "Download EC2 Credentials" %}{% endblock %}
|
||||
|
||||
{% block modal-body %}
|
||||
<div class="left">
|
||||
<fieldset>
|
||||
{% include "horizon/common/_form_fields.html" %}
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="right">
|
||||
<h3>{% trans "Description:" %}</h3>
|
||||
<p>{% trans 'Clicking "Download EC2 Credentials" will download a zip file which includes an rc file with your access/secret keys, as well as your x509 private key and certificate.' %}</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<input class="btn btn-primary pull-right always-enabled" type="submit" value="{% trans "Download EC2 Credentials" %}" />
|
||||
{% if hide %}<a href="{% url horizon:settings:ec2:index %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>{% endif %}
|
||||
{% endblock %}
|
@ -1,11 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Download EC2 Credentials" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Download EC2 Credentials") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% include "settings/ec2/download_form.html" %}
|
||||
{% endblock %}
|
@ -1,23 +0,0 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 Openstack, LLC
|
||||
#
|
||||
# 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.urls.defaults import patterns, url
|
||||
|
||||
from .views import IndexView
|
||||
|
||||
urlpatterns = patterns('openstack_dashboard.dashboards.settings.ec2.views',
|
||||
url(r'^$', IndexView.as_view(), name='index'),
|
||||
)
|
@ -1,32 +0,0 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 Openstack, LLC
|
||||
#
|
||||
# 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
|
||||
|
||||
from horizon import forms
|
||||
|
||||
from .forms import DownloadX509Credentials
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class IndexView(forms.ModalFormView):
|
||||
form_class = DownloadX509Credentials
|
||||
template_name = 'settings/ec2/index.html'
|
||||
|
||||
def form_valid(self, form):
|
||||
return form.handle(self.request, form.cleaned_data)
|
@ -1,78 +0,0 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Copyright 2012 Nebula, Inc.
|
||||
#
|
||||
# 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
|
||||
|
||||
from django import shortcuts
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import messages
|
||||
|
||||
from openstack_dashboard import api
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DownloadOpenRCForm(forms.SelfHandlingForm):
|
||||
tenant = forms.ChoiceField(label=_("Select a Project"))
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(DownloadOpenRCForm, self).__init__(request, *args, **kwargs)
|
||||
# Populate tenant choices
|
||||
tenant_choices = []
|
||||
try:
|
||||
tenants = api.keystone.tenant_list(request)
|
||||
except:
|
||||
tenants = []
|
||||
exceptions.handle(request, _("Unable to retrieve project list."))
|
||||
for tenant in tenants:
|
||||
if tenant.enabled:
|
||||
tenant_choices.append((tenant.id, tenant.name))
|
||||
self.fields['tenant'].choices = tenant_choices
|
||||
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
tenant_id = data['tenant']
|
||||
tenant_name = dict(self.fields['tenant'].choices)[tenant_id]
|
||||
|
||||
keystone_url = api.base.url_for(request,
|
||||
'identity',
|
||||
endpoint_type='publicURL')
|
||||
|
||||
context = {'user': request.user,
|
||||
'auth_url': keystone_url,
|
||||
'tenant_id': tenant_id,
|
||||
'tenant_name': tenant_name}
|
||||
|
||||
response = shortcuts.render(request,
|
||||
'settings/project/openrc.sh.template',
|
||||
context,
|
||||
content_type="text/plain")
|
||||
response['Content-Disposition'] = 'attachment; filename=openrc.sh'
|
||||
response['Content-Length'] = str(len(response.content))
|
||||
return response
|
||||
|
||||
except Exception, e:
|
||||
LOG.exception("Exception in DownloadOpenRCForm.")
|
||||
messages.error(request, _('Error Downloading RC File: %s') % e)
|
||||
return shortcuts.redirect(request.build_absolute_uri())
|
@ -1,29 +0,0 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 Nebula, Inc.
|
||||
#
|
||||
# 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 openstack_dashboard.dashboards.settings import dashboard
|
||||
|
||||
|
||||
class TenantPanel(horizon.Panel):
|
||||
name = _("OpenStack API")
|
||||
slug = 'project'
|
||||
|
||||
|
||||
dashboard.Settings.register(TenantPanel)
|
@ -1,32 +0,0 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block form_id %}openrc_download_form{% endblock %}
|
||||
{% block form_action %}{% url horizon:settings:project:index %}{% endblock %}
|
||||
|
||||
{% block modal_id %}language_settings_modal{% endblock %}
|
||||
{% block modal-header %}{% trans "OpenStack API" %}{% endblock %}
|
||||
|
||||
{% block modal-body %}
|
||||
<div>
|
||||
{{ endpoints.render }}
|
||||
</div>
|
||||
<div>
|
||||
<h3>{% trans "Download OpenStack RC File" %}</h3>
|
||||
<hr />
|
||||
</div>
|
||||
<div class="left">
|
||||
<fieldset>
|
||||
{% include "horizon/common/_form_fields.html" %}
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="right">
|
||||
<h3>{% trans "Description:" %}</h3>
|
||||
<p>{% trans 'Download the RC file for the selected project, then type "source openrc" in the terminal to configure your environment to communicate with OpenStack.' %}</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<button class="btn btn-primary pull-right always-enabled" type="submit">{% trans "Download RC File" %}</button>
|
||||
{% if hide %}<a href="{% url horizon:settings:project:index %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>{% endif %}
|
||||
{% endblock %}
|
@ -1,11 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "OpenStack API" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("OpenStack API") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% include "settings/project/_openrc.html" %}
|
||||
{% endblock %}
|
@ -1,44 +0,0 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 Nebula, Inc.
|
||||
#
|
||||
# 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 horizon.forms import ModalFormView
|
||||
|
||||
from openstack_dashboard.api import keystone
|
||||
from .forms import DownloadOpenRCForm
|
||||
from .tables import EndpointsTable
|
||||
|
||||
|
||||
class OpenRCView(ModalFormView):
|
||||
form_class = DownloadOpenRCForm
|
||||
template_name = 'settings/project/settings.html'
|
||||
|
||||
def get_data(self):
|
||||
services = []
|
||||
for i, service in enumerate(self.request.user.service_catalog):
|
||||
service['id'] = i
|
||||
services.append(keystone.Service(service))
|
||||
return services
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(OpenRCView, self).get_context_data(**kwargs)
|
||||
context["endpoints"] = EndpointsTable(self.request, self.get_data())
|
||||
return context
|
||||
|
||||
def get_initial(self):
|
||||
return {'tenant': self.request.user.tenant_id}
|
||||
|
||||
def form_valid(self, form):
|
||||
return form.handle(self.request, form.cleaned_data)
|
Loading…
x
Reference in New Issue
Block a user