Create links in LBaaS detail pages

LBaaS detail pages contains many UUIDs and it is not human-friendly.
This commit replaces UUIDs with name or similar strings and
add links to details of corresponding resource.

In addition, some columns are added to health monitor tables
because UUID and monitor type are not enough to identify
health monitors.

Change-Id: I7b13c827d96b57a82e3855743e7d68e564edebd7
Closes-Bug: #1243123
Closes-Bug: #1243126
This commit is contained in:
Akihiro Motoki 2014-03-16 08:39:33 +09:00
parent 857ccf9ccc
commit 68d42f0e6b
11 changed files with 198 additions and 87 deletions

View File

@ -96,7 +96,15 @@ def vip_list(request, **kwargs):
def vip_get(request, vip_id):
return _vip_get(request, vip_id, expand_resource=True)
def _vip_get(request, vip_id, expand_resource=False):
vip = neutronclient(request).show_vip(vip_id).get('vip')
if expand_resource:
vip['subnet'] = neutron.subnet_get(request, vip['subnet_id'])
vip['port'] = neutron.port_get(request, vip['port_id'])
vip['pool'] = _pool_get(request, vip['pool_id'])
return Vip(vip)
@ -132,15 +140,18 @@ def pool_create(request, **kwargs):
return Pool(pool)
def _get_vip_name(request, pool, vip_dict):
def _get_vip(request, pool, vip_dict, expand_name_only=False):
if pool['vip_id'] is not None:
try:
if vip_dict:
return vip_dict.get(pool['vip_id']).name
vip = vip_dict.get(pool['vip_id'])
else:
return vip_get(request, pool['vip_id']).name
vip = _vip_get(request, pool['vip_id'])
except Exception:
return pool['vip_id']
vip = Vip({'id': pool['vip_id'], 'name': ''})
if expand_name_only:
vip = vip.name_or_id
return vip
else:
return None
@ -160,21 +171,25 @@ def _pool_list(request, expand_subnet=False, expand_vip=False, **kwargs):
vips = vip_list(request)
vip_dict = SortedDict((v.id, v) for v in vips)
for p in pools:
p['vip_name'] = _get_vip_name(request, p, vip_dict)
p['vip_name'] = _get_vip(request, p, vip_dict,
expand_name_only=True)
return [Pool(p) for p in pools]
def pool_get(request, pool_id):
return _pool_get(request, pool_id, expand_subnet=True, expand_vip=True)
return _pool_get(request, pool_id, expand_resource=True)
def _pool_get(request, pool_id, expand_subnet=False, expand_vip=False):
def _pool_get(request, pool_id, expand_resource=False):
pool = neutronclient(request).show_pool(pool_id).get('pool')
if expand_subnet:
pool['subnet_name'] = neutron.subnet_get(request,
pool['subnet_id']).cidr
if expand_vip:
pool['vip_name'] = _get_vip_name(request, pool, vip_dict=False)
if expand_resource:
pool['subnet'] = neutron.subnet_get(request, pool['subnet_id'])
pool['vip'] = _get_vip(request, pool, vip_dict=None,
expand_name_only=False)
pool['members'] = _member_list(request, expand_pool=False,
pool_id=pool_id)
pool['health_monitors'] = pool_health_monitor_list(
request, id=pool['health_monitors'])
return Pool(pool)
@ -230,9 +245,16 @@ def pool_health_monitor_list(request, **kwargs):
def pool_health_monitor_get(request, monitor_id):
return _pool_health_monitor_get(request, monitor_id, expand_resource=True)
def _pool_health_monitor_get(request, monitor_id, expand_resource=False):
monitor = neutronclient(request
).show_health_monitor(monitor_id
).get('health_monitor')
if expand_resource:
pool_ids = [p['pool_id'] for p in monitor['pools']]
monitor['pools'] = _pool_list(request, id=pool_ids)
return PoolMonitor(monitor)
@ -276,7 +298,7 @@ def _member_list(request, expand_pool, **kwargs):
pools = _pool_list(request)
pool_dict = SortedDict((p.id, p) for p in pools)
for m in members:
m['pool_name'] = pool_dict.get(m['pool_id']).name
m['pool_name'] = pool_dict.get(m['pool_id']).name_or_id
return [Member(m) for m in members]
@ -287,7 +309,7 @@ def member_get(request, member_id):
def _member_get(request, member_id, expand_pool):
member = neutronclient(request).show_member(member_id).get('member')
if expand_pool:
member['pool_name'] = _pool_get(request, member['pool_id']).name
member['pool'] = _pool_get(request, member['pool_id'])
return Member(member)

View File

@ -244,11 +244,24 @@ class MembersTable(tables.DataTable):
row_actions = (UpdateMemberLink, DeleteMemberLink)
def get_monitor_details(monitor):
if monitor.type in ('HTTP', 'HTTPS'):
return ("%(http_method)s %(url_path)s => %(codes)s" %
{'http_method': monitor.http_method,
'url_path': monitor.url_path,
'codes': monitor.expected_codes})
else:
return _("-")
class MonitorsTable(tables.DataTable):
id = tables.Column("id",
verbose_name=_("ID"),
link="horizon:project:loadbalancers:monitordetails")
monitorType = tables.Column('type', verbose_name=_("Monitor Type"))
monitor_type = tables.Column(
"type", verbose_name=_("Monitor Type"),
link="horizon:project:loadbalancers:monitordetails")
delay = tables.Column("delay", verbose_name=_("Delay"))
timeout = tables.Column("timeout", verbose_name=_("Timeout"))
max_retries = tables.Column("max_retries", verbose_name=_("Max Retries"))
details = tables.Column(get_monitor_details, verbose_name=_("Details"))
class Meta:
name = "monitorstable"

View File

@ -23,6 +23,7 @@ from horizon import tabs
from openstack_dashboard import api
from openstack_dashboard.dashboards.project.loadbalancers import tables
from openstack_dashboard.dashboards.project.loadbalancers import utils
class PoolsTab(tabs.TableTab):
@ -102,6 +103,9 @@ class PoolDetailsTab(tabs.Tab):
pool = []
exceptions.handle(request,
_('Unable to retrieve pool details.'))
for monitor in pool.health_monitors:
display_name = utils.get_monitor_display_name(monitor)
setattr(monitor, 'display_name', display_name)
return {'pool': pool}

View File

@ -9,8 +9,9 @@
<dt>{% trans "Project ID" %}</dt>
<dd>{{ member.tenant_id }}</dd>
<dt>{% trans "Pool ID" %}</dt>
<dd>{{ member.pool_id }}</dd>
<dt>{% trans "Pool" %}</dt>
{% url 'horizon:project:loadbalancers:pooldetails' member.pool_id as pool_url %}
<dd><a href="{{pool_url}}">{{ member.pool.name_or_id }}</a></dd>
<dt>{% trans "Address" %}</dt>
<dd>{{ member.address }}</dd>

View File

@ -34,5 +34,15 @@
<dt>{% trans "Admin State Up" %}</dt>
<dd>{{ monitor.admin_state_up|yesno|capfirst }}</dd>
<dt>{% trans "Pools" %}</dt>
{% if monitor.pools %}
{% for pool in monitor.pools %}
{% url 'horizon:project:loadbalancers:pooldetails' pool.id as pool_url %}
<dd><a href="{{pool_url}}">{{ pool.name_or_id }}</a></dd>
{% endfor %}
{% else %}
<dd>{% trans "None" %}</dd>
{% endif %}
</dl>
</div>

View File

@ -15,14 +15,20 @@
<dt>{% trans "Project ID" %}</dt>
<dd>{{ pool.tenant_id }}</dd>
<dt>{% trans "VIP ID" %}</dt>
<dd>{{ pool.vip_id|default:_("-") }}</dd>
<dt>{% trans "VIP" %}</dt>
{% if pool.vip_id %}
{% url 'horizon:project:loadbalancers:vipdetails' pool.vip_id as vip_url %}
<dd><a href="{{vip_url}}">{{ pool.vip.name_or_id }}</a></dd>
{% else %}
<dd>{% trans "-" %}</dd>
{% endif %}
<dt>{% trans "Provider" %}</dt>
<dd>{{ pool.provider|default:_("N/A") }}</dd>
<dt>{% trans "Subnet ID" %}</dt>
<dd>{{ pool.subnet_id }}</dd>
<dt>{% trans "Subnet" %}</dt>
{% url 'horizon:project:networks:subnets:detail' pool.subnet_id as subnet_url %}
<dd><a href="{{subnet_url}}">{{ pool.subnet.name_or_id }} {{pool.subnet.cidr}}</a></dd>
<dt>{% trans "Protocol" %}</dt>
<dd>{{ pool.protocol }}</dd>
@ -31,10 +37,28 @@
<dd>{{ pool.lb_method }}</dd>
<dt>{% trans "Members" %}</dt>
<dd>{{ pool.members }}</dd>
<dd>
{% if pool.members %}
{% for member in pool.members %}
{% url 'horizon:project:loadbalancers:memberdetails' member.id as member_url %}
<a href="{{member_url}}">{{member.address}}:{{member.protocol_port}}</a><br>
{% endfor %}
{% else %}
<dd>{% trans "-" %}</dd>
{% endif %}
</dd>
<dt>{% trans "Health Monitors" %}</dt>
<dd>{{ pool.health_monitors }}</dd>
<dd>
{% if pool.health_monitors %}
{% for monitor in pool.health_monitors %}
{% url 'horizon:project:loadbalancers:monitordetails' monitor.id as monitor_url %}
<a href="{{monitor_url}}">{{ monitor.display_name }}</a><br>
{% endfor %}
{% else %}
<dd>{% trans "-" %}</dd>
{% endif %}
</dd>
<dt>{% trans "Admin State Up" %}</dt>
<dd>{{ pool.admin_state_up|yesno|capfirst }}</dd>

View File

@ -15,8 +15,9 @@
<dt>{% trans "Project ID" %}</dt>
<dd>{{ vip.tenant_id }}</dd>
<dt>{% trans "Subnet ID" %}</dt>
<dd>{{ vip.subnet_id }}</dd>
<dt>{% trans "Subnet" %}</dt>
{% url 'horizon:project:networks:subnets:detail' vip.subnet_id as subnet_url %}
<dd><a href="{{subnet_url}}">{{ vip.subnet.name_or_id }} {{ vip.subnet.cidr }}</a></dd>
<dt>{% trans "Address" %}</dt>
<dd>{{ vip.address }}</dd>
@ -27,11 +28,13 @@
<dt>{% trans "Protocol" %}</dt>
<dd>{{ vip.protocol }}</dd>
<dt>{% trans "Pool ID" %}</dt>
<dd>{{ vip.pool_id }}</dd>
<dt>{% trans "Pool" %}</dt>
{% url 'horizon:project:loadbalancers:pooldetails' vip.pool_id as pool_url %}
<dd><a href="{{pool_url}}">{{ vip.pool.name_or_id }}</a></dd>
<dt>{% trans "Port ID" %}</dt>
<dd>{{ vip.port_id }}</dd>
{% url 'horizon:project:networks:ports:detail' vip.port_id as port_url %}
<dd><a href="{{port_url}}">{{ vip.port_id }}</a></dd>
<dt>{% trans "Session Persistence" %}</dt>
{% if vip.session_persistence %}

View File

@ -0,0 +1,31 @@
# Copyright 2014, NEC Corporation
#
# 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 _
def get_monitor_display_name(monitor):
fields = ['type', 'delay', 'max_retries', 'timeout']
if monitor.type in ['HTTP', 'HTTPS']:
fields.extend(['url_path', 'expected_codes', 'http_method'])
name = _("%(type)s: url:%(url_path)s "
"method:%(http_method)s codes:%(expected_codes)s "
"delay:%(delay)d retries:%(max_retries)d "
"timeout:%(timeout)d")
else:
name = _("%(type)s delay:%(delay)d "
"retries:%(max_retries)d "
"timeout:%(timeout)d")
params = dict((key, getattr(monitor, key)) for key in fields)
return name % params

View File

@ -23,6 +23,7 @@ from horizon.utils import validators
from horizon import workflows
from openstack_dashboard import api
from openstack_dashboard.dashboards.project.loadbalancers import utils
AVAILABLE_PROTOCOLS = ('HTTP', 'HTTPS', 'TCP')
@ -537,25 +538,7 @@ class AddMonitor(workflows.Workflow):
return False
class MonitorMixin():
def _get_monitor_display_name(self, monitor):
fields = ['type', 'delay', 'max_retries', 'timeout']
if monitor.type in ['HTTP', 'HTTPS']:
fields.extend(['url_path', 'expected_codes', 'http_method'])
name = _("%(type)s url:%(url_path)s "
"method:%(http_method)s codes:%(expected_codes)s "
"delay:%(delay)d retries:%(max_retries)d "
"timeout:%(timeout)d")
else:
name = _("%(type)s delay:%(delay)d "
"retries:%(max_retries)d "
"timeout:%(timeout)d")
params = dict((key, getattr(monitor, key)) for key in fields)
return name % params
class AddPMAssociationAction(workflows.Action, MonitorMixin):
class AddPMAssociationAction(workflows.Action):
monitor_id = forms.ChoiceField(label=_("Monitor"))
def __init__(self, request, *args, **kwargs):
@ -572,7 +555,7 @@ class AddPMAssociationAction(workflows.Action, MonitorMixin):
tenant_id=tenant_id)
for m in monitors:
if m.id not in context['pool_monitors']:
display_name = self._get_monitor_display_name(m)
display_name = utils.get_monitor_display_name(m)
monitor_id_choices.append((m.id, display_name))
except Exception:
exceptions.handle(request,
@ -617,7 +600,7 @@ class AddPMAssociation(workflows.Workflow):
return False
class DeletePMAssociationAction(workflows.Action, MonitorMixin):
class DeletePMAssociationAction(workflows.Action):
monitor_id = forms.ChoiceField(label=_("Monitor"))
def __init__(self, request, *args, **kwargs):
@ -633,7 +616,7 @@ class DeletePMAssociationAction(workflows.Action, MonitorMixin):
monitors = api.lbaas.pool_health_monitor_list(request)
for m in monitors:
if m.id in context['pool_monitors']:
display_name = self._get_monitor_display_name(m)
display_name = utils.get_monitor_display_name(m)
monitor_id_choices.append((m.id, display_name))
except Exception:
exceptions.handle(request,

View File

@ -88,24 +88,27 @@ class LbaasApiTests(test.APITestCase):
self.assertIsInstance(v, api.lbaas.Vip)
self.assertTrue(v.id)
@test.create_stubs({neutronclient: ('show_vip',)})
@test.create_stubs({neutronclient: ('show_vip', 'show_pool'),
api.neutron: ('subnet_get', 'port_get')})
def test_vip_get(self):
vip = {'vip': {'id': 'abcdef-c3eb-4fee-9763-12de3338041e',
'address': '10.0.0.100',
'name': 'vip1name',
'description': 'vip1description',
'subnet_id': '12381d38-c3eb-4fee-9763-12de3338041e',
'protocol_port': '80',
'protocol': 'HTTP',
'pool_id': '8913dde8-4915-4b90-8d3e-b95eeedb0d49',
'connection_limit': '10',
'admin_state_up': True
}}
neutronclient.show_vip(vip['vip']['id']).AndReturn(vip)
vip = self.api_vips.first()
neutronclient.show_vip(vip['id']).AndReturn({'vip': vip})
api.neutron.subnet_get(self.request, vip['subnet_id']
).AndReturn(self.subnets.first())
api.neutron.port_get(self.request, vip['port_id']
).AndReturn(self.ports.first())
neutronclient.show_pool(vip['pool_id']
).AndReturn({'pool': self.api_pools.first()})
self.mox.ReplayAll()
ret_val = api.lbaas.vip_get(self.request, vip['vip']['id'])
ret_val = api.lbaas.vip_get(self.request, vip['id'])
self.assertIsInstance(ret_val, api.lbaas.Vip)
self.assertIsInstance(ret_val.subnet, api.neutron.Subnet)
self.assertEqual(vip['subnet_id'], ret_val.subnet.id)
self.assertIsInstance(ret_val.port, api.neutron.Port)
self.assertEqual(vip['port_id'], ret_val.port.id)
self.assertIsInstance(ret_val.pool, api.lbaas.Pool)
self.assertEqual(self.api_pools.first()['id'], ret_val.pool.id)
@test.create_stubs({neutronclient: ('update_vip',)})
def test_vip_update(self):
@ -181,7 +184,9 @@ class LbaasApiTests(test.APITestCase):
self.assertIsInstance(v, api.lbaas.Pool)
self.assertTrue(v.id)
@test.create_stubs({neutronclient: ('show_pool', 'show_vip'),
@test.create_stubs({neutronclient: ('show_pool', 'show_vip',
'list_members',
'list_health_monitors',),
api.neutron: ('subnet_get',)})
def test_pool_get(self):
pool = self.pools.first()
@ -192,10 +197,23 @@ class LbaasApiTests(test.APITestCase):
neutronclient.show_pool(pool.id).AndReturn(pool_dict)
api.neutron.subnet_get(self.request, subnet.id).AndReturn(subnet)
neutronclient.show_vip(pool.vip_id).AndReturn(vip_dict)
neutronclient.list_members(pool_id=pool.id).AndReturn(
{'members': self.members.list()})
neutronclient.list_health_monitors(id=pool.health_monitors).AndReturn(
{'health_monitors': [self.monitors.first()]})
self.mox.ReplayAll()
ret_val = api.lbaas.pool_get(self.request, pool.id)
self.assertIsInstance(ret_val, api.lbaas.Pool)
self.assertIsInstance(ret_val.vip, api.lbaas.Vip)
self.assertEqual(ret_val.vip.id, vip_dict['vip']['id'])
self.assertIsInstance(ret_val.subnet, api.neutron.Subnet)
self.assertEqual(ret_val.subnet.id, subnet.id)
self.assertEqual(2, len(ret_val.members))
self.assertIsInstance(ret_val.members[0], api.lbaas.Member)
self.assertEqual(1, len(ret_val.health_monitors))
self.assertIsInstance(ret_val.health_monitors[0],
api.lbaas.PoolMonitor)
@test.create_stubs({neutronclient: ('update_pool',)})
def test_pool_update(self):
@ -267,25 +285,21 @@ class LbaasApiTests(test.APITestCase):
self.assertIsInstance(v, api.lbaas.PoolMonitor)
self.assertTrue(v.id)
@test.create_stubs({neutronclient: ('show_health_monitor',)})
@test.create_stubs({neutronclient: ('show_health_monitor',
'list_pools')})
def test_pool_health_monitor_get(self):
monitor = {'health_monitor':
{'id': 'abcdef-c3eb-4fee-9763-12de3338041e',
'type': 'PING',
'delay': '10',
'timeout': '10',
'max_retries': '10',
'http_method': 'GET',
'url_path': '/monitor',
'expected_codes': '200',
'admin_state_up': True}}
monitor = self.api_monitors.first()
neutronclient.show_health_monitor(
monitor['health_monitor']['id']).AndReturn(monitor)
monitor['id']).AndReturn({'health_monitor': monitor})
neutronclient.list_pools(id=[p['pool_id'] for p in monitor['pools']]
).AndReturn({'pools': self.api_pools.list()})
self.mox.ReplayAll()
ret_val = api.lbaas.pool_health_monitor_get(
self.request, monitor['health_monitor']['id'])
self.request, monitor['id'])
self.assertIsInstance(ret_val, api.lbaas.PoolMonitor)
self.assertEqual(2, len(ret_val.pools))
self.assertIsInstance(ret_val.pools[0], api.lbaas.Pool)
@test.create_stubs({neutronclient: ('create_member', )})
def test_member_create(self):

View File

@ -438,6 +438,7 @@ def data(TEST):
'protocol': 'HTTP',
'lb_method': 'ROUND_ROBIN',
'health_monitors': ['d4a0500f-db2b-4cc4-afcf-ec026febff96'],
'members': ['78a46e5e-eb1a-418a-88c7-0e3f5968b08'],
'admin_state_up': True,
'status': 'ACTIVE',
'provider': 'haproxy'}
@ -454,6 +455,7 @@ def data(TEST):
'protocol': 'HTTPS',
'lb_method': 'ROUND_ROBIN',
'health_monitors': ['d4a0500f-db2b-4cc4-afcf-ec026febff97'],
'members': [],
'status': 'PENDING_CREATE',
'admin_state_up': True}
TEST.api_pools.add(pool_dict)
@ -467,6 +469,7 @@ def data(TEST):
'other_address': '10.0.0.100',
'description': 'vip description',
'subnet_id': TEST.subnets.first().id,
'port_id': TEST.ports.first().id,
'subnet': TEST.subnets.first().cidr,
'protocol_port': 80,
'protocol': pool_dict['protocol'],
@ -486,6 +489,7 @@ def data(TEST):
'other_address': '10.0.0.110',
'description': 'vip description',
'subnet_id': TEST.subnets.first().id,
'port_id': TEST.ports.list()[0].id,
'subnet': TEST.subnets.first().cidr,
'protocol_port': 80,
'protocol': pool_dict['protocol'],
@ -523,14 +527,17 @@ def data(TEST):
# 1st monitor
monitor_dict = {'id': 'd4a0500f-db2b-4cc4-afcf-ec026febff96',
'type': 'ping',
'type': 'http',
'delay': 10,
'timeout': 10,
'max_retries': 10,
'http_method': 'GET',
'url_path': '/',
'expected_codes': '200',
'admin_state_up': True}
'admin_state_up': True,
"pools": [{"pool_id": TEST.pools.list()[0].id},
{"pool_id": TEST.pools.list()[1].id}],
}
TEST.api_monitors.add(monitor_dict)
TEST.monitors.add(lbaas.PoolMonitor(monitor_dict))
@ -540,10 +547,9 @@ def data(TEST):
'delay': 10,
'timeout': 10,
'max_retries': 10,
'http_method': 'GET',
'url_path': '/',
'expected_codes': '200',
'admin_state_up': True}
'admin_state_up': True,
'pools': [],
}
TEST.api_monitors.add(monitor_dict)
TEST.monitors.add(lbaas.PoolMonitor(monitor_dict))