Merge "Drop LBaaS v1 dashboard"
This commit is contained in:
commit
22b71f7c26
@ -1196,7 +1196,6 @@ Default::
|
||||
'enable_router': True,
|
||||
'enable_distributed_router': False,
|
||||
'enable_ha_router': False,
|
||||
'enable_lb': True,
|
||||
'enable_quotas': False,
|
||||
'enable_firewall': True,
|
||||
'enable_vpn': True,
|
||||
@ -1253,24 +1252,6 @@ Even when your Neutron plugin (like ML2 plugin) supports HA router mode,
|
||||
the feature depends on l3-agent configuration, so deployers should set this
|
||||
option appropriately depending on your deployment.
|
||||
|
||||
``enable_lb``
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
.. versionadded:: 2013.1(Grizzly)
|
||||
|
||||
(Deprecated)
|
||||
|
||||
Default: ``True``
|
||||
|
||||
Enables the load balancer panel. The load balancer panel will be enabled when
|
||||
this option is True and your Neutron deployment supports LBaaS. If you want
|
||||
to disable load balancer panel even when your Neutron supports LBaaS, set it to False.
|
||||
|
||||
This option is now marked as "deprecated" and will be removed in Kilo or later release.
|
||||
The load balancer panel is now enabled only when LBaaS feature is available in Neutron
|
||||
and this option is no longer needed. We suggest not to use this option to disable the
|
||||
load balancer panel from now on.
|
||||
|
||||
``enable_quotas``
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
@ -38,7 +38,6 @@ from openstack_dashboard.api import fwaas
|
||||
from openstack_dashboard.api import glance
|
||||
from openstack_dashboard.api import heat
|
||||
from openstack_dashboard.api import keystone
|
||||
from openstack_dashboard.api import lbaas
|
||||
from openstack_dashboard.api import network
|
||||
from openstack_dashboard.api import neutron
|
||||
from openstack_dashboard.api import nova
|
||||
@ -53,7 +52,6 @@ __all__ = [
|
||||
"glance",
|
||||
"heat",
|
||||
"keystone",
|
||||
"lbaas",
|
||||
"network",
|
||||
"neutron",
|
||||
"nova",
|
||||
|
@ -1,394 +0,0 @@
|
||||
# Copyright 2013, Big Switch Networks, 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 __future__ import absolute_import
|
||||
|
||||
from collections import OrderedDict
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import messages
|
||||
|
||||
from openstack_dashboard.api import neutron
|
||||
|
||||
neutronclient = neutron.neutronclient
|
||||
|
||||
|
||||
class Vip(neutron.NeutronAPIDictWrapper):
|
||||
"""Wrapper for neutron load balancer vip."""
|
||||
|
||||
def __init__(self, apiresource):
|
||||
super(Vip, self).__init__(apiresource)
|
||||
|
||||
|
||||
class Pool(neutron.NeutronAPIDictWrapper):
|
||||
"""Wrapper for neutron load balancer pool."""
|
||||
|
||||
def __init__(self, apiresource):
|
||||
if 'provider' not in apiresource:
|
||||
apiresource['provider'] = None
|
||||
apiresource['admin_state'] = \
|
||||
'UP' if apiresource['admin_state_up'] else 'DOWN'
|
||||
super(Pool, self).__init__(apiresource)
|
||||
|
||||
|
||||
class Member(neutron.NeutronAPIDictWrapper):
|
||||
"""Wrapper for neutron load balancer member."""
|
||||
|
||||
def __init__(self, apiresource):
|
||||
apiresource['admin_state'] = \
|
||||
'UP' if apiresource['admin_state_up'] else 'DOWN'
|
||||
super(Member, self).__init__(apiresource)
|
||||
|
||||
|
||||
class PoolStats(neutron.NeutronAPIDictWrapper):
|
||||
"""Wrapper for neutron load balancer pool stats."""
|
||||
|
||||
def __init__(self, apiresource):
|
||||
super(PoolStats, self).__init__(apiresource)
|
||||
|
||||
|
||||
class PoolMonitor(neutron.NeutronAPIDictWrapper):
|
||||
"""Wrapper for neutron load balancer pool health monitor."""
|
||||
|
||||
def __init__(self, apiresource):
|
||||
apiresource['admin_state'] = \
|
||||
'UP' if apiresource['admin_state_up'] else 'DOWN'
|
||||
super(PoolMonitor, self).__init__(apiresource)
|
||||
|
||||
|
||||
def vip_create(request, **kwargs):
|
||||
"""Create a vip for a specified pool.
|
||||
|
||||
:param request: request context
|
||||
:param address: virtual IP address
|
||||
:param name: name for vip
|
||||
:param description: description for vip
|
||||
:param subnet_id: subnet_id for subnet of vip
|
||||
:param protocol_port: transport layer port number for vip
|
||||
:returns: Vip object
|
||||
"""
|
||||
body = {'vip': {'name': kwargs['name'],
|
||||
'description': kwargs['description'],
|
||||
'subnet_id': kwargs['subnet_id'],
|
||||
'protocol_port': kwargs['protocol_port'],
|
||||
'protocol': kwargs['protocol'],
|
||||
'pool_id': kwargs['pool_id'],
|
||||
'session_persistence': kwargs['session_persistence'],
|
||||
'admin_state_up': kwargs['admin_state_up']
|
||||
}}
|
||||
if kwargs.get('connection_limit'):
|
||||
body['vip']['connection_limit'] = kwargs['connection_limit']
|
||||
|
||||
if kwargs.get('address'):
|
||||
body['vip']['address'] = kwargs['address']
|
||||
|
||||
vip = neutronclient(request).create_vip(body).get('vip')
|
||||
return Vip(vip)
|
||||
|
||||
|
||||
def vip_list(request, **kwargs):
|
||||
vips = neutronclient(request).list_vips(**kwargs).get('vips')
|
||||
return [Vip(v) for v in vips]
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def vip_update(request, vip_id, **kwargs):
|
||||
vip = neutronclient(request).update_vip(vip_id, kwargs).get('vip')
|
||||
return Vip(vip)
|
||||
|
||||
|
||||
def vip_delete(request, vip_id):
|
||||
neutronclient(request).delete_vip(vip_id)
|
||||
|
||||
|
||||
def pool_create(request, **kwargs):
|
||||
"""Create a pool for specified protocol
|
||||
|
||||
:param request: request context
|
||||
:param name: name for pool
|
||||
:param description: description for pool
|
||||
:param subnet_id: subnet_id for subnet of pool
|
||||
:param protocol: load balanced protocol
|
||||
:param lb_method: load balancer method
|
||||
:param admin_state_up: admin state (default on)
|
||||
"""
|
||||
body = {'pool': {'name': kwargs['name'],
|
||||
'description': kwargs['description'],
|
||||
'subnet_id': kwargs['subnet_id'],
|
||||
'protocol': kwargs['protocol'],
|
||||
'lb_method': kwargs['lb_method'],
|
||||
'admin_state_up': kwargs['admin_state_up'],
|
||||
'provider': kwargs['provider'],
|
||||
}}
|
||||
pool = neutronclient(request).create_pool(body).get('pool')
|
||||
return Pool(pool)
|
||||
|
||||
|
||||
def _get_vip(request, pool, vip_dict):
|
||||
if pool['vip_id'] is not None:
|
||||
try:
|
||||
if vip_dict:
|
||||
vip = vip_dict.get(pool['vip_id'])
|
||||
else:
|
||||
vip = _vip_get(request, pool['vip_id'])
|
||||
except Exception:
|
||||
messages.warning(request, _("Unable to get VIP for pool "
|
||||
"%(pool)s.") % {"pool": pool["id"]})
|
||||
vip = Vip({'id': pool['vip_id'], 'name': ''})
|
||||
return vip
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def pool_list(request, **kwargs):
|
||||
return _pool_list(request, expand_subnet=True, expand_vip=True, **kwargs)
|
||||
|
||||
|
||||
def _pool_list(request, expand_subnet=False, expand_vip=False, **kwargs):
|
||||
pools = neutronclient(request).list_pools(**kwargs).get('pools')
|
||||
if expand_subnet:
|
||||
subnets = neutron.subnet_list(request)
|
||||
subnet_dict = OrderedDict((s.id, s) for s in subnets)
|
||||
for p in pools:
|
||||
p['subnet'] = subnet_dict.get(p['subnet_id'])
|
||||
if expand_vip:
|
||||
vips = vip_list(request)
|
||||
vip_dict = OrderedDict((v.id, v) for v in vips)
|
||||
for p in pools:
|
||||
p['vip'] = _get_vip(request, p, vip_dict)
|
||||
return [Pool(p) for p in pools]
|
||||
|
||||
|
||||
def pool_get(request, pool_id):
|
||||
return _pool_get(request, pool_id, expand_resource=True)
|
||||
|
||||
|
||||
def _pool_get(request, pool_id, expand_resource=False):
|
||||
try:
|
||||
pool = neutronclient(request).show_pool(pool_id).get('pool')
|
||||
except Exception:
|
||||
messages.warning(request, _("Unable to get pool detail."))
|
||||
return None
|
||||
if expand_resource:
|
||||
# TODO(lyj): The expand resource(subnet, member etc.) attached
|
||||
# to a pool could be deleted without cleanup pool related database,
|
||||
# this will cause exceptions if we trying to get the deleted resources.
|
||||
# so we need to handle the situation by showing a warning message here.
|
||||
# we can safely remove the try/except once the neutron bug is fixed
|
||||
# https://bugs.launchpad.net/neutron/+bug/1406854
|
||||
try:
|
||||
pool['subnet'] = neutron.subnet_get(request, pool['subnet_id'])
|
||||
except Exception:
|
||||
messages.warning(request, _("Unable to get subnet for pool "
|
||||
"%(pool)s.") % {"pool": pool_id})
|
||||
pool['vip'] = _get_vip(request, pool, vip_dict=None)
|
||||
# Check here to reduce the additional request if pool['members'] is
|
||||
# empty
|
||||
if pool['members']:
|
||||
try:
|
||||
pool['members'] = _member_list(request, expand_pool=False,
|
||||
pool_id=pool_id)
|
||||
except Exception:
|
||||
messages.warning(request, _("Unable to get members for pool "
|
||||
"%(pool)s.") % {"pool": pool_id})
|
||||
# If the filter to get health monitors list is empty, all health
|
||||
# monitors will be returned in the tenant.
|
||||
if pool['health_monitors']:
|
||||
monitors = []
|
||||
for monitor_id in pool['health_monitors']:
|
||||
try:
|
||||
monitors.append(_pool_health_monitor_get(request,
|
||||
monitor_id,
|
||||
False))
|
||||
except Exception:
|
||||
messages.warning(request,
|
||||
_("Unable to get health monitor "
|
||||
"%(monitor_id)s for pool %(pool)s.")
|
||||
% {"pool": pool_id,
|
||||
"monitor_id": monitor_id})
|
||||
pool['health_monitors'] = monitors
|
||||
return Pool(pool)
|
||||
|
||||
|
||||
def pool_update(request, pool_id, **kwargs):
|
||||
pool = neutronclient(request).update_pool(pool_id, kwargs).get('pool')
|
||||
return Pool(pool)
|
||||
|
||||
|
||||
def pool_delete(request, pool):
|
||||
neutronclient(request).delete_pool(pool)
|
||||
|
||||
|
||||
# not linked to UI yet
|
||||
def pool_stats(request, pool_id, **kwargs):
|
||||
stats = neutronclient(request).retrieve_pool_stats(pool_id, **kwargs)
|
||||
return PoolStats(stats)
|
||||
|
||||
|
||||
def pool_health_monitor_create(request, **kwargs):
|
||||
"""Create a health monitor
|
||||
|
||||
:param request: request context
|
||||
:param type: type of monitor
|
||||
:param delay: delay of monitor
|
||||
:param timeout: timeout of monitor
|
||||
:param max_retries: max retries [1..10]
|
||||
:param http_method: http method
|
||||
:param url_path: url path
|
||||
:param expected_codes: http return code
|
||||
:param admin_state_up: admin state
|
||||
"""
|
||||
monitor_type = kwargs['type'].upper()
|
||||
body = {'health_monitor': {'type': monitor_type,
|
||||
'delay': kwargs['delay'],
|
||||
'timeout': kwargs['timeout'],
|
||||
'max_retries': kwargs['max_retries'],
|
||||
'admin_state_up': kwargs['admin_state_up']
|
||||
}}
|
||||
if monitor_type in ['HTTP', 'HTTPS']:
|
||||
body['health_monitor']['http_method'] = kwargs['http_method']
|
||||
body['health_monitor']['url_path'] = kwargs['url_path']
|
||||
body['health_monitor']['expected_codes'] = kwargs['expected_codes']
|
||||
mon = neutronclient(request).create_health_monitor(body).get(
|
||||
'health_monitor')
|
||||
|
||||
return PoolMonitor(mon)
|
||||
|
||||
|
||||
def pool_health_monitor_list(request, **kwargs):
|
||||
monitors = neutronclient(request).list_health_monitors(
|
||||
**kwargs).get('health_monitors')
|
||||
return [PoolMonitor(m) for m in monitors]
|
||||
|
||||
|
||||
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']]
|
||||
# If the filter to get pools list is empty, all pools will be
|
||||
# returned in the tenant.
|
||||
if pool_ids:
|
||||
monitor['pools'] = _pool_list(request, id=pool_ids)
|
||||
return PoolMonitor(monitor)
|
||||
|
||||
|
||||
def pool_health_monitor_update(request, monitor_id, **kwargs):
|
||||
monitor = neutronclient(request
|
||||
).update_health_monitor(monitor_id, kwargs
|
||||
).get('health_monitor')
|
||||
return PoolMonitor(monitor)
|
||||
|
||||
|
||||
def pool_health_monitor_delete(request, mon_id):
|
||||
neutronclient(request).delete_health_monitor(mon_id)
|
||||
|
||||
|
||||
def member_create(request, **kwargs):
|
||||
"""Create a load balance member
|
||||
|
||||
:param request: request context
|
||||
:param pool_id: pool_id of pool for member
|
||||
:param address: IP address
|
||||
:param protocol_port: transport layer port number
|
||||
:param weight: weight for member
|
||||
:param admin_state_up: admin_state
|
||||
"""
|
||||
body = {'member': {'pool_id': kwargs['pool_id'],
|
||||
'address': kwargs['address'],
|
||||
'protocol_port': kwargs['protocol_port'],
|
||||
'admin_state_up': kwargs['admin_state_up']
|
||||
}}
|
||||
if kwargs.get('weight'):
|
||||
body['member']['weight'] = kwargs['weight']
|
||||
member = neutronclient(request).create_member(body).get('member')
|
||||
return Member(member)
|
||||
|
||||
|
||||
def member_list(request, **kwargs):
|
||||
return _member_list(request, expand_pool=True, **kwargs)
|
||||
|
||||
|
||||
def _member_list(request, expand_pool, **kwargs):
|
||||
members = neutronclient(request).list_members(**kwargs).get('members')
|
||||
if expand_pool:
|
||||
pools = _pool_list(request)
|
||||
pool_dict = OrderedDict((p.id, p) for p in pools)
|
||||
for m in members:
|
||||
m['pool_name'] = pool_dict.get(m['pool_id']).name_or_id
|
||||
return [Member(m) for m in members]
|
||||
|
||||
|
||||
def member_get(request, member_id):
|
||||
return _member_get(request, member_id, expand_pool=True)
|
||||
|
||||
|
||||
def _member_get(request, member_id, expand_pool):
|
||||
member = neutronclient(request).show_member(member_id).get('member')
|
||||
if expand_pool:
|
||||
member['pool'] = _pool_get(request, member['pool_id'])
|
||||
return Member(member)
|
||||
|
||||
|
||||
def member_update(request, member_id, **kwargs):
|
||||
member = neutronclient(request).update_member(member_id, kwargs
|
||||
).get('member')
|
||||
return Member(member)
|
||||
|
||||
|
||||
def member_delete(request, mem_id):
|
||||
neutronclient(request).delete_member(mem_id)
|
||||
|
||||
|
||||
def pool_monitor_association_create(request, **kwargs):
|
||||
"""Associate a health monitor with pool
|
||||
|
||||
:param request: request context
|
||||
:param monitor_id: id of monitor
|
||||
:param pool_id: id of pool
|
||||
"""
|
||||
|
||||
body = {'health_monitor': {'id': kwargs['monitor_id'], }}
|
||||
|
||||
neutronclient(request).associate_health_monitor(
|
||||
kwargs['pool_id'], body)
|
||||
|
||||
|
||||
def pool_monitor_association_delete(request, **kwargs):
|
||||
"""Disassociate a health monitor from pool
|
||||
|
||||
:param request: request context
|
||||
:param monitor_id: id of monitor
|
||||
:param pool_id: id of pool
|
||||
"""
|
||||
|
||||
neutronclient(request).disassociate_health_monitor(
|
||||
kwargs['pool_id'], kwargs['monitor_id'])
|
@ -476,14 +476,6 @@ class FloatingIpManager(network_base.FloatingIpManager):
|
||||
server_dict = collections.OrderedDict(
|
||||
[(s.id, s.name) for s in servers])
|
||||
reachable_subnets = self._get_reachable_subnets(ports)
|
||||
if is_service_enabled(self.request,
|
||||
config_name='enable_lb',
|
||||
ext_name='lbaas'):
|
||||
# Also get the loadbalancer VIPs
|
||||
vip_dict = {v['port_id']: v['name']
|
||||
for v in self.client.list_vips().get('vips', [])}
|
||||
else:
|
||||
vip_dict = {}
|
||||
|
||||
targets = []
|
||||
for p in ports:
|
||||
@ -491,7 +483,7 @@ class FloatingIpManager(network_base.FloatingIpManager):
|
||||
if p.device_owner.startswith('network:'):
|
||||
continue
|
||||
port_id = p.id
|
||||
server_name = server_dict.get(p.device_id) or vip_dict.get(port_id)
|
||||
server_name = server_dict.get(p.device_id)
|
||||
|
||||
for ip in p.fixed_ips:
|
||||
if ip['subnet_id'] not in reachable_subnets:
|
||||
|
@ -1,281 +0,0 @@
|
||||
# Copyright 2013, Mirantis 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.core.urlresolvers import reverse
|
||||
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 UpdatePool(forms.SelfHandlingForm):
|
||||
name = forms.CharField(max_length=80, label=_("Name"))
|
||||
pool_id = forms.CharField(label=_("ID"),
|
||||
widget=forms.TextInput(
|
||||
attrs={'readonly': 'readonly'}))
|
||||
description = forms.CharField(required=False,
|
||||
max_length=80, label=_("Description"))
|
||||
lb_method = forms.ThemableChoiceField(label=_("Load Balancing Method"))
|
||||
admin_state_up = forms.ThemableChoiceField(
|
||||
choices=[(True, _('UP')),
|
||||
(False, _('DOWN'))],
|
||||
label=_("Admin State"))
|
||||
|
||||
failure_url = 'horizon:project:loadbalancers:index'
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(UpdatePool, self).__init__(request, *args, **kwargs)
|
||||
|
||||
lb_method_choices = [('ROUND_ROBIN', 'ROUND_ROBIN'),
|
||||
('LEAST_CONNECTIONS', 'LEAST_CONNECTIONS'),
|
||||
('SOURCE_IP', 'SOURCE_IP')]
|
||||
self.fields['lb_method'].choices = lb_method_choices
|
||||
|
||||
def handle(self, request, context):
|
||||
context['admin_state_up'] = (context['admin_state_up'] == 'True')
|
||||
try:
|
||||
data = {'pool': {'name': context['name'],
|
||||
'description': context['description'],
|
||||
'lb_method': context['lb_method'],
|
||||
'admin_state_up': context['admin_state_up'],
|
||||
}}
|
||||
pool = api.lbaas.pool_update(request, context['pool_id'], **data)
|
||||
msg = _('Pool %s was successfully updated.') % context['name']
|
||||
LOG.debug(msg)
|
||||
messages.success(request, msg)
|
||||
return pool
|
||||
except Exception:
|
||||
msg = _('Failed to update pool %s') % context['name']
|
||||
LOG.info(msg)
|
||||
redirect = reverse(self.failure_url)
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
||||
|
||||
|
||||
class UpdateVip(forms.SelfHandlingForm):
|
||||
name = forms.CharField(max_length=80, label=_("Name"))
|
||||
vip_id = forms.CharField(label=_("ID"),
|
||||
widget=forms.TextInput(
|
||||
attrs={'readonly': 'readonly'}))
|
||||
description = forms.CharField(required=False,
|
||||
max_length=80, label=_("Description"))
|
||||
pool_id = forms.ThemableChoiceField(label=_("Pool"))
|
||||
session_persistence = forms.ThemableChoiceField(
|
||||
required=False, initial={}, label=_("Session Persistence"))
|
||||
|
||||
cookie_name = forms.CharField(
|
||||
initial="", required=False,
|
||||
max_length=80, label=_("Cookie Name"),
|
||||
help_text=_("Required for APP_COOKIE persistence;"
|
||||
" Ignored otherwise."))
|
||||
|
||||
connection_limit = forms.IntegerField(
|
||||
min_value=-1, label=_("Connection Limit"),
|
||||
help_text=_("Maximum number of connections allowed "
|
||||
"for the VIP or '-1' if the limit is not set"))
|
||||
admin_state_up = forms.ThemableChoiceField(
|
||||
choices=[(True, _('UP')),
|
||||
(False, _('DOWN'))],
|
||||
label=_("Admin State"))
|
||||
|
||||
failure_url = 'horizon:project:loadbalancers:index'
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(UpdateVip, self).__init__(request, *args, **kwargs)
|
||||
|
||||
pool_id_choices = []
|
||||
try:
|
||||
tenant_id = request.user.tenant_id
|
||||
pools = api.lbaas.pool_list(request, tenant_id=tenant_id)
|
||||
except Exception:
|
||||
pools = []
|
||||
exceptions.handle(request,
|
||||
_('Unable to retrieve pools list.'))
|
||||
pools = sorted(pools,
|
||||
key=lambda pool: pool.name)
|
||||
for p in pools:
|
||||
if (p.vip_id is None) or (p.id == kwargs['initial']['pool_id']):
|
||||
pool_id_choices.append((p.id, p.name))
|
||||
self.fields['pool_id'].choices = pool_id_choices
|
||||
|
||||
session_persistence_choices = []
|
||||
for mode in ('SOURCE_IP', 'HTTP_COOKIE', 'APP_COOKIE'):
|
||||
session_persistence_choices.append((mode, mode))
|
||||
session_persistence_choices.append(('', _('No session persistence')))
|
||||
self.fields[
|
||||
'session_persistence'].choices = session_persistence_choices
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(UpdateVip, self).clean()
|
||||
|
||||
persistence = cleaned_data.get('session_persistence')
|
||||
if (persistence == 'APP_COOKIE' and
|
||||
not cleaned_data.get('cookie_name')):
|
||||
msg = _('Cookie name is required for APP_COOKIE persistence.')
|
||||
self._errors['cookie_name'] = self.error_class([msg])
|
||||
return cleaned_data
|
||||
|
||||
def handle(self, request, context):
|
||||
context['admin_state_up'] = (context['admin_state_up'] == 'True')
|
||||
if context['session_persistence']:
|
||||
stype = context['session_persistence']
|
||||
if stype == 'APP_COOKIE':
|
||||
cookie = context['cookie_name']
|
||||
context['session_persistence'] = {'type': stype,
|
||||
'cookie_name': cookie}
|
||||
else:
|
||||
context['session_persistence'] = {'type': stype}
|
||||
else:
|
||||
context['session_persistence'] = {}
|
||||
|
||||
try:
|
||||
data = {'vip': {'name': context['name'],
|
||||
'description': context['description'],
|
||||
'pool_id': context['pool_id'],
|
||||
'session_persistence':
|
||||
context['session_persistence'],
|
||||
'connection_limit': context['connection_limit'],
|
||||
'admin_state_up': context['admin_state_up'],
|
||||
}}
|
||||
vip = api.lbaas.vip_update(request, context['vip_id'], **data)
|
||||
msg = _('VIP %s was successfully updated.') % context['name']
|
||||
LOG.debug(msg)
|
||||
messages.success(request, msg)
|
||||
return vip
|
||||
except Exception:
|
||||
msg = _('Failed to update VIP %s') % context['name']
|
||||
LOG.info(msg)
|
||||
redirect = reverse(self.failure_url)
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
||||
|
||||
|
||||
class UpdateMember(forms.SelfHandlingForm):
|
||||
member_id = forms.CharField(label=_("ID"),
|
||||
widget=forms.TextInput(
|
||||
attrs={'readonly': 'readonly'}))
|
||||
pool_id = forms.ThemableChoiceField(label=_("Pool"))
|
||||
weight = forms.IntegerField(max_value=256, min_value=0, label=_("Weight"),
|
||||
help_text=_("Relative part of requests this "
|
||||
"pool member serves compared to others"))
|
||||
admin_state_up = forms.ThemableChoiceField(
|
||||
choices=[(True, _('UP')),
|
||||
(False, _('DOWN'))],
|
||||
label=_("Admin State"))
|
||||
|
||||
failure_url = 'horizon:project:loadbalancers:index'
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(UpdateMember, self).__init__(request, *args, **kwargs)
|
||||
|
||||
pool_id_choices = []
|
||||
try:
|
||||
tenant_id = request.user.tenant_id
|
||||
pools = api.lbaas.pool_list(request, tenant_id=tenant_id)
|
||||
except Exception:
|
||||
pools = []
|
||||
exceptions.handle(request,
|
||||
_('Unable to retrieve pools list.'))
|
||||
pools = sorted(pools,
|
||||
key=lambda pool: pool.name)
|
||||
for p in pools:
|
||||
pool_id_choices.append((p.id, p.name))
|
||||
self.fields['pool_id'].choices = pool_id_choices
|
||||
|
||||
def handle(self, request, context):
|
||||
context['admin_state_up'] = (context['admin_state_up'] == 'True')
|
||||
try:
|
||||
data = {'member': {'pool_id': context['pool_id'],
|
||||
'weight': context['weight'],
|
||||
'admin_state_up': context['admin_state_up']}}
|
||||
member = api.lbaas.member_update(request,
|
||||
context['member_id'], **data)
|
||||
msg = _('Member %s was successfully updated.')\
|
||||
% context['member_id']
|
||||
LOG.debug(msg)
|
||||
messages.success(request, msg)
|
||||
return member
|
||||
except Exception:
|
||||
msg = _('Failed to update member %s') % context['member_id']
|
||||
LOG.info(msg)
|
||||
redirect = reverse(self.failure_url)
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
||||
|
||||
|
||||
class UpdateMonitor(forms.SelfHandlingForm):
|
||||
monitor_id = forms.CharField(label=_("ID"),
|
||||
widget=forms.TextInput(
|
||||
attrs={'readonly': 'readonly'}))
|
||||
delay = forms.IntegerField(
|
||||
min_value=1,
|
||||
label=_("Delay"),
|
||||
help_text=_("The minimum time in seconds between regular checks "
|
||||
"of a member. It must be greater than or equal to "
|
||||
"timeout"))
|
||||
timeout = forms.IntegerField(
|
||||
min_value=1,
|
||||
label=_("Timeout"),
|
||||
help_text=_("The maximum time in seconds for a monitor to wait "
|
||||
"for a reply. It must be less than or equal to delay"))
|
||||
max_retries = forms.IntegerField(
|
||||
max_value=10, min_value=1,
|
||||
label=_("Max Retries (1~10)"),
|
||||
help_text=_("Number of permissible failures before changing "
|
||||
"the status of member to inactive"))
|
||||
admin_state_up = forms.ThemableChoiceField(
|
||||
choices=[(True, _('UP')),
|
||||
(False, _('DOWN'))],
|
||||
label=_("Admin State"))
|
||||
|
||||
failure_url = 'horizon:project:loadbalancers:index'
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(UpdateMonitor, self).__init__(request, *args, **kwargs)
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(UpdateMonitor, self).clean()
|
||||
delay = cleaned_data.get('delay')
|
||||
timeout = cleaned_data.get('timeout')
|
||||
if not delay >= timeout:
|
||||
msg = _('Delay must be greater than or equal to timeout')
|
||||
self._errors['delay'] = self.error_class([msg])
|
||||
return cleaned_data
|
||||
|
||||
def handle(self, request, context):
|
||||
context['admin_state_up'] = (context['admin_state_up'] == 'True')
|
||||
try:
|
||||
data = {'health_monitor': {
|
||||
'delay': context['delay'],
|
||||
'timeout': context['timeout'],
|
||||
'max_retries': context['max_retries'],
|
||||
'admin_state_up': context['admin_state_up']}}
|
||||
monitor = api.lbaas.pool_health_monitor_update(
|
||||
request, context['monitor_id'], **data)
|
||||
msg = _('Health monitor %s was successfully updated.')\
|
||||
% context['monitor_id']
|
||||
LOG.debug(msg)
|
||||
messages.success(request, msg)
|
||||
return monitor
|
||||
except Exception:
|
||||
msg = _('Failed to update health monitor %s')\
|
||||
% context['monitor_id']
|
||||
LOG.info(msg)
|
||||
redirect = reverse(self.failure_url)
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
@ -1,50 +0,0 @@
|
||||
# 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.utils.translation import ugettext_lazy as _
|
||||
|
||||
import horizon
|
||||
|
||||
from openstack_dashboard.api import neutron
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LoadBalancer(horizon.Panel):
|
||||
name = _("Load Balancers")
|
||||
slug = "loadbalancers"
|
||||
permissions = ('openstack.services.network',)
|
||||
|
||||
def allowed(self, context):
|
||||
request = context['request']
|
||||
if not request.user.has_perms(self.permissions):
|
||||
return False
|
||||
try:
|
||||
if not neutron.is_service_enabled(request,
|
||||
config_name='enable_lb',
|
||||
ext_name='lbaas'):
|
||||
return False
|
||||
except Exception:
|
||||
LOG.error("Call to list enabled services failed. This is likely "
|
||||
"due to a problem communicating with the Neutron "
|
||||
"endpoint. Load Balancers panel will not be displayed.")
|
||||
return False
|
||||
if not super(LoadBalancer, self).allowed(context):
|
||||
return False
|
||||
|
||||
LOG.warning(
|
||||
"DEPRECATION: LBaaS v1 dashboard in Horizon is deprecated "
|
||||
"in 'Newton' release and will be removed in 'Ocata' release. "
|
||||
"For more detail, check Horizon Newton release notes.")
|
||||
return True
|
@ -1,566 +0,0 @@
|
||||
# Copyright 2013, Big Switch Networks, 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.core.urlresolvers import reverse
|
||||
from django import shortcuts
|
||||
from django import template
|
||||
from django.template import defaultfilters as filters
|
||||
from django.utils import http
|
||||
from django.utils.http import urlencode
|
||||
from django.utils.translation import pgettext_lazy
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import ungettext_lazy
|
||||
|
||||
from horizon import conf
|
||||
from horizon import exceptions
|
||||
from horizon import messages
|
||||
from horizon import tables
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.dashboards.project.access_and_security.floating_ips \
|
||||
import workflows
|
||||
from openstack_dashboard import policy
|
||||
|
||||
|
||||
class AddPoolLink(tables.LinkAction):
|
||||
name = "addpool"
|
||||
verbose_name = _("Add Pool")
|
||||
url = "horizon:project:loadbalancers:addpool"
|
||||
classes = ("ajax-modal",)
|
||||
icon = "plus"
|
||||
policy_rules = (("network", "create_pool"),)
|
||||
|
||||
|
||||
class AddVipLink(tables.LinkAction):
|
||||
name = "addvip"
|
||||
verbose_name = _("Add VIP")
|
||||
classes = ("ajax-modal",)
|
||||
icon = "plus"
|
||||
policy_rules = (("network", "create_vip"),)
|
||||
|
||||
def get_link_url(self, pool):
|
||||
base_url = reverse("horizon:project:loadbalancers:addvip",
|
||||
kwargs={'pool_id': pool.id})
|
||||
return base_url
|
||||
|
||||
def allowed(self, request, datum=None):
|
||||
if datum and datum.vip_id:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class AddMemberLink(tables.LinkAction):
|
||||
name = "addmember"
|
||||
verbose_name = _("Add Member")
|
||||
url = "horizon:project:loadbalancers:addmember"
|
||||
classes = ("ajax-modal",)
|
||||
icon = "plus"
|
||||
policy_rules = (("network", "create_member"),)
|
||||
|
||||
|
||||
class AddMonitorLink(tables.LinkAction):
|
||||
name = "addmonitor"
|
||||
verbose_name = _("Add Monitor")
|
||||
url = "horizon:project:loadbalancers:addmonitor"
|
||||
classes = ("ajax-modal",)
|
||||
icon = "plus"
|
||||
policy_rules = (("network", "create_health_monitor"),)
|
||||
|
||||
|
||||
class DeleteVipLink(policy.PolicyTargetMixin, tables.Action):
|
||||
name = "deletevip"
|
||||
preempt = True
|
||||
verbose_name = _("Delete VIP")
|
||||
policy_rules = (("network", "delete_vip"),)
|
||||
action_type = "danger"
|
||||
|
||||
def allowed(self, request, datum=None):
|
||||
if datum and datum.vip_id:
|
||||
self.help_text = _("Deleting VIP %s from this pool "
|
||||
"cannot be undone.") % datum.vip_id
|
||||
return True
|
||||
return False
|
||||
|
||||
def single(self, table, request, obj_id):
|
||||
try:
|
||||
vip_id = api.lbaas.pool_get(request, obj_id).vip_id
|
||||
except Exception as e:
|
||||
exceptions.handle(request,
|
||||
_('Unable to locate VIP to delete. %s')
|
||||
% e)
|
||||
if vip_id is not None:
|
||||
try:
|
||||
api.lbaas.vip_delete(request, vip_id)
|
||||
messages.success(request, _('Deleted VIP %s') % vip_id)
|
||||
except Exception as e:
|
||||
exceptions.handle(request,
|
||||
_('Unable to delete VIP. %s') % e)
|
||||
|
||||
|
||||
class DeletePoolLink(policy.PolicyTargetMixin, tables.DeleteAction):
|
||||
name = "deletepool"
|
||||
policy_rules = (("network", "delete_pool"),)
|
||||
|
||||
@staticmethod
|
||||
def action_present(count):
|
||||
return ungettext_lazy(
|
||||
u"Delete Pool",
|
||||
u"Delete Pools",
|
||||
count
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def action_past(count):
|
||||
return ungettext_lazy(
|
||||
u"Scheduled deletion of Pool",
|
||||
u"Scheduled deletion of Pools",
|
||||
count
|
||||
)
|
||||
|
||||
def allowed(self, request, datum=None):
|
||||
if datum and datum.vip_id:
|
||||
return False
|
||||
return True
|
||||
|
||||
def delete(self, request, obj_id):
|
||||
try:
|
||||
api.lbaas.pool_delete(request, obj_id)
|
||||
except Exception as e:
|
||||
exceptions.handle(request,
|
||||
_('Unable to delete pool. %s') % e)
|
||||
|
||||
|
||||
class DeleteMonitorLink(policy.PolicyTargetMixin,
|
||||
tables.DeleteAction):
|
||||
name = "deletemonitor"
|
||||
policy_rules = (("network", "delete_health_monitor"),)
|
||||
|
||||
@staticmethod
|
||||
def action_present(count):
|
||||
return ungettext_lazy(
|
||||
u"Delete Monitor",
|
||||
u"Delete Monitors",
|
||||
count
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def action_past(count):
|
||||
return ungettext_lazy(
|
||||
u"Scheduled deletion of Monitor",
|
||||
u"Scheduled deletion of Monitors",
|
||||
count
|
||||
)
|
||||
|
||||
def delete(self, request, obj_id):
|
||||
try:
|
||||
api.lbaas.pool_health_monitor_delete(request, obj_id)
|
||||
except Exception as e:
|
||||
exceptions.handle(request,
|
||||
_('Unable to delete monitor. %s') % e)
|
||||
|
||||
|
||||
class DeleteMemberLink(policy.PolicyTargetMixin, tables.DeleteAction):
|
||||
name = "deletemember"
|
||||
policy_rules = (("network", "delete_member"),)
|
||||
|
||||
@staticmethod
|
||||
def action_present(count):
|
||||
return ungettext_lazy(
|
||||
u"Delete Member",
|
||||
u"Delete Members",
|
||||
count
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def action_past(count):
|
||||
return ungettext_lazy(
|
||||
u"Scheduled deletion of Member",
|
||||
u"Scheduled deletion of Members",
|
||||
count
|
||||
)
|
||||
|
||||
def delete(self, request, obj_id):
|
||||
try:
|
||||
api.lbaas.member_delete(request, obj_id)
|
||||
except Exception as e:
|
||||
exceptions.handle(request,
|
||||
_('Unable to delete member. %s') % e)
|
||||
|
||||
|
||||
class UpdatePoolLink(policy.PolicyTargetMixin, tables.LinkAction):
|
||||
name = "updatepool"
|
||||
verbose_name = _("Edit Pool")
|
||||
classes = ("ajax-modal", "btn-update",)
|
||||
policy_rules = (("network", "update_pool"),)
|
||||
|
||||
def get_link_url(self, pool):
|
||||
base_url = reverse("horizon:project:loadbalancers:updatepool",
|
||||
kwargs={'pool_id': pool.id})
|
||||
return base_url
|
||||
|
||||
|
||||
class UpdateVipLink(policy.PolicyTargetMixin, tables.LinkAction):
|
||||
name = "updatevip"
|
||||
verbose_name = _("Edit VIP")
|
||||
classes = ("ajax-modal", "btn-update",)
|
||||
policy_rules = (("network", "update_vip"),)
|
||||
|
||||
def get_link_url(self, pool):
|
||||
base_url = reverse("horizon:project:loadbalancers:updatevip",
|
||||
kwargs={'vip_id': pool.vip_id})
|
||||
return base_url
|
||||
|
||||
def allowed(self, request, datum=None):
|
||||
if datum and not datum.vip_id:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class UpdateMemberLink(policy.PolicyTargetMixin, tables.LinkAction):
|
||||
name = "updatemember"
|
||||
verbose_name = _("Edit Member")
|
||||
classes = ("ajax-modal", "btn-update",)
|
||||
policy_rules = (("network", "update_member"),)
|
||||
|
||||
def get_link_url(self, member):
|
||||
base_url = reverse("horizon:project:loadbalancers:updatemember",
|
||||
kwargs={'member_id': member.id})
|
||||
return base_url
|
||||
|
||||
|
||||
class UpdateMonitorLink(policy.PolicyTargetMixin, tables.LinkAction):
|
||||
name = "updatemonitor"
|
||||
verbose_name = _("Edit Monitor")
|
||||
classes = ("ajax-modal", "btn-update",)
|
||||
policy_rules = (("network", "update_health_monitor"),)
|
||||
|
||||
def get_link_url(self, monitor):
|
||||
base_url = reverse("horizon:project:loadbalancers:updatemonitor",
|
||||
kwargs={'monitor_id': monitor.id})
|
||||
return base_url
|
||||
|
||||
|
||||
class AddPMAssociationLink(policy.PolicyTargetMixin,
|
||||
tables.LinkAction):
|
||||
name = "addassociation"
|
||||
verbose_name = _("Associate Monitor")
|
||||
url = "horizon:project:loadbalancers:addassociation"
|
||||
classes = ("ajax-modal",)
|
||||
icon = "plus"
|
||||
policy_rules = (("network", "create_pool_health_monitor"),)
|
||||
|
||||
def allowed(self, request, datum=None):
|
||||
try:
|
||||
tenant_id = request.user.tenant_id
|
||||
monitors = api.lbaas.pool_health_monitor_list(request,
|
||||
tenant_id=tenant_id)
|
||||
for m in monitors:
|
||||
if m.id not in datum['health_monitors']:
|
||||
return True
|
||||
except Exception:
|
||||
exceptions.handle(request,
|
||||
_('Failed to retrieve health monitors.'))
|
||||
return False
|
||||
|
||||
|
||||
class DeletePMAssociationLink(policy.PolicyTargetMixin,
|
||||
tables.LinkAction):
|
||||
name = "deleteassociation"
|
||||
verbose_name = _("Disassociate Monitor")
|
||||
url = "horizon:project:loadbalancers:deleteassociation"
|
||||
classes = ("ajax-modal",)
|
||||
icon = "trash"
|
||||
policy_rules = (("network", "delete_pool_health_monitor"),)
|
||||
action_type = "danger"
|
||||
|
||||
def allowed(self, request, datum=None):
|
||||
if datum and not datum['health_monitors']:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class AddVIPFloatingIP(policy.PolicyTargetMixin, tables.LinkAction):
|
||||
"""Add floating ip to VIP
|
||||
|
||||
This class is extremely similar to AssociateIP from
|
||||
the instances page
|
||||
"""
|
||||
name = "associate"
|
||||
verbose_name = _("Associate Floating IP")
|
||||
url = "horizon:project:access_and_security:floating_ips:associate"
|
||||
classes = ("ajax-modal",)
|
||||
icon = "link"
|
||||
policy_rules = (("compute", "network:associate_floating_ip"),)
|
||||
|
||||
def allowed(self, request, pool):
|
||||
if not api.network.floating_ip_supported(request):
|
||||
return False
|
||||
if api.network.floating_ip_simple_associate_supported(request):
|
||||
return False
|
||||
if hasattr(pool, "vip") and pool.vip:
|
||||
vip = pool.vip
|
||||
return not (hasattr(vip, "fip") and vip.fip)
|
||||
return False
|
||||
|
||||
def get_link_url(self, datum):
|
||||
base_url = reverse(self.url)
|
||||
next_url = self.table.get_full_url()
|
||||
params = {
|
||||
workflows.IPAssociationWorkflow.redirect_param_name: next_url}
|
||||
if hasattr(datum, "vip") and datum.vip:
|
||||
vip = datum.vip
|
||||
params['port_id'] = vip.port_id
|
||||
params = urlencode(params)
|
||||
return "?".join([base_url, params])
|
||||
|
||||
|
||||
class RemoveVIPFloatingIP(policy.PolicyTargetMixin, tables.Action):
|
||||
"""Remove floating IP from VIP
|
||||
|
||||
This class is extremely similar to the project instance table
|
||||
SimpleDisassociateIP feature, but just different enough to not
|
||||
be able to share much code
|
||||
"""
|
||||
name = "disassociate"
|
||||
preempt = True
|
||||
icon = "unlink"
|
||||
verbose_name = _("Disassociate Floating IP")
|
||||
classes = ("btn-disassociate",)
|
||||
policy_rules = (("compute", "network:disassociate_floating_ip"),)
|
||||
action_type = "danger"
|
||||
|
||||
def allowed(self, request, pool):
|
||||
if not api.network.floating_ip_supported(request):
|
||||
return False
|
||||
if not conf.HORIZON_CONFIG["simple_ip_management"]:
|
||||
return False
|
||||
if hasattr(pool, "vip") and pool.vip:
|
||||
vip = pool.vip
|
||||
self.help_text = _('Floating IP will be removed '
|
||||
'from VIP "%s".') % vip.name
|
||||
return hasattr(vip, "fip") and vip.fip
|
||||
return False
|
||||
|
||||
def single(self, table, request, pool_id):
|
||||
try:
|
||||
pool = api.lbaas.pool_get(request, pool_id)
|
||||
fips = api.network.tenant_floating_ip_list(request)
|
||||
vip_fips = [fip for fip in fips
|
||||
if fip.port_id == pool.vip.port_id]
|
||||
if not vip_fips:
|
||||
messages.info(request, _("No floating IPs to disassociate."))
|
||||
else:
|
||||
api.network.floating_ip_disassociate(request,
|
||||
vip_fips[0].id)
|
||||
messages.success(request,
|
||||
_("Successfully disassociated "
|
||||
"floating IP: %s") % vip_fips[0].ip)
|
||||
except Exception:
|
||||
exceptions.handle(request,
|
||||
_("Unable to disassociate floating IP."))
|
||||
return shortcuts.redirect(request.get_full_path())
|
||||
|
||||
|
||||
class UpdatePoolsRow(tables.Row):
|
||||
ajax = True
|
||||
|
||||
def get_data(self, request, pool_id):
|
||||
pool = api.lbaas.pool_get(request, pool_id)
|
||||
try:
|
||||
vip = api.lbaas.vip_get(request, pool.vip_id)
|
||||
pool.vip = vip
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
subnet = api.neutron.subnet_get(request, pool.subnet_id)
|
||||
pool.subnet_name = subnet.cidr
|
||||
except Exception:
|
||||
pool.subnet_name = pool.subnet_id
|
||||
return pool
|
||||
|
||||
|
||||
STATUS_CHOICES = (
|
||||
("Active", True),
|
||||
("Down", True),
|
||||
("Error", False),
|
||||
)
|
||||
|
||||
|
||||
STATUS_DISPLAY_CHOICES = (
|
||||
("Active", pgettext_lazy("Current status of a Pool",
|
||||
u"Active")),
|
||||
("Down", pgettext_lazy("Current status of a Pool",
|
||||
u"Down")),
|
||||
("Error", pgettext_lazy("Current status of a Pool",
|
||||
u"Error")),
|
||||
("Created", pgettext_lazy("Current status of a Pool",
|
||||
u"Created")),
|
||||
("Pending_Create", pgettext_lazy("Current status of a Pool",
|
||||
u"Pending Create")),
|
||||
("Pending_Update", pgettext_lazy("Current status of a Pool",
|
||||
u"Pending Update")),
|
||||
("Pending_Delete", pgettext_lazy("Current status of a Pool",
|
||||
u"Pending Delete")),
|
||||
("Inactive", pgettext_lazy("Current status of a Pool",
|
||||
u"Inactive")),
|
||||
)
|
||||
|
||||
|
||||
ADMIN_STATE_DISPLAY_CHOICES = (
|
||||
("UP", pgettext_lazy("Admin state of a Load balancer", u"UP")),
|
||||
("DOWN", pgettext_lazy("Admin state of a Load balancer", u"DOWN")),
|
||||
)
|
||||
|
||||
|
||||
def get_vip_name(pool):
|
||||
if hasattr(pool, "vip") and pool.vip:
|
||||
template_name = 'project/loadbalancers/_pool_table_vip_cell.html'
|
||||
context = {"vip": pool.vip, }
|
||||
return template.loader.render_to_string(template_name, context)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def get_subnet(pool):
|
||||
if hasattr(pool, "subnet") and pool.subnet:
|
||||
template_name = 'project/loadbalancers/_pool_table_subnet_cell.html'
|
||||
context = {"subnet": pool.subnet}
|
||||
return template.loader.render_to_string(template_name, context)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
class PoolsTable(tables.DataTable):
|
||||
METHOD_DISPLAY_CHOICES = (
|
||||
("round_robin", pgettext_lazy("load balancing method",
|
||||
u"Round Robin")),
|
||||
("least_connections", pgettext_lazy("load balancing method",
|
||||
u"Least Connections")),
|
||||
("source_ip", pgettext_lazy("load balancing method",
|
||||
u"Source IP")),
|
||||
)
|
||||
|
||||
name = tables.Column("name_or_id",
|
||||
verbose_name=_("Name"),
|
||||
link="horizon:project:loadbalancers:pooldetails")
|
||||
description = tables.Column('description', verbose_name=_("Description"))
|
||||
provider = tables.Column('provider', verbose_name=_("Provider"),
|
||||
filters=(lambda v: filters.default(v, _('N/A')),))
|
||||
subnet_name = tables.Column(get_subnet, verbose_name=_("Subnet"))
|
||||
protocol = tables.Column('protocol', verbose_name=_("Protocol"))
|
||||
method = tables.Column('lb_method',
|
||||
verbose_name=_("LB Method"),
|
||||
display_choices=METHOD_DISPLAY_CHOICES)
|
||||
status = tables.Column('status',
|
||||
verbose_name=_("Status"),
|
||||
status=True,
|
||||
status_choices=STATUS_CHOICES,
|
||||
display_choices=STATUS_DISPLAY_CHOICES)
|
||||
vip_name = tables.Column(get_vip_name, verbose_name=_("VIP"))
|
||||
admin_state = tables.Column("admin_state",
|
||||
verbose_name=_("Admin State"),
|
||||
display_choices=ADMIN_STATE_DISPLAY_CHOICES)
|
||||
|
||||
class Meta(object):
|
||||
name = "poolstable"
|
||||
verbose_name = _("Pools")
|
||||
status_columns = ["status"]
|
||||
row_class = UpdatePoolsRow
|
||||
table_actions = (AddPoolLink, DeletePoolLink)
|
||||
row_actions = (UpdatePoolLink, AddVipLink, UpdateVipLink,
|
||||
DeleteVipLink, AddPMAssociationLink,
|
||||
DeletePMAssociationLink, DeletePoolLink,
|
||||
AddVIPFloatingIP, RemoveVIPFloatingIP)
|
||||
|
||||
|
||||
def get_pool_link(member):
|
||||
return reverse("horizon:project:loadbalancers:pooldetails",
|
||||
args=(http.urlquote(member.pool_id),))
|
||||
|
||||
|
||||
def get_member_link(member):
|
||||
return reverse("horizon:project:loadbalancers:memberdetails",
|
||||
args=(http.urlquote(member.id),))
|
||||
|
||||
|
||||
class UpdateMemberRow(tables.Row):
|
||||
ajax = True
|
||||
|
||||
def get_data(self, request, member_id):
|
||||
member = api.lbaas.member_get(request, member_id)
|
||||
try:
|
||||
pool = api.lbaas.pool_get(request, member.pool_id)
|
||||
member.pool_name = pool.name
|
||||
except Exception:
|
||||
member.pool_name = member.pool_id
|
||||
return member
|
||||
|
||||
|
||||
class MembersTable(tables.DataTable):
|
||||
address = tables.Column('address',
|
||||
verbose_name=_("IP Address"),
|
||||
link=get_member_link,
|
||||
attrs={'data-type': "ip"})
|
||||
protocol_port = tables.Column('protocol_port',
|
||||
verbose_name=_("Protocol Port"))
|
||||
weight = tables.Column('weight',
|
||||
verbose_name=_("Weight"))
|
||||
pool_name = tables.Column('pool_name',
|
||||
verbose_name=_("Pool"), link=get_pool_link)
|
||||
status = tables.Column('status',
|
||||
verbose_name=_("Status"),
|
||||
status=True,
|
||||
status_choices=STATUS_CHOICES,
|
||||
display_choices=STATUS_DISPLAY_CHOICES)
|
||||
admin_state = tables.Column("admin_state",
|
||||
verbose_name=_("Admin State"),
|
||||
display_choices=ADMIN_STATE_DISPLAY_CHOICES)
|
||||
|
||||
class Meta(object):
|
||||
name = "memberstable"
|
||||
verbose_name = _("Members")
|
||||
status_columns = ["status"]
|
||||
row_class = UpdateMemberRow
|
||||
table_actions = (AddMemberLink, DeleteMemberLink)
|
||||
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):
|
||||
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"))
|
||||
admin_state = tables.Column("admin_state",
|
||||
verbose_name=_("Admin State"),
|
||||
display_choices=ADMIN_STATE_DISPLAY_CHOICES)
|
||||
|
||||
class Meta(object):
|
||||
name = "monitorstable"
|
||||
verbose_name = _("Monitors")
|
||||
table_actions = (AddMonitorLink, DeleteMonitorLink)
|
||||
row_actions = (UpdateMonitorLink, DeleteMonitorLink)
|
@ -1,151 +0,0 @@
|
||||
# Copyright 2013, Big Switch Networks, 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 _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import tabs
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.dashboards.project.loadbalancers import tables
|
||||
|
||||
|
||||
class PoolsTab(tabs.TableTab):
|
||||
table_classes = (tables.PoolsTable,)
|
||||
name = _("Pools")
|
||||
slug = "pools"
|
||||
template_name = "horizon/common/_detail_table.html"
|
||||
|
||||
def get_poolstable_data(self):
|
||||
pools = []
|
||||
try:
|
||||
request = self.tab_group.request
|
||||
tenant_id = self.request.user.tenant_id
|
||||
pools = api.lbaas.pool_list(request,
|
||||
tenant_id=tenant_id)
|
||||
fips = None
|
||||
for pool in pools:
|
||||
if hasattr(pool, "vip") and pool.vip:
|
||||
if not fips:
|
||||
fips = api.network.tenant_floating_ip_list(request)
|
||||
vip_fip = [fip for fip in fips
|
||||
if fip.port_id == pool.vip.port_id]
|
||||
if vip_fip:
|
||||
pool.vip.fip = vip_fip[0]
|
||||
except Exception:
|
||||
exceptions.handle(self.tab_group.request,
|
||||
_('Unable to retrieve pools list.'))
|
||||
return pools
|
||||
|
||||
|
||||
class MembersTab(tabs.TableTab):
|
||||
table_classes = (tables.MembersTable,)
|
||||
name = _("Members")
|
||||
slug = "members"
|
||||
template_name = "horizon/common/_detail_table.html"
|
||||
|
||||
def get_memberstable_data(self):
|
||||
try:
|
||||
tenant_id = self.request.user.tenant_id
|
||||
members = api.lbaas.member_list(self.tab_group.request,
|
||||
tenant_id=tenant_id)
|
||||
except Exception:
|
||||
members = []
|
||||
exceptions.handle(self.tab_group.request,
|
||||
_('Unable to retrieve member list.'))
|
||||
return members
|
||||
|
||||
|
||||
class MonitorsTab(tabs.TableTab):
|
||||
table_classes = (tables.MonitorsTable,)
|
||||
name = _("Monitors")
|
||||
slug = "monitors"
|
||||
template_name = "horizon/common/_detail_table.html"
|
||||
|
||||
def get_monitorstable_data(self):
|
||||
try:
|
||||
tenant_id = self.request.user.tenant_id
|
||||
monitors = api.lbaas.pool_health_monitor_list(
|
||||
self.tab_group.request, tenant_id=tenant_id)
|
||||
except Exception:
|
||||
monitors = []
|
||||
exceptions.handle(self.tab_group.request,
|
||||
_('Unable to retrieve monitor list.'))
|
||||
return monitors
|
||||
|
||||
|
||||
class LoadBalancerTabs(tabs.TabGroup):
|
||||
slug = "lbtabs"
|
||||
tabs = (PoolsTab, MembersTab, MonitorsTab)
|
||||
sticky = True
|
||||
|
||||
|
||||
class PoolDetailsTab(tabs.Tab):
|
||||
name = _("Pool Details")
|
||||
slug = "pooldetails"
|
||||
template_name = "project/loadbalancers/_pool_details.html"
|
||||
|
||||
def get_context_data(self, request):
|
||||
pool = self.tab_group.kwargs['pool']
|
||||
return {'pool': pool}
|
||||
|
||||
|
||||
class VipDetailsTab(tabs.Tab):
|
||||
name = _("VIP Details")
|
||||
slug = "vipdetails"
|
||||
template_name = "project/loadbalancers/_vip_details.html"
|
||||
|
||||
def get_context_data(self, request):
|
||||
vip = self.tab_group.kwargs['vip']
|
||||
return {'vip': vip}
|
||||
|
||||
|
||||
class MemberDetailsTab(tabs.Tab):
|
||||
name = _("Member Details")
|
||||
slug = "memberdetails"
|
||||
template_name = "project/loadbalancers/_member_details.html"
|
||||
|
||||
def get_context_data(self, request):
|
||||
member = self.tab_group.kwargs['member']
|
||||
return {'member': member}
|
||||
|
||||
|
||||
class MonitorDetailsTab(tabs.Tab):
|
||||
name = _("Monitor Details")
|
||||
slug = "monitordetails"
|
||||
template_name = "project/loadbalancers/_monitor_details.html"
|
||||
|
||||
def get_context_data(self, request):
|
||||
monitor = self.tab_group.kwargs['monitor']
|
||||
return {'monitor': monitor}
|
||||
|
||||
|
||||
class PoolDetailsTabs(tabs.TabGroup):
|
||||
slug = "pooltabs"
|
||||
tabs = (PoolDetailsTab,)
|
||||
|
||||
|
||||
class VipDetailsTabs(tabs.TabGroup):
|
||||
slug = "viptabs"
|
||||
tabs = (VipDetailsTab,)
|
||||
|
||||
|
||||
class MemberDetailsTabs(tabs.TabGroup):
|
||||
slug = "membertabs"
|
||||
tabs = (MemberDetailsTab,)
|
||||
|
||||
|
||||
class MonitorDetailsTabs(tabs.TabGroup):
|
||||
slug = "monitortabs"
|
||||
tabs = (MonitorDetailsTab,)
|
@ -1,27 +0,0 @@
|
||||
{% load i18n %}
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
Assign a name and description for the pool. Choose one subnet where all
|
||||
members of this pool must be on. Select the protocol and load balancing
|
||||
method for this pool. Admin State is UP (checked) by default.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
|
||||
<p><label>{% blocktrans trimmed %}Load Balancing Method{% endblocktrans %}</label><br/>
|
||||
{% blocktrans trimmed %}
|
||||
Use one of these load balancing methods to distribute incoming requests:
|
||||
{% endblocktrans %}
|
||||
<div>
|
||||
<dl>
|
||||
<dt>{% blocktrans trimmed %}Round robin{% endblocktrans %}</dt>
|
||||
<dd>{% blocktrans trimmed %}Rotates requests evenly between multiple
|
||||
instances.{% endblocktrans %}</dd>
|
||||
<dt>{% blocktrans trimmed %}Source IP{% endblocktrans %}</dt>
|
||||
<dd>{% blocktrans trimmed %}Requests from a unique source IP address are consistently
|
||||
directed to the same instance.{% endblocktrans %}</dd>
|
||||
<dt>{% blocktrans trimmed %}Least connections{% endblocktrans %}</dt>
|
||||
<dd>{% blocktrans trimmed %}Allocates requests to the instance with the least number of
|
||||
active connections.{% endblocktrans %}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</p>
|
@ -1,16 +0,0 @@
|
||||
{% load i18n %}
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
Create a VIP for this pool. Assign a name, description, IP address, port,
|
||||
and maximum connections allowed for the VIP. Choose the protocol and session persistence
|
||||
method for the VIP. Admin State is UP (checked) by default.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
|
||||
<p><label>{% blocktrans trimmed %}IP address{% endblocktrans %}</label><br/>
|
||||
{% blocktrans trimmed %}
|
||||
When no IP address is provided, the VIP will obtain an address from
|
||||
the selected subnet. If a specific IP address is desired, it may be provided and
|
||||
must also be an address within the selected subnet.
|
||||
{% endblocktrans %}
|
||||
</p>
|
@ -1,30 +0,0 @@
|
||||
{% load i18n sizeformat parse_date %}
|
||||
|
||||
<div class="detail">
|
||||
<dl class="dl-horizontal">
|
||||
<dt>{% trans "ID" %}</dt>
|
||||
<dd>{{ member.id }}</dd>
|
||||
|
||||
<dt>{% trans "Project ID" %}</dt>
|
||||
<dd>{{ member.tenant_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>
|
||||
|
||||
<dt>{% trans "Protocol Port" %}</dt>
|
||||
<dd>{{ member.protocol_port }}</dd>
|
||||
|
||||
<dt>{% trans "Weight" %}</dt>
|
||||
<dd>{{ member.weight }}</dd>
|
||||
|
||||
<dt>{% trans "Admin State Up" %}</dt>
|
||||
<dd>{{ member.admin_state_up|yesno|capfirst }}</dd>
|
||||
|
||||
<dt>{% trans "Status" %}</dt>
|
||||
<dd>{{ member.status }}</dd>
|
||||
</dl>
|
||||
</div>
|
@ -1,5 +0,0 @@
|
||||
{% block main %}
|
||||
|
||||
{{ table.render }}
|
||||
|
||||
{% endblock %}
|
@ -1,51 +0,0 @@
|
||||
{% load i18n sizeformat parse_date %}
|
||||
|
||||
<div class="detail">
|
||||
<dl class="dl-horizontal">
|
||||
<dt>{% trans "ID" %}</dt>
|
||||
<dd>{{ monitor.id }}</dd>
|
||||
|
||||
<dt>{% trans "Project ID" %}</dt>
|
||||
<dd>{{ monitor.tenant_id }}</dd>
|
||||
|
||||
<dt>{% trans "Type" %}</dt>
|
||||
<dd>{{ monitor.type }}</dd>
|
||||
|
||||
<dt>{% trans "Delay" %}</dt>
|
||||
<dd>{{ monitor.delay }}</dd>
|
||||
|
||||
<dt>{% trans "Timeout" %}</dt>
|
||||
<dd>{{ monitor.timeout }}</dd>
|
||||
|
||||
<dt>{% trans "Max Retries" %}</dt>
|
||||
<dd>{{ monitor.max_retries }}</dd>
|
||||
|
||||
{% if monitor.type == 'HTTP' or monitor.type == 'HTTPS' %}
|
||||
<dt>{% trans "HTTP Method" %}</dt>
|
||||
<dd>{{ monitor.http_method }}</dd>
|
||||
|
||||
<dt>{% trans "URL Path" %}</dt>
|
||||
<dd>{{ monitor.url_path }}</dd>
|
||||
|
||||
<dt>{% trans "Expected Codes" %}</dt>
|
||||
<dd>{{ monitor.expected_codes }}</dd>
|
||||
{% endif %}
|
||||
|
||||
<dt>{% trans "Admin State Up" %}</dt>
|
||||
<dd>{{ monitor.admin_state_up|yesno|capfirst }}</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="dl-horizontal">
|
||||
<h4>{% trans "Pools" %}</h4>
|
||||
<hr class="header_rule">
|
||||
{% if monitor.pools %}
|
||||
<ul>
|
||||
{% for pool in monitor.pools %}
|
||||
{% url 'horizon:project:loadbalancers:pooldetails' pool.id as pool_url %}
|
||||
<li><a href="{{ pool_url }}">{{ pool.name_or_id }}</a></li>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% trans "None" %}
|
||||
{% endif %}
|
||||
</dl>
|
||||
</div>
|
@ -1,5 +0,0 @@
|
||||
{% block main %}
|
||||
|
||||
{{ table.render }}
|
||||
|
||||
{% endblock %}
|
@ -1,74 +0,0 @@
|
||||
{% load i18n sizeformat parse_date %}
|
||||
|
||||
<div class="detail">
|
||||
<dl class="dl-horizontal">
|
||||
<dt>{% trans "ID" %}</dt>
|
||||
<dd>{{ pool.id }}</dd>
|
||||
|
||||
<dt>{% trans "Name" %}</dt>
|
||||
<dd>{{ pool.name|default:_("-") }}</dd>
|
||||
|
||||
<dt>{% trans "Description" %}</dt>
|
||||
<dd>{{ pool.description|default:_("-") }}</dd>
|
||||
|
||||
<dt>{% trans "Project ID" %}</dt>
|
||||
<dd>{{ pool.tenant_id }}</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 "None" %}</dd>
|
||||
{% endif %}
|
||||
|
||||
<dt>{% trans "Provider" %}</dt>
|
||||
<dd>{{ pool.provider|default:_("N/A") }}</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>
|
||||
|
||||
<dt>{% trans "Load Balancing Method" %}</dt>
|
||||
<dd>{{ pool.lb_method }}</dd>
|
||||
|
||||
<dt>{% trans "Admin State Up" %}</dt>
|
||||
<dd>{{ pool.admin_state_up|yesno|capfirst }}</dd>
|
||||
|
||||
<dt>{% trans "Status" %}</dt>
|
||||
<dd>{{ pool.status }}</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="dl-horizontal">
|
||||
<h4>{% trans "Members" %}</h4>
|
||||
<hr class="header_rule">
|
||||
{% if pool.members %}
|
||||
<ul>
|
||||
{% for member in pool.members %}
|
||||
{% url 'horizon:project:loadbalancers:memberdetails' member.id as member_url %}
|
||||
<li><a href="{{ member_url }}">{{ member.address }}:{{ member.protocol_port }}</a></li>
|
||||
{% endfor %}
|
||||
<ul>
|
||||
{% else %}
|
||||
{% trans "None" %}
|
||||
{% endif %}
|
||||
</dl>
|
||||
|
||||
<dl class="dl-horizontal">
|
||||
<h4>{% trans "Health Monitors" %}</h4>
|
||||
<hr class="header_rule">
|
||||
{% if pool.health_monitors %}
|
||||
<ul>
|
||||
{% for monitor in pool.health_monitors %}
|
||||
{% url 'horizon:project:loadbalancers:monitordetails' monitor.id as monitor_url %}
|
||||
<li><a href="{{ monitor_url }}">{{ monitor.display_name }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
{% trans "None" %}
|
||||
{% endif %}
|
||||
</dl>
|
||||
</div>
|
@ -1,8 +0,0 @@
|
||||
<ul>
|
||||
<li>
|
||||
{% if subnet.name|length > 0 %}
|
||||
<b>{{ subnet.name }}</b>
|
||||
{% endif %}
|
||||
{{ subnet.cidr }}
|
||||
</li>
|
||||
</ul>
|
@ -1,9 +0,0 @@
|
||||
{% load i18n %}
|
||||
{% url 'horizon:project:loadbalancers:vipdetails' vip.id as vip_url %}
|
||||
<a href="{{ vip_url }}">{{ vip.name }}</a>
|
||||
<br/>
|
||||
{% trans "Address:" %} {{ vip.address }}
|
||||
{% if vip.fip %}
|
||||
<br/>
|
||||
{% trans "Floating IP:" %} {{ vip.fip.floating_ip_address }}
|
||||
{% endif %}
|
@ -1,5 +0,0 @@
|
||||
{% block main %}
|
||||
|
||||
{{ poolstable.render }}
|
||||
|
||||
{% endblock %}
|
@ -1,7 +0,0 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block modal-body-right %}
|
||||
<h3>{% trans "Description:" %}</h3>
|
||||
<p>{% trans "You may update member attributes here: edit pool, weight or admin state." %}</p>
|
||||
{% endblock %}
|
@ -1,7 +0,0 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block modal-body-right %}
|
||||
<h3>{% trans "Description:" %}</h3>
|
||||
<p>{% trans "You may update health monitor attributes here: edit delay, timeout, max retries or admin state." %}</p>
|
||||
{% endblock %}
|
@ -1,7 +0,0 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block modal-body-right %}
|
||||
<h3>{% trans "Description:" %}</h3>
|
||||
<p>{% trans "You may update pool attributes here: edit name, description, load balancing method or admin state." %}</p>
|
||||
{% endblock %}
|
@ -1,7 +0,0 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block modal-body-right %}
|
||||
<h3>{% trans "Description:" %}</h3>
|
||||
<p>{% trans "You may update VIP attributes here: edit name, description, pool, session persistence, connection limit or admin state." %}</p>
|
||||
{% endblock %}
|
@ -1,67 +0,0 @@
|
||||
{% load i18n sizeformat parse_date %}
|
||||
|
||||
<div class="detail">
|
||||
<dl class="dl-horizontal">
|
||||
<dt>{% trans "ID" %}</dt>
|
||||
<dd>{{ vip.id }}</dd>
|
||||
|
||||
<dt>{% trans "Name" %}</dt>
|
||||
<dd>{{ vip.name|default:_("-") }}</dd>
|
||||
|
||||
<dt>{% trans "Description" %}</dt>
|
||||
<dd>{{ vip.description|default:_("-") }}</dd>
|
||||
|
||||
<dt>{% trans "Project ID" %}</dt>
|
||||
<dd>{{ vip.tenant_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>
|
||||
|
||||
{% if vip.fip %}
|
||||
<dt>{% trans "Floating IP" %}</dt>
|
||||
<dd>{{ vip.fip.floating_ip_address }}</dd>
|
||||
{% endif %}
|
||||
|
||||
<dt>{% trans "Protocol Port" %}</dt>
|
||||
<dd>{{ vip.protocol_port }}</dd>
|
||||
|
||||
<dt>{% trans "Protocol" %}</dt>
|
||||
<dd>{{ vip.protocol }}</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>
|
||||
{% 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 %}
|
||||
<dd>
|
||||
{% blocktrans with persistence_type=vip.session_persistence.type %}Type: {{ persistence_type }}{% endblocktrans %}
|
||||
</dd>
|
||||
|
||||
{% if vip.session_persistence.cookie_name %}
|
||||
<dd>
|
||||
{% blocktrans with cookie_name=vip.session_persistence.cookie_name %}Cookie Name: {{ cookie_name }}{% endblocktrans %}
|
||||
</dd>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<dd>{% trans "None" %}</dd>
|
||||
{% endif %}
|
||||
|
||||
<dt>{% trans "Connection Limit" %}</dt>
|
||||
<dd>{{ vip.connection_limit }}</dd>
|
||||
|
||||
<dt>{% trans "Admin State Up" %}</dt>
|
||||
<dd>{{ vip.admin_state_up|yesno|capfirst }}</dd>
|
||||
|
||||
<dt>{% trans "Status" %}</dt>
|
||||
<dd>{{ vip.status }}</dd>
|
||||
</dl>
|
||||
</div>
|
@ -1,11 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Load Balancer" %}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
{{ tab_group.render }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -1,7 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Edit Member" %}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'project/loadbalancers/_updatemember.html' %}
|
||||
{% endblock %}
|
@ -1,7 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Edit Monitor" %}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'project/loadbalancers/_updatemonitor.html' %}
|
||||
{% endblock %}
|
@ -1,7 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Edit Pool" %}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'project/loadbalancers/_updatepool.html' %}
|
||||
{% endblock %}
|
@ -1,7 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Edit VIP" %}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'project/loadbalancers/_updatevip.html' %}
|
||||
{% endblock %}
|
File diff suppressed because it is too large
Load Diff
@ -1,50 +0,0 @@
|
||||
# Copyright 2013, Big Switch Networks, 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.conf.urls import url
|
||||
|
||||
from openstack_dashboard.dashboards.project.loadbalancers import views
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', views.IndexView.as_view(), name='index'),
|
||||
url(r'^\?tab=lbtabs__members$', views.IndexView.as_view(), name='members'),
|
||||
url(r'^\?tab=lbtabs__monitors$',
|
||||
views.IndexView.as_view(), name='monitors'),
|
||||
url(r'^addpool$', views.AddPoolView.as_view(), name='addpool'),
|
||||
url(r'^updatepool/(?P<pool_id>[^/]+)/$',
|
||||
views.UpdatePoolView.as_view(), name='updatepool'),
|
||||
url(r'^addvip/(?P<pool_id>[^/]+)/$',
|
||||
views.AddVipView.as_view(), name='addvip'),
|
||||
url(r'^updatevip/(?P<vip_id>[^/]+)/$',
|
||||
views.UpdateVipView.as_view(), name='updatevip'),
|
||||
url(r'^addmember$', views.AddMemberView.as_view(), name='addmember'),
|
||||
url(r'^updatemember/(?P<member_id>[^/]+)/$',
|
||||
views.UpdateMemberView.as_view(), name='updatemember'),
|
||||
url(r'^addmonitor$', views.AddMonitorView.as_view(), name='addmonitor'),
|
||||
url(r'^updatemonitor/(?P<monitor_id>[^/]+)/$',
|
||||
views.UpdateMonitorView.as_view(), name='updatemonitor'),
|
||||
url(r'^association/add/(?P<pool_id>[^/]+)/$',
|
||||
views.AddPMAssociationView.as_view(), name='addassociation'),
|
||||
url(r'^association/delete/(?P<pool_id>[^/]+)/$',
|
||||
views.DeletePMAssociationView.as_view(), name='deleteassociation'),
|
||||
url(r'^pool/(?P<pool_id>[^/]+)/$',
|
||||
views.PoolDetailsView.as_view(), name='pooldetails'),
|
||||
url(r'^vip/(?P<vip_id>[^/]+)/$',
|
||||
views.VipDetailsView.as_view(), name='vipdetails'),
|
||||
url(r'^member/(?P<member_id>[^/]+)/$',
|
||||
views.MemberDetailsView.as_view(), name='memberdetails'),
|
||||
url(r'^monitor/(?P<monitor_id>[^/]+)/$',
|
||||
views.MonitorDetailsView.as_view(), name='monitordetails'),
|
||||
]
|
@ -1,31 +0,0 @@
|
||||
# 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
|
@ -1,422 +0,0 @@
|
||||
# Copyright 2013, Big Switch Networks, 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.core.urlresolvers import reverse
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import tabs
|
||||
from horizon.utils import memoized
|
||||
from horizon import workflows
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.dashboards.project.loadbalancers \
|
||||
import forms as project_forms
|
||||
from openstack_dashboard.dashboards.project.loadbalancers \
|
||||
import tables as project_tables
|
||||
from openstack_dashboard.dashboards.project.loadbalancers \
|
||||
import tabs as project_tabs
|
||||
from openstack_dashboard.dashboards.project.loadbalancers import utils
|
||||
from openstack_dashboard.dashboards.project.loadbalancers \
|
||||
import workflows as project_workflows
|
||||
|
||||
|
||||
class IndexView(tabs.TabbedTableView):
|
||||
tab_group_class = (project_tabs.LoadBalancerTabs)
|
||||
template_name = 'project/loadbalancers/details_tabs.html'
|
||||
page_title = _("Load Balancer")
|
||||
|
||||
|
||||
class AddPoolView(workflows.WorkflowView):
|
||||
workflow_class = project_workflows.AddPool
|
||||
|
||||
|
||||
class AddVipView(workflows.WorkflowView):
|
||||
workflow_class = project_workflows.AddVip
|
||||
|
||||
def get_initial(self):
|
||||
initial = super(AddVipView, self).get_initial()
|
||||
initial['pool_id'] = self.kwargs['pool_id']
|
||||
try:
|
||||
pool = api.lbaas.pool_get(self.request, initial['pool_id'])
|
||||
initial['subnet'] = api.neutron.subnet_get(
|
||||
self.request, pool.subnet_id).cidr
|
||||
except Exception as e:
|
||||
initial['subnet'] = ''
|
||||
msg = _('Unable to retrieve pool subnet. %s') % e
|
||||
exceptions.handle(self.request, msg)
|
||||
return initial
|
||||
|
||||
|
||||
class AddMemberView(workflows.WorkflowView):
|
||||
workflow_class = project_workflows.AddMember
|
||||
|
||||
|
||||
class AddMonitorView(workflows.WorkflowView):
|
||||
workflow_class = project_workflows.AddMonitor
|
||||
|
||||
|
||||
class PoolDetailsView(tabs.TabView):
|
||||
tab_group_class = project_tabs.PoolDetailsTabs
|
||||
template_name = 'horizon/common/_detail.html'
|
||||
page_title = "{{ pool.name|default:pool.id }}"
|
||||
|
||||
@memoized.memoized_method
|
||||
def get_data(self):
|
||||
pid = self.kwargs['pool_id']
|
||||
|
||||
try:
|
||||
pool = api.lbaas.pool_get(self.request, pid)
|
||||
except Exception:
|
||||
pool = []
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve pool details.'))
|
||||
else:
|
||||
for monitor in pool.health_monitors:
|
||||
display_name = utils.get_monitor_display_name(monitor)
|
||||
setattr(monitor, 'display_name', display_name)
|
||||
|
||||
return pool
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(PoolDetailsView, self).get_context_data(**kwargs)
|
||||
pool = self.get_data()
|
||||
context['pool'] = pool
|
||||
table = project_tables.PoolsTable(self.request)
|
||||
context["url"] = self.get_redirect_url()
|
||||
context["actions"] = table.render_row_actions(pool)
|
||||
return context
|
||||
|
||||
def get_tabs(self, request, *args, **kwargs):
|
||||
pool = self.get_data()
|
||||
return self.tab_group_class(self.request, pool=pool, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def get_redirect_url():
|
||||
return reverse_lazy("horizon:project:loadbalancers:index")
|
||||
|
||||
|
||||
class VipDetailsView(tabs.TabView):
|
||||
tab_group_class = project_tabs.VipDetailsTabs
|
||||
template_name = 'horizon/common/_detail.html'
|
||||
page_title = "{{ vip.name|default:vip_id }}"
|
||||
|
||||
@memoized.memoized_method
|
||||
def get_data(self):
|
||||
vid = self.kwargs['vip_id']
|
||||
vip = []
|
||||
try:
|
||||
vip = api.lbaas.vip_get(self.request, vid)
|
||||
fips = api.network.tenant_floating_ip_list(self.request)
|
||||
vip_fip = [fip for fip in fips
|
||||
if fip.port_id == vip.port.id]
|
||||
if vip_fip:
|
||||
vip.fip = vip_fip[0]
|
||||
except Exception:
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve VIP details.'))
|
||||
return vip
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(VipDetailsView, self).get_context_data(**kwargs)
|
||||
vip = self.get_data()
|
||||
context['vip'] = vip
|
||||
vip_nav = vip.pool.name_or_id
|
||||
breadcrumb = [
|
||||
(vip_nav,
|
||||
reverse('horizon:project:loadbalancers:vipdetails',
|
||||
args=(vip.id,))),
|
||||
(_("VIP"), None)
|
||||
]
|
||||
context["custom_breadcrumb"] = breadcrumb
|
||||
return context
|
||||
|
||||
def get_tabs(self, request, *args, **kwargs):
|
||||
vip = self.get_data()
|
||||
return self.tab_group_class(request, vip=vip, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def get_redirect_url():
|
||||
return reverse("horizon:project:loadbalancers:index")
|
||||
|
||||
|
||||
class MemberDetailsView(tabs.TabView):
|
||||
tab_group_class = project_tabs.MemberDetailsTabs
|
||||
template_name = 'horizon/common/_detail.html'
|
||||
page_title = "{{ member.name|default:member.id }}"
|
||||
|
||||
@memoized.memoized_method
|
||||
def get_data(self):
|
||||
mid = self.kwargs['member_id']
|
||||
try:
|
||||
return api.lbaas.member_get(self.request, mid)
|
||||
except Exception:
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve member details.'))
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(MemberDetailsView, self).get_context_data(**kwargs)
|
||||
member = self.get_data()
|
||||
context['member'] = member
|
||||
member_nav = member.pool.name_or_id
|
||||
breadcrumb = [
|
||||
(member_nav,
|
||||
reverse('horizon:project:loadbalancers:pooldetails',
|
||||
args=(member.pool.id,))),
|
||||
(_("Members"), reverse('horizon:project:loadbalancers:members')),
|
||||
]
|
||||
context["custom_breadcrumb"] = breadcrumb
|
||||
table = project_tables.MembersTable(self.request)
|
||||
context["url"] = self.get_redirect_url()
|
||||
context["actions"] = table.render_row_actions(member)
|
||||
return context
|
||||
|
||||
def get_tabs(self, request, *args, **kwargs):
|
||||
member = self.get_data()
|
||||
return self.tab_group_class(request, member=member, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def get_redirect_url():
|
||||
return reverse_lazy("horizon:project:loadbalancers:index")
|
||||
|
||||
|
||||
class MonitorDetailsView(tabs.TabView):
|
||||
tab_group_class = project_tabs.MonitorDetailsTabs
|
||||
template_name = 'horizon/common/_detail.html'
|
||||
page_title = "{{ monitor.name|default:monitor.id }}"
|
||||
|
||||
@memoized.memoized_method
|
||||
def get_data(self):
|
||||
mid = self.kwargs['monitor_id']
|
||||
try:
|
||||
return api.lbaas.pool_health_monitor_get(self.request, mid)
|
||||
except Exception:
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve monitor details.'))
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(MonitorDetailsView, self).get_context_data(**kwargs)
|
||||
monitor = self.get_data()
|
||||
context['monitor'] = monitor
|
||||
breadcrumb = [
|
||||
(_("Monitors"), reverse('horizon:project:loadbalancers:monitors')),
|
||||
]
|
||||
context["custom_breadcrumb"] = breadcrumb
|
||||
table = project_tables.MonitorsTable(self.request)
|
||||
context["url"] = self.get_redirect_url()
|
||||
context["actions"] = table.render_row_actions(monitor)
|
||||
return context
|
||||
|
||||
def get_tabs(self, request, *args, **kwargs):
|
||||
monitor = self.get_data()
|
||||
return self.tab_group_class(request, monitor=monitor, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def get_redirect_url():
|
||||
return reverse_lazy("horizon:project:loadbalancers:index")
|
||||
|
||||
|
||||
class UpdatePoolView(forms.ModalFormView):
|
||||
form_class = project_forms.UpdatePool
|
||||
form_id = "update_pool_form"
|
||||
modal_header = _("Edit Pool")
|
||||
template_name = "project/loadbalancers/updatepool.html"
|
||||
context_object_name = 'pool'
|
||||
submit_label = _("Save Changes")
|
||||
submit_url = "horizon:project:loadbalancers:updatepool"
|
||||
success_url = reverse_lazy("horizon:project:loadbalancers:index")
|
||||
page_title = _("Edit Pool")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(UpdatePoolView, self).get_context_data(**kwargs)
|
||||
context["pool_id"] = self.kwargs['pool_id']
|
||||
args = (self.kwargs['pool_id'],)
|
||||
context['submit_url'] = reverse(self.submit_url, args=args)
|
||||
return context
|
||||
|
||||
@memoized.memoized_method
|
||||
def _get_object(self, *args, **kwargs):
|
||||
pool_id = self.kwargs['pool_id']
|
||||
try:
|
||||
return api.lbaas.pool_get(self.request, pool_id)
|
||||
except Exception as e:
|
||||
redirect = self.success_url
|
||||
msg = _('Unable to retrieve pool details. %s') % e
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
|
||||
def get_initial(self):
|
||||
pool = self._get_object()
|
||||
return {'name': pool['name'],
|
||||
'pool_id': pool['id'],
|
||||
'description': pool['description'],
|
||||
'lb_method': pool['lb_method'],
|
||||
'admin_state_up': pool['admin_state_up']}
|
||||
|
||||
|
||||
class UpdateVipView(forms.ModalFormView):
|
||||
form_class = project_forms.UpdateVip
|
||||
form_id = "update_vip_form"
|
||||
modal_header = _("Edit VIP")
|
||||
template_name = "project/loadbalancers/updatevip.html"
|
||||
context_object_name = 'vip'
|
||||
submit_label = _("Save Changes")
|
||||
submit_url = "horizon:project:loadbalancers:updatevip"
|
||||
success_url = reverse_lazy("horizon:project:loadbalancers:index")
|
||||
page_title = _("Edit VIP")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(UpdateVipView, self).get_context_data(**kwargs)
|
||||
context["vip_id"] = self.kwargs['vip_id']
|
||||
args = (self.kwargs['vip_id'],)
|
||||
context['submit_url'] = reverse(self.submit_url, args=args)
|
||||
return context
|
||||
|
||||
@memoized.memoized_method
|
||||
def _get_object(self, *args, **kwargs):
|
||||
vip_id = self.kwargs['vip_id']
|
||||
try:
|
||||
return api.lbaas.vip_get(self.request, vip_id)
|
||||
except Exception as e:
|
||||
redirect = self.success_url
|
||||
msg = _('Unable to retrieve VIP details. %s') % e
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
|
||||
def get_initial(self):
|
||||
vip = self._get_object()
|
||||
persistence = getattr(vip, 'session_persistence', None)
|
||||
if persistence:
|
||||
stype = persistence['type']
|
||||
if stype == 'APP_COOKIE':
|
||||
cookie = persistence['cookie_name']
|
||||
else:
|
||||
cookie = ''
|
||||
else:
|
||||
stype = ''
|
||||
cookie = ''
|
||||
|
||||
return {'name': vip['name'],
|
||||
'vip_id': vip['id'],
|
||||
'description': vip['description'],
|
||||
'pool_id': vip['pool_id'],
|
||||
'session_persistence': stype,
|
||||
'cookie_name': cookie,
|
||||
'connection_limit': vip['connection_limit'],
|
||||
'admin_state_up': vip['admin_state_up']}
|
||||
|
||||
|
||||
class UpdateMemberView(forms.ModalFormView):
|
||||
form_class = project_forms.UpdateMember
|
||||
form_id = "update_pool_form"
|
||||
modal_header = _("Edit Member")
|
||||
template_name = "project/loadbalancers/updatemember.html"
|
||||
context_object_name = 'member'
|
||||
submit_label = _("Save Changes")
|
||||
submit_url = "horizon:project:loadbalancers:updatemember"
|
||||
success_url = reverse_lazy("horizon:project:loadbalancers:index")
|
||||
page_title = _("Edit Member")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(UpdateMemberView, self).get_context_data(**kwargs)
|
||||
context["member_id"] = self.kwargs['member_id']
|
||||
args = (self.kwargs['member_id'],)
|
||||
context['submit_url'] = reverse(self.submit_url, args=args)
|
||||
return context
|
||||
|
||||
@memoized.memoized_method
|
||||
def _get_object(self, *args, **kwargs):
|
||||
member_id = self.kwargs['member_id']
|
||||
try:
|
||||
return api.lbaas.member_get(self.request, member_id)
|
||||
except Exception as e:
|
||||
redirect = self.success_url
|
||||
msg = _('Unable to retrieve member details. %s') % e
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
|
||||
def get_initial(self):
|
||||
member = self._get_object()
|
||||
return {'member_id': member['id'],
|
||||
'pool_id': member['pool_id'],
|
||||
'weight': member['weight'],
|
||||
'admin_state_up': member['admin_state_up']}
|
||||
|
||||
|
||||
class UpdateMonitorView(forms.ModalFormView):
|
||||
form_class = project_forms.UpdateMonitor
|
||||
form_id = "update_monitor_form"
|
||||
modal_header = _("Edit Monitor")
|
||||
template_name = "project/loadbalancers/updatemonitor.html"
|
||||
context_object_name = 'monitor'
|
||||
submit_label = _("Save Changes")
|
||||
submit_url = "horizon:project:loadbalancers:updatemonitor"
|
||||
success_url = reverse_lazy("horizon:project:loadbalancers:index")
|
||||
page_title = _("Edit Monitor")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(UpdateMonitorView, self).get_context_data(**kwargs)
|
||||
context["monitor_id"] = self.kwargs['monitor_id']
|
||||
args = (self.kwargs['monitor_id'],)
|
||||
context['submit_url'] = reverse(self.submit_url, args=args)
|
||||
return context
|
||||
|
||||
@memoized.memoized_method
|
||||
def _get_object(self, *args, **kwargs):
|
||||
monitor_id = self.kwargs['monitor_id']
|
||||
try:
|
||||
return api.lbaas.pool_health_monitor_get(self.request, monitor_id)
|
||||
except Exception as e:
|
||||
redirect = self.success_url
|
||||
msg = _('Unable to retrieve health monitor details. %s') % e
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
|
||||
def get_initial(self):
|
||||
monitor = self._get_object()
|
||||
return {'monitor_id': monitor['id'],
|
||||
'delay': monitor['delay'],
|
||||
'timeout': monitor['timeout'],
|
||||
'max_retries': monitor['max_retries'],
|
||||
'admin_state_up': monitor['admin_state_up']}
|
||||
|
||||
|
||||
class AddPMAssociationView(workflows.WorkflowView):
|
||||
workflow_class = project_workflows.AddPMAssociation
|
||||
|
||||
def get_initial(self):
|
||||
initial = super(AddPMAssociationView, self).get_initial()
|
||||
initial['pool_id'] = self.kwargs['pool_id']
|
||||
try:
|
||||
pool = api.lbaas.pool_get(self.request, initial['pool_id'])
|
||||
initial['pool_name'] = pool.name
|
||||
initial['pool_monitors'] = pool.health_monitors
|
||||
except Exception as e:
|
||||
msg = _('Unable to retrieve pool. %s') % e
|
||||
exceptions.handle(self.request, msg)
|
||||
return initial
|
||||
|
||||
|
||||
class DeletePMAssociationView(workflows.WorkflowView):
|
||||
workflow_class = project_workflows.DeletePMAssociation
|
||||
|
||||
def get_initial(self):
|
||||
initial = super(DeletePMAssociationView, self).get_initial()
|
||||
initial['pool_id'] = self.kwargs['pool_id']
|
||||
try:
|
||||
pool = api.lbaas.pool_get(self.request, initial['pool_id'])
|
||||
initial['pool_name'] = pool.name
|
||||
initial['pool_monitors'] = pool.health_monitors
|
||||
except Exception as e:
|
||||
msg = _('Unable to retrieve pool. %s') % e
|
||||
exceptions.handle(self.request, msg)
|
||||
return initial
|
@ -1,745 +0,0 @@
|
||||
# Copyright 2013, Big Switch Networks, 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.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
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')
|
||||
AVAILABLE_METHODS = ('ROUND_ROBIN', 'LEAST_CONNECTIONS', 'SOURCE_IP')
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AddPoolAction(workflows.Action):
|
||||
name = forms.CharField(max_length=80, label=_("Name"))
|
||||
description = forms.CharField(
|
||||
initial="", required=False,
|
||||
max_length=80, label=_("Description"))
|
||||
# provider is optional because some LBaaS implementation does
|
||||
# not support service-type extension.
|
||||
provider = forms.ThemableChoiceField(label=_("Provider"), required=False)
|
||||
subnet_id = forms.ThemableChoiceField(label=_("Subnet"))
|
||||
protocol = forms.ThemableChoiceField(label=_("Protocol"))
|
||||
lb_method = forms.ThemableChoiceField(label=_("Load Balancing Method"))
|
||||
admin_state_up = forms.ThemableChoiceField(
|
||||
choices=[(True, _('UP')),
|
||||
(False, _('DOWN'))],
|
||||
label=_("Admin State"))
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(AddPoolAction, self).__init__(request, *args, **kwargs)
|
||||
|
||||
tenant_id = request.user.tenant_id
|
||||
|
||||
subnet_id_choices = [('', _("Select a Subnet"))]
|
||||
try:
|
||||
networks = api.neutron.network_list_for_tenant(request, tenant_id)
|
||||
except Exception:
|
||||
exceptions.handle(request,
|
||||
_('Unable to retrieve networks list.'))
|
||||
networks = []
|
||||
for n in networks:
|
||||
for s in n['subnets']:
|
||||
name = "%s (%s)" % (s.name_or_id, s.cidr)
|
||||
subnet_id_choices.append((s.id, name))
|
||||
self.fields['subnet_id'].choices = subnet_id_choices
|
||||
|
||||
protocol_choices = [('', _("Select a Protocol"))]
|
||||
[protocol_choices.append((p, p)) for p in AVAILABLE_PROTOCOLS]
|
||||
self.fields['protocol'].choices = protocol_choices
|
||||
|
||||
lb_method_choices = [('', _("Select a Method"))]
|
||||
[lb_method_choices.append((m, m)) for m in AVAILABLE_METHODS]
|
||||
self.fields['lb_method'].choices = lb_method_choices
|
||||
|
||||
# provider choice
|
||||
try:
|
||||
if api.neutron.is_extension_supported(request, 'service-type'):
|
||||
provider_list = api.neutron.provider_list(request)
|
||||
providers = [p for p in provider_list
|
||||
if p['service_type'] == 'LOADBALANCER']
|
||||
else:
|
||||
providers = None
|
||||
except Exception:
|
||||
exceptions.handle(request,
|
||||
_('Unable to retrieve providers list.'))
|
||||
providers = []
|
||||
|
||||
if providers:
|
||||
default_providers = [p for p in providers if p.get('default')]
|
||||
if default_providers:
|
||||
default_provider = default_providers[0]['name']
|
||||
else:
|
||||
default_provider = None
|
||||
provider_choices = [(p['name'], p['name']) for p in providers
|
||||
if p['name'] != default_provider]
|
||||
if default_provider:
|
||||
provider_choices.insert(
|
||||
0, (default_provider,
|
||||
_("%s (default)") % default_provider))
|
||||
else:
|
||||
if providers is None:
|
||||
msg = _("Provider for Load Balancer is not supported")
|
||||
else:
|
||||
msg = _("No provider is available")
|
||||
provider_choices = [('', msg)]
|
||||
self.fields['provider'].widget.attrs['readonly'] = True
|
||||
self.fields['provider'].choices = provider_choices
|
||||
|
||||
class Meta(object):
|
||||
name = _("Add New Pool")
|
||||
permissions = ('openstack.services.network',)
|
||||
help_text_template = 'project/loadbalancers/_create_pool_help.html'
|
||||
|
||||
|
||||
class AddPoolStep(workflows.Step):
|
||||
action_class = AddPoolAction
|
||||
contributes = ("name", "description", "subnet_id", "provider",
|
||||
"protocol", "lb_method", "admin_state_up")
|
||||
|
||||
def contribute(self, data, context):
|
||||
context = super(AddPoolStep, self).contribute(data, context)
|
||||
context['admin_state_up'] = (context['admin_state_up'] == 'True')
|
||||
if data:
|
||||
return context
|
||||
|
||||
|
||||
class AddPool(workflows.Workflow):
|
||||
slug = "addpool"
|
||||
name = _("Add Pool")
|
||||
finalize_button_name = _("Add")
|
||||
success_message = _('Added pool "%s".')
|
||||
failure_message = _('Unable to add pool "%s".')
|
||||
success_url = "horizon:project:loadbalancers:index"
|
||||
default_steps = (AddPoolStep,)
|
||||
|
||||
def format_status_message(self, message):
|
||||
name = self.context.get('name')
|
||||
return message % name
|
||||
|
||||
def handle(self, request, context):
|
||||
try:
|
||||
api.lbaas.pool_create(request, **context)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
class AddVipAction(workflows.Action):
|
||||
name = forms.CharField(max_length=80, label=_("Name"))
|
||||
description = forms.CharField(
|
||||
initial="", required=False,
|
||||
max_length=80, label=_("Description"))
|
||||
subnet_id = forms.ThemableChoiceField(label=_("VIP Subnet"),
|
||||
initial="",
|
||||
required=False)
|
||||
address = forms.IPField(label=_("IP address"),
|
||||
version=forms.IPv4 | forms.IPv6,
|
||||
mask=False,
|
||||
required=False)
|
||||
protocol_port = forms.IntegerField(
|
||||
label=_("Protocol Port"), min_value=1,
|
||||
help_text=_("Enter an integer value "
|
||||
"between 1 and 65535."),
|
||||
validators=[validators.validate_port_range])
|
||||
protocol = forms.ThemableChoiceField(label=_("Protocol"))
|
||||
session_persistence = forms.ChoiceField(
|
||||
required=False, initial={}, label=_("Session Persistence"),
|
||||
widget=forms.ThemableSelectWidget(attrs={
|
||||
'class': 'switchable',
|
||||
'data-slug': 'persistence'
|
||||
}))
|
||||
cookie_name = forms.CharField(
|
||||
initial="", required=False,
|
||||
max_length=80, label=_("Cookie Name"),
|
||||
help_text=_("Required for APP_COOKIE persistence;"
|
||||
" Ignored otherwise."),
|
||||
widget=forms.TextInput(attrs={
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'persistence',
|
||||
'data-persistence-app_cookie': 'APP_COOKIE',
|
||||
}))
|
||||
connection_limit = forms.IntegerField(
|
||||
required=False, min_value=-1, label=_("Connection Limit"),
|
||||
help_text=_("Maximum number of connections allowed "
|
||||
"for the VIP or '-1' if the limit is not set"))
|
||||
admin_state_up = forms.ThemableChoiceField(
|
||||
choices=[(True, _('UP')),
|
||||
(False, _('DOWN'))],
|
||||
label=_("Admin State"))
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(AddVipAction, self).__init__(request, *args, **kwargs)
|
||||
tenant_id = request.user.tenant_id
|
||||
subnet_id_choices = [('', _("Select a Subnet"))]
|
||||
try:
|
||||
networks = api.neutron.network_list_for_tenant(request, tenant_id)
|
||||
except Exception:
|
||||
exceptions.handle(request,
|
||||
_('Unable to retrieve networks list.'))
|
||||
networks = []
|
||||
for n in networks:
|
||||
for s in n['subnets']:
|
||||
name = "%s (%s)" % (s.name, s.cidr)
|
||||
subnet_id_choices.append((s.id, name))
|
||||
self.fields['subnet_id'].choices = subnet_id_choices
|
||||
protocol_choices = [('', _("Select a Protocol"))]
|
||||
[protocol_choices.append((p, p)) for p in AVAILABLE_PROTOCOLS]
|
||||
self.fields['protocol'].choices = protocol_choices
|
||||
|
||||
session_persistence_choices = [('', _("No Session Persistence"))]
|
||||
for mode in ('SOURCE_IP', 'HTTP_COOKIE', 'APP_COOKIE'):
|
||||
session_persistence_choices.append((mode.lower(), mode))
|
||||
self.fields[
|
||||
'session_persistence'].choices = session_persistence_choices
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(AddVipAction, self).clean()
|
||||
persistence = cleaned_data.get('session_persistence')
|
||||
if persistence:
|
||||
cleaned_data['session_persistence'] = persistence.upper()
|
||||
if (cleaned_data.get('session_persistence') == 'APP_COOKIE' and
|
||||
not cleaned_data.get('cookie_name')):
|
||||
msg = _('Cookie name is required for APP_COOKIE persistence.')
|
||||
self._errors['cookie_name'] = self.error_class([msg])
|
||||
return cleaned_data
|
||||
|
||||
class Meta(object):
|
||||
name = _("Specify VIP")
|
||||
permissions = ('openstack.services.network',)
|
||||
help_text_template = 'project/loadbalancers/_create_vip_help.html'
|
||||
|
||||
|
||||
class AddVipStep(workflows.Step):
|
||||
action_class = AddVipAction
|
||||
depends_on = ("pool_id", "subnet")
|
||||
contributes = ("name", "description", "subnet_id",
|
||||
"address", "protocol_port", "protocol",
|
||||
"session_persistence", "cookie_name",
|
||||
"connection_limit", "admin_state_up")
|
||||
|
||||
def contribute(self, data, context):
|
||||
context = super(AddVipStep, self).contribute(data, context)
|
||||
context['admin_state_up'] = (context['admin_state_up'] == 'True')
|
||||
return context
|
||||
|
||||
|
||||
class AddVip(workflows.Workflow):
|
||||
slug = "addvip"
|
||||
name = _("Add VIP")
|
||||
finalize_button_name = _("Add")
|
||||
success_message = _('Added VIP "%s".')
|
||||
failure_message = _('Unable to add VIP "%s".')
|
||||
success_url = "horizon:project:loadbalancers:index"
|
||||
default_steps = (AddVipStep,)
|
||||
|
||||
def format_status_message(self, message):
|
||||
name = self.context.get('name')
|
||||
return message % name
|
||||
|
||||
def handle(self, request, context):
|
||||
if context['subnet_id'] == '':
|
||||
try:
|
||||
pool = api.lbaas.pool_get(request, context['pool_id'])
|
||||
context['subnet_id'] = pool['subnet_id']
|
||||
except Exception:
|
||||
context['subnet_id'] = None
|
||||
self.failure_message = _(
|
||||
'Unable to retrieve the specified pool. '
|
||||
'Unable to add VIP "%s".')
|
||||
return False
|
||||
|
||||
if context['session_persistence']:
|
||||
stype = context['session_persistence']
|
||||
if stype == 'APP_COOKIE':
|
||||
cookie = context['cookie_name']
|
||||
context['session_persistence'] = {'type': stype,
|
||||
'cookie_name': cookie}
|
||||
else:
|
||||
context['session_persistence'] = {'type': stype}
|
||||
else:
|
||||
context['session_persistence'] = {}
|
||||
|
||||
try:
|
||||
api.lbaas.vip_create(request, **context)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
class AddMemberAction(workflows.Action):
|
||||
pool_id = forms.ThemableChoiceField(label=_("Pool"))
|
||||
member_type = forms.ChoiceField(
|
||||
label=_("Member Source"),
|
||||
choices=[('server_list', _("Select from active instances")),
|
||||
('member_address', _("Specify member IP address"))],
|
||||
required=False,
|
||||
widget=forms.Select(attrs={
|
||||
'class': 'switchable',
|
||||
'data-slug': 'membertype'
|
||||
}))
|
||||
members = forms.MultipleChoiceField(
|
||||
required=False,
|
||||
initial=["default"],
|
||||
widget=forms.SelectMultiple(attrs={
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'membertype',
|
||||
'data-membertype-server_list': _("Member Instance(s)"),
|
||||
}),
|
||||
help_text=_("Select members for this pool "))
|
||||
address = forms.IPField(
|
||||
required=False,
|
||||
help_text=_("Specify member IP address"),
|
||||
widget=forms.TextInput(attrs={
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'membertype',
|
||||
'data-membertype-member_address': _("Member Address"),
|
||||
}),
|
||||
initial="", version=forms.IPv4 | forms.IPv6, mask=False)
|
||||
weight = forms.IntegerField(
|
||||
max_value=256, min_value=1, label=_("Weight"), required=False,
|
||||
help_text=_("Relative part of requests this pool member serves "
|
||||
"compared to others. \nThe same weight will be applied to "
|
||||
"all the selected members and can be modified later. "
|
||||
"Weight must be in the range 1 to 256.")
|
||||
)
|
||||
protocol_port = forms.IntegerField(
|
||||
label=_("Protocol Port"), min_value=1,
|
||||
help_text=_("Enter an integer value between 1 and 65535. "
|
||||
"The same port will be used for all the selected "
|
||||
"members and can be modified later."),
|
||||
validators=[validators.validate_port_range]
|
||||
)
|
||||
admin_state_up = forms.ThemableChoiceField(
|
||||
choices=[(True, _('UP')),
|
||||
(False, _('DOWN'))],
|
||||
label=_("Admin State"))
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(AddMemberAction, self).__init__(request, *args, **kwargs)
|
||||
|
||||
pool_id_choices = [('', _("Select a Pool"))]
|
||||
try:
|
||||
tenant_id = self.request.user.tenant_id
|
||||
pools = api.lbaas.pool_list(request, tenant_id=tenant_id)
|
||||
except Exception:
|
||||
pools = []
|
||||
exceptions.handle(request,
|
||||
_('Unable to retrieve pools list.'))
|
||||
pools = sorted(pools,
|
||||
key=lambda pool: pool.name)
|
||||
for p in pools:
|
||||
pool_id_choices.append((p.id, p.name))
|
||||
self.fields['pool_id'].choices = pool_id_choices
|
||||
|
||||
members_choices = []
|
||||
try:
|
||||
servers, has_more = api.nova.server_list(request)
|
||||
except Exception:
|
||||
servers = []
|
||||
exceptions.handle(request,
|
||||
_('Unable to retrieve instances list.'))
|
||||
|
||||
if len(servers) == 0:
|
||||
self.fields['members'].widget.attrs[
|
||||
'data-membertype-server_list'] = _(
|
||||
"No servers available. To add a member, you "
|
||||
"need at least one running instance.")
|
||||
return
|
||||
|
||||
for m in servers:
|
||||
members_choices.append((m.id, m.name))
|
||||
self.fields['members'].choices = sorted(
|
||||
members_choices,
|
||||
key=lambda member: member[1])
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(AddMemberAction, self).clean()
|
||||
if (cleaned_data.get('member_type') == 'server_list' and
|
||||
not cleaned_data.get('members')):
|
||||
msg = _('At least one member must be specified')
|
||||
self._errors['members'] = self.error_class([msg])
|
||||
elif (cleaned_data.get('member_type') == 'member_address' and
|
||||
not cleaned_data.get('address')):
|
||||
msg = _('Member IP address must be specified')
|
||||
self._errors['address'] = self.error_class([msg])
|
||||
return cleaned_data
|
||||
|
||||
class Meta(object):
|
||||
name = _("Add New Member")
|
||||
permissions = ('openstack.services.network',)
|
||||
help_text = _("Add member(s) to the selected pool.\n\n"
|
||||
"Choose one or more listed instances to be "
|
||||
"added to the pool as member(s). "
|
||||
"Assign a numeric weight and port number for the "
|
||||
"selected member(s) to operate(s) on; e.g., 80. \n\n"
|
||||
"Only one port can be associated with "
|
||||
"each instance.")
|
||||
|
||||
|
||||
class AddMemberStep(workflows.Step):
|
||||
action_class = AddMemberAction
|
||||
contributes = ("pool_id", "member_type", "members", "address",
|
||||
"protocol_port", "weight", "admin_state_up")
|
||||
|
||||
def contribute(self, data, context):
|
||||
context = super(AddMemberStep, self).contribute(data, context)
|
||||
context['admin_state_up'] = (context['admin_state_up'] == 'True')
|
||||
return context
|
||||
|
||||
|
||||
class AddMember(workflows.Workflow):
|
||||
slug = "addmember"
|
||||
name = _("Add Member")
|
||||
finalize_button_name = _("Add")
|
||||
success_message = _('Added member(s).')
|
||||
failure_message = _('Unable to add member(s)')
|
||||
success_url = "horizon:project:loadbalancers:index"
|
||||
default_steps = (AddMemberStep,)
|
||||
|
||||
def handle(self, request, context):
|
||||
if context['member_type'] == 'server_list':
|
||||
try:
|
||||
pool = api.lbaas.pool_get(request, context['pool_id'])
|
||||
subnet_id = pool['subnet_id']
|
||||
except Exception:
|
||||
self.failure_message = _('Unable to retrieve '
|
||||
'the specified pool.')
|
||||
return False
|
||||
for m in context['members']:
|
||||
params = {'device_id': m}
|
||||
try:
|
||||
plist = api.neutron.port_list(request, **params)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
# Sort port list for each member. This is needed to avoid
|
||||
# attachment of random ports in case of creation of several
|
||||
# members attached to several networks.
|
||||
if plist:
|
||||
plist = sorted(plist, key=lambda port: port.network_id)
|
||||
psubnet = [p for p in plist for ips in p.fixed_ips
|
||||
if ips['subnet_id'] == subnet_id]
|
||||
|
||||
# If possible, select a port on pool subnet.
|
||||
if psubnet:
|
||||
selected_port = psubnet[0]
|
||||
elif plist:
|
||||
selected_port = plist[0]
|
||||
else:
|
||||
selected_port = None
|
||||
|
||||
if selected_port:
|
||||
context['address'] = \
|
||||
selected_port.fixed_ips[0]['ip_address']
|
||||
try:
|
||||
api.lbaas.member_create(request, **context).id
|
||||
except Exception as e:
|
||||
msg = self.failure_message
|
||||
LOG.info('%s: %s' % (msg, e))
|
||||
return False
|
||||
else:
|
||||
self.failure_message = _('No ports available.')
|
||||
return False
|
||||
return True
|
||||
else:
|
||||
try:
|
||||
context['member_id'] = api.lbaas.member_create(
|
||||
request, **context).id
|
||||
return True
|
||||
except Exception as e:
|
||||
msg = self.failure_message
|
||||
LOG.info('%s: %s' % (msg, e))
|
||||
return False
|
||||
|
||||
|
||||
class AddMonitorAction(workflows.Action):
|
||||
type = forms.ChoiceField(
|
||||
label=_("Type"),
|
||||
choices=[('ping', _('PING')),
|
||||
('tcp', _('TCP')),
|
||||
('http', _('HTTP')),
|
||||
('https', _('HTTPS'))],
|
||||
widget=forms.Select(attrs={
|
||||
'class': 'switchable',
|
||||
'data-slug': 'type'
|
||||
}))
|
||||
delay = forms.IntegerField(
|
||||
min_value=1,
|
||||
label=_("Delay"),
|
||||
help_text=_("The minimum time in seconds between regular checks "
|
||||
"of a member. It must be greater than or equal to "
|
||||
"timeout"))
|
||||
timeout = forms.IntegerField(
|
||||
min_value=1,
|
||||
label=_("Timeout"),
|
||||
help_text=_("The maximum time in seconds for a monitor to wait "
|
||||
"for a reply. It must be less than or equal to delay"))
|
||||
max_retries = forms.IntegerField(
|
||||
max_value=10, min_value=1,
|
||||
label=_("Max Retries (1~10)"),
|
||||
help_text=_("Number of permissible failures before changing "
|
||||
"the status of member to inactive"))
|
||||
http_method = forms.ChoiceField(
|
||||
initial="GET",
|
||||
required=False,
|
||||
choices=[('GET', _('GET'))],
|
||||
label=_("HTTP Method"),
|
||||
help_text=_("HTTP method used to check health status of a member"),
|
||||
widget=forms.Select(attrs={
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'type',
|
||||
'data-type-http': _('HTTP Method'),
|
||||
'data-type-https': _('HTTP Method')
|
||||
}))
|
||||
url_path = forms.CharField(
|
||||
initial="/",
|
||||
required=False,
|
||||
max_length=80,
|
||||
label=_("URL"),
|
||||
widget=forms.TextInput(attrs={
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'type',
|
||||
'data-type-http': _('URL'),
|
||||
'data-type-https': _('URL')
|
||||
}))
|
||||
expected_codes = forms.RegexField(
|
||||
initial="200",
|
||||
required=False,
|
||||
max_length=80,
|
||||
regex=r'^(\d{3}(\s*,\s*\d{3})*)$|^(\d{3}-\d{3})$',
|
||||
label=_("Expected HTTP Status Codes"),
|
||||
help_text=_("Expected code may be a single value (e.g. 200), "
|
||||
"a list of values (e.g. 200, 202), "
|
||||
"or range of values (e.g. 200-204)"),
|
||||
widget=forms.TextInput(attrs={
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'type',
|
||||
'data-type-http': _('Expected HTTP Status Codes'),
|
||||
'data-type-https': _('Expected HTTP Status Codes')
|
||||
}))
|
||||
admin_state_up = forms.ThemableChoiceField(
|
||||
choices=[(True, _('UP')),
|
||||
(False, _('DOWN'))],
|
||||
label=_("Admin State"))
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(AddMonitorAction, self).__init__(request, *args, **kwargs)
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(AddMonitorAction, self).clean()
|
||||
type_opt = cleaned_data.get('type')
|
||||
delay = cleaned_data.get('delay')
|
||||
timeout = cleaned_data.get('timeout')
|
||||
|
||||
if (delay is None) or (delay < timeout):
|
||||
msg = _('Delay must be greater than or equal to Timeout')
|
||||
self._errors['delay'] = self.error_class([msg])
|
||||
|
||||
if type_opt in ['http', 'https']:
|
||||
http_method_opt = cleaned_data.get('http_method')
|
||||
url_path = cleaned_data.get('url_path')
|
||||
expected_codes = cleaned_data.get('expected_codes')
|
||||
|
||||
if not http_method_opt:
|
||||
msg = _('Please choose a HTTP method')
|
||||
self._errors['http_method'] = self.error_class([msg])
|
||||
if not url_path:
|
||||
msg = _('Please specify an URL')
|
||||
self._errors['url_path'] = self.error_class([msg])
|
||||
if not expected_codes:
|
||||
msg = _('Please enter a single value (e.g. 200), '
|
||||
'a list of values (e.g. 200, 202), '
|
||||
'or range of values (e.g. 200-204)')
|
||||
self._errors['expected_codes'] = self.error_class([msg])
|
||||
return cleaned_data
|
||||
|
||||
class Meta(object):
|
||||
name = _("Add New Monitor")
|
||||
permissions = ('openstack.services.network',)
|
||||
help_text = _("Create a monitor template.\n\n"
|
||||
"Select type of monitoring. "
|
||||
"Specify delay, timeout, and retry limits "
|
||||
"required by the monitor. "
|
||||
"Specify method, URL path, and expected "
|
||||
"HTTP codes upon success.")
|
||||
|
||||
|
||||
class AddMonitorStep(workflows.Step):
|
||||
action_class = AddMonitorAction
|
||||
contributes = ("type", "delay", "timeout", "max_retries",
|
||||
"http_method", "url_path", "expected_codes",
|
||||
"admin_state_up")
|
||||
|
||||
def contribute(self, data, context):
|
||||
context = super(AddMonitorStep, self).contribute(data, context)
|
||||
context['admin_state_up'] = (context['admin_state_up'] == 'True')
|
||||
if data:
|
||||
return context
|
||||
|
||||
|
||||
class AddMonitor(workflows.Workflow):
|
||||
slug = "addmonitor"
|
||||
name = _("Add Monitor")
|
||||
finalize_button_name = _("Add")
|
||||
success_message = _('Added monitor')
|
||||
failure_message = _('Unable to add monitor')
|
||||
success_url = "horizon:project:loadbalancers:index"
|
||||
default_steps = (AddMonitorStep,)
|
||||
|
||||
def handle(self, request, context):
|
||||
try:
|
||||
context['monitor_id'] = api.lbaas.pool_health_monitor_create(
|
||||
request, **context).get('id')
|
||||
return True
|
||||
except Exception:
|
||||
exceptions.handle(request, _("Unable to add monitor."))
|
||||
return False
|
||||
|
||||
|
||||
class AddPMAssociationAction(workflows.Action):
|
||||
monitor_id = forms.ThemableChoiceField(label=_("Monitor"))
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(AddPMAssociationAction, self).__init__(request, *args, **kwargs)
|
||||
|
||||
def populate_monitor_id_choices(self, request, context):
|
||||
self.fields['monitor_id'].label = _("Select a monitor template "
|
||||
"for %s") % context['pool_name']
|
||||
|
||||
monitor_id_choices = [('', _("Select a Monitor"))]
|
||||
try:
|
||||
tenant_id = self.request.user.tenant_id
|
||||
monitors = api.lbaas.pool_health_monitor_list(request,
|
||||
tenant_id=tenant_id)
|
||||
pool_monitors_ids = [pm.id for pm in context['pool_monitors']]
|
||||
for m in monitors:
|
||||
if m.id not in pool_monitors_ids:
|
||||
display_name = utils.get_monitor_display_name(m)
|
||||
monitor_id_choices.append((m.id, display_name))
|
||||
except Exception:
|
||||
exceptions.handle(request,
|
||||
_('Unable to retrieve monitors list.'))
|
||||
self.fields['monitor_id'].choices = monitor_id_choices
|
||||
|
||||
return monitor_id_choices
|
||||
|
||||
class Meta(object):
|
||||
name = _("Association Details")
|
||||
permissions = ('openstack.services.network',)
|
||||
help_text = _("Associate a health monitor with target pool.")
|
||||
|
||||
|
||||
class AddPMAssociationStep(workflows.Step):
|
||||
action_class = AddPMAssociationAction
|
||||
depends_on = ("pool_id", "pool_name", "pool_monitors")
|
||||
contributes = ("monitor_id",)
|
||||
|
||||
def contribute(self, data, context):
|
||||
context = super(AddPMAssociationStep, self).contribute(data, context)
|
||||
if data:
|
||||
return context
|
||||
|
||||
|
||||
class AddPMAssociation(workflows.Workflow):
|
||||
slug = "addassociation"
|
||||
name = _("Associate Monitor")
|
||||
finalize_button_name = _("Associate")
|
||||
success_message = _('Associated monitor.')
|
||||
failure_message = _('Unable to associate monitor.')
|
||||
success_url = "horizon:project:loadbalancers:index"
|
||||
default_steps = (AddPMAssociationStep,)
|
||||
|
||||
def handle(self, request, context):
|
||||
try:
|
||||
context['monitor_id'] = api.lbaas.pool_monitor_association_create(
|
||||
request, **context)
|
||||
return True
|
||||
except Exception:
|
||||
exceptions.handle(request, _("Unable to associate monitor."))
|
||||
return False
|
||||
|
||||
|
||||
class DeletePMAssociationAction(workflows.Action):
|
||||
monitor_id = forms.ThemableChoiceField(label=_("Monitor"))
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(DeletePMAssociationAction, self).__init__(
|
||||
request, *args, **kwargs)
|
||||
|
||||
def populate_monitor_id_choices(self, request, context):
|
||||
self.fields['monitor_id'].label = (_("Select a health monitor of %s") %
|
||||
context['pool_name'])
|
||||
|
||||
monitor_id_choices = [('', _("Select a Monitor"))]
|
||||
try:
|
||||
monitors = api.lbaas.pool_health_monitor_list(request)
|
||||
pool_monitors_ids = [pm.id for pm in context['pool_monitors']]
|
||||
for m in monitors:
|
||||
if m.id in pool_monitors_ids:
|
||||
display_name = utils.get_monitor_display_name(m)
|
||||
monitor_id_choices.append((m.id, display_name))
|
||||
except Exception:
|
||||
exceptions.handle(request,
|
||||
_('Unable to retrieve monitors list.'))
|
||||
self.fields['monitor_id'].choices = monitor_id_choices
|
||||
|
||||
return monitor_id_choices
|
||||
|
||||
class Meta(object):
|
||||
name = _("Association Details")
|
||||
permissions = ('openstack.services.network',)
|
||||
help_text = _("Disassociate a health monitor from target pool. ")
|
||||
|
||||
|
||||
class DeletePMAssociationStep(workflows.Step):
|
||||
action_class = DeletePMAssociationAction
|
||||
depends_on = ("pool_id", "pool_name", "pool_monitors")
|
||||
contributes = ("monitor_id",)
|
||||
|
||||
def contribute(self, data, context):
|
||||
context = super(DeletePMAssociationStep, self).contribute(
|
||||
data, context)
|
||||
if data:
|
||||
return context
|
||||
|
||||
|
||||
class DeletePMAssociation(workflows.Workflow):
|
||||
slug = "deleteassociation"
|
||||
name = _("Disassociate Monitor")
|
||||
finalize_button_name = _("Disassociate")
|
||||
success_message = _('Disassociated monitor.')
|
||||
failure_message = _('Unable to disassociate monitor.')
|
||||
success_url = "horizon:project:loadbalancers:index"
|
||||
default_steps = (DeletePMAssociationStep,)
|
||||
|
||||
def handle(self, request, context):
|
||||
try:
|
||||
context['monitor_id'] = api.lbaas.pool_monitor_association_delete(
|
||||
request, **context)
|
||||
return True
|
||||
except Exception:
|
||||
exceptions.handle(request, _("Unable to disassociate monitor."))
|
||||
return False
|
@ -83,8 +83,6 @@ resource_urls = {
|
||||
'link': 'horizon:project:stacks:detail'},
|
||||
"OS::Heat::WaitConditionHandle": {
|
||||
'link': 'horizon:project:stacks:detail'},
|
||||
"OS::Neutron::HealthMonitor": {
|
||||
'link': 'horizon:project:loadbalancers:monitordetails'},
|
||||
"OS::Neutron::IKEPolicy": {
|
||||
'link': 'horizon:project:vpn:ikepolicydetails'},
|
||||
"OS::Neutron::IPsecPolicy": {
|
||||
@ -93,10 +91,6 @@ resource_urls = {
|
||||
'link': 'horizon:project:vpn:ipsecsiteconnectiondetails'},
|
||||
"OS::Neutron::Net": {
|
||||
'link': 'horizon:project:networks:detail'},
|
||||
"OS::Neutron::Pool": {
|
||||
'link': 'horizon:project:loadbalancers:pooldetails'},
|
||||
"OS::Neutron::PoolMember": {
|
||||
'link': 'horizon:project:loadbalancers:memberdetails'},
|
||||
"OS::Neutron::Port": {
|
||||
'link': 'horizon:project:networks:ports:detail'},
|
||||
"OS::Neutron::Router": {
|
||||
|
@ -1,10 +0,0 @@
|
||||
# The slug of the panel to be added to HORIZON_CONFIG. Required.
|
||||
PANEL = 'loadbalancers'
|
||||
# The slug of the dashboard the PANEL associated with. Required.
|
||||
PANEL_DASHBOARD = 'project'
|
||||
# The slug of the panel group the PANEL is associated with.
|
||||
PANEL_GROUP = 'network'
|
||||
|
||||
# Python panel class of the PANEL to be added.
|
||||
ADD_PANEL = ('openstack_dashboard.dashboards.project.'
|
||||
'loadbalancers.panel.LoadBalancer')
|
@ -1,379 +0,0 @@
|
||||
# Copyright 2013, Big Switch Networks, 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 openstack_dashboard import api
|
||||
from openstack_dashboard.test import helpers as test
|
||||
|
||||
from neutronclient.v2_0 import client
|
||||
|
||||
neutronclient = client.Client
|
||||
|
||||
|
||||
class LbaasApiTests(test.APITestCase):
|
||||
@test.create_stubs({neutronclient: ('create_vip',)})
|
||||
def test_vip_create(self):
|
||||
vip1 = self.api_vips.first()
|
||||
form_data = {'address': vip1['address'],
|
||||
'name': vip1['name'],
|
||||
'description': vip1['description'],
|
||||
'subnet_id': vip1['subnet_id'],
|
||||
'protocol_port': vip1['protocol_port'],
|
||||
'protocol': vip1['protocol'],
|
||||
'pool_id': vip1['pool_id'],
|
||||
'session_persistence': vip1['session_persistence'],
|
||||
'connection_limit': vip1['connection_limit'],
|
||||
'admin_state_up': vip1['admin_state_up']
|
||||
}
|
||||
vip = {'vip': self.api_vips.first()}
|
||||
neutronclient.create_vip({'vip': form_data}).AndReturn(vip)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
ret_val = api.lbaas.vip_create(self.request, **form_data)
|
||||
self.assertIsInstance(ret_val, api.lbaas.Vip)
|
||||
|
||||
@test.create_stubs({neutronclient: ('create_vip',)})
|
||||
def test_vip_create_skip_address_if_empty(self):
|
||||
vip1 = self.api_vips.first()
|
||||
vipform_data = {'name': vip1['name'],
|
||||
'description': vip1['description'],
|
||||
'subnet_id': vip1['subnet_id'],
|
||||
'protocol_port': vip1['protocol_port'],
|
||||
'protocol': vip1['protocol'],
|
||||
'pool_id': vip1['pool_id'],
|
||||
'session_persistence': vip1['session_persistence'],
|
||||
'connection_limit': vip1['connection_limit'],
|
||||
'admin_state_up': vip1['admin_state_up']
|
||||
}
|
||||
|
||||
neutronclient.create_vip({'vip': vipform_data}).AndReturn(
|
||||
{'vip': vipform_data})
|
||||
self.mox.ReplayAll()
|
||||
|
||||
form_data = dict(vipform_data)
|
||||
form_data['address'] = ""
|
||||
ret_val = api.lbaas.vip_create(self.request, **form_data)
|
||||
self.assertIsInstance(ret_val, api.lbaas.Vip)
|
||||
|
||||
@test.create_stubs({neutronclient: ('list_vips',)})
|
||||
def test_vip_list(self):
|
||||
vips = {'vips': [{'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.list_vips().AndReturn(vips)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
ret_val = api.lbaas.vip_list(self.request)
|
||||
for v in ret_val:
|
||||
self.assertIsInstance(v, api.lbaas.Vip)
|
||||
self.assertTrue(v.id)
|
||||
|
||||
@test.create_stubs({neutronclient: ('show_vip', 'show_pool'),
|
||||
api.neutron: ('subnet_get', 'port_get')})
|
||||
def test_vip_get(self):
|
||||
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['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):
|
||||
form_data = {'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
|
||||
}
|
||||
|
||||
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.update_vip(vip['vip']['id'], form_data).AndReturn(vip)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
ret_val = api.lbaas.vip_update(self.request,
|
||||
vip['vip']['id'], **form_data)
|
||||
self.assertIsInstance(ret_val, api.lbaas.Vip)
|
||||
|
||||
@test.create_stubs({neutronclient: ('create_pool',)})
|
||||
def test_pool_create(self):
|
||||
form_data = {'name': 'pool1name',
|
||||
'description': 'pool1description',
|
||||
'subnet_id': '12381d38-c3eb-4fee-9763-12de3338041e',
|
||||
'protocol': 'HTTP',
|
||||
'lb_method': 'ROUND_ROBIN',
|
||||
'admin_state_up': True,
|
||||
'provider': 'dummy'
|
||||
}
|
||||
|
||||
pool = {'pool': {'id': 'abcdef-c3eb-4fee-9763-12de3338041e',
|
||||
'name': 'pool1name',
|
||||
'description': 'pool1description',
|
||||
'subnet_id': '12381d38-c3eb-4fee-9763-12de3338041e',
|
||||
'protocol': 'HTTP',
|
||||
'lb_method': 'ROUND_ROBIN',
|
||||
'admin_state_up': True,
|
||||
'provider': 'dummy'
|
||||
}}
|
||||
neutronclient.create_pool({'pool': form_data}).AndReturn(pool)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
ret_val = api.lbaas.pool_create(self.request, **form_data)
|
||||
self.assertIsInstance(ret_val, api.lbaas.Pool)
|
||||
|
||||
@test.create_stubs({neutronclient: ('list_pools', 'list_vips'),
|
||||
api.neutron: ('subnet_list',)})
|
||||
def test_pool_list(self):
|
||||
pools = {'pools': self.api_pools.list()}
|
||||
subnets = self.subnets.list()
|
||||
vips = {'vips': self.api_vips.list()}
|
||||
|
||||
neutronclient.list_pools().AndReturn(pools)
|
||||
api.neutron.subnet_list(self.request).AndReturn(subnets)
|
||||
neutronclient.list_vips().AndReturn(vips)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
ret_val = api.lbaas.pool_list(self.request)
|
||||
for v in ret_val:
|
||||
self.assertIsInstance(v, api.lbaas.Pool)
|
||||
self.assertTrue(v.id)
|
||||
|
||||
@test.create_stubs({neutronclient: ('show_pool', 'show_vip',
|
||||
'list_members',
|
||||
'show_health_monitor',),
|
||||
api.neutron: ('subnet_get',)})
|
||||
def test_pool_get(self):
|
||||
pool = self.pools.first()
|
||||
subnet = self.subnets.first()
|
||||
pool_dict = {'pool': self.api_pools.first()}
|
||||
vip_dict = {'vip': self.api_vips.first()}
|
||||
|
||||
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.api_members.list()})
|
||||
monitor = self.api_monitors.first()
|
||||
for pool_mon in pool.health_monitors:
|
||||
neutronclient.show_health_monitor(pool_mon).AndReturn(
|
||||
{'health_monitor': monitor})
|
||||
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(3, len(ret_val.members))
|
||||
self.assertIsInstance(ret_val.members[0], api.lbaas.Member)
|
||||
self.assertEqual(len(pool.health_monitors),
|
||||
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):
|
||||
form_data = {'name': 'pool1name',
|
||||
'description': 'pool1description',
|
||||
'subnet_id': '12381d38-c3eb-4fee-9763-12de3338041e',
|
||||
'protocol': 'HTTPS',
|
||||
'lb_method': 'LEAST_CONNECTION',
|
||||
'admin_state_up': True
|
||||
}
|
||||
|
||||
pool = {'pool': {'id': 'abcdef-c3eb-4fee-9763-12de3338041e',
|
||||
'name': 'pool1name',
|
||||
'description': 'pool1description',
|
||||
'subnet_id': '12381d38-c3eb-4fee-9763-12de3338041e',
|
||||
'protocol': 'HTTPS',
|
||||
'lb_method': 'LEAST_CONNECTION',
|
||||
'admin_state_up': True
|
||||
}}
|
||||
neutronclient.update_pool(pool['pool']['id'],
|
||||
form_data).AndReturn(pool)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
ret_val = api.lbaas.pool_update(self.request,
|
||||
pool['pool']['id'], **form_data)
|
||||
self.assertIsInstance(ret_val, api.lbaas.Pool)
|
||||
|
||||
@test.create_stubs({neutronclient: ('create_health_monitor',)})
|
||||
def test_pool_health_monitor_create(self):
|
||||
form_data = {'type': 'PING',
|
||||
'delay': '10',
|
||||
'timeout': '10',
|
||||
'max_retries': '10',
|
||||
'admin_state_up': True
|
||||
}
|
||||
monitor = {'health_monitor': {
|
||||
'id': 'abcdef-c3eb-4fee-9763-12de3338041e',
|
||||
'type': 'PING',
|
||||
'delay': '10',
|
||||
'timeout': '10',
|
||||
'max_retries': '10',
|
||||
'admin_state_up': True}}
|
||||
neutronclient.create_health_monitor({
|
||||
'health_monitor': form_data}).AndReturn(monitor)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
ret_val = api.lbaas.pool_health_monitor_create(
|
||||
self.request, **form_data)
|
||||
self.assertIsInstance(ret_val, api.lbaas.PoolMonitor)
|
||||
|
||||
@test.create_stubs({neutronclient: ('list_health_monitors',)})
|
||||
def test_pool_health_monitor_list(self):
|
||||
monitors = {'health_monitors': [
|
||||
{'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}, ]}
|
||||
|
||||
neutronclient.list_health_monitors().AndReturn(monitors)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
ret_val = api.lbaas.pool_health_monitor_list(self.request)
|
||||
for v in ret_val:
|
||||
self.assertIsInstance(v, api.lbaas.PoolMonitor)
|
||||
self.assertTrue(v.id)
|
||||
|
||||
@test.create_stubs({neutronclient: ('show_health_monitor',
|
||||
'list_pools')})
|
||||
def test_pool_health_monitor_get(self):
|
||||
monitor = self.api_monitors.first()
|
||||
neutronclient.show_health_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['id'])
|
||||
self.assertIsInstance(ret_val, api.lbaas.PoolMonitor)
|
||||
self.assertEqual(3, len(ret_val.pools))
|
||||
self.assertIsInstance(ret_val.pools[0], api.lbaas.Pool)
|
||||
|
||||
@test.create_stubs({neutronclient: ('create_member', )})
|
||||
def test_member_create(self):
|
||||
form_data = {'pool_id': 'abcdef-c3eb-4fee-9763-12de3338041e',
|
||||
'address': '10.0.1.2',
|
||||
'protocol_port': '80',
|
||||
'weight': '10',
|
||||
'admin_state_up': True
|
||||
}
|
||||
|
||||
member = {'member':
|
||||
{'id': 'abcdef-c3eb-4fee-9763-12de3338041e',
|
||||
'pool_id': 'abcdef-c3eb-4fee-9763-12de3338041e',
|
||||
'address': '10.0.1.2',
|
||||
'protocol_port': '80',
|
||||
'weight': '10',
|
||||
'admin_state_up': True}}
|
||||
|
||||
neutronclient.create_member({'member': form_data}).AndReturn(member)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
ret_val = api.lbaas.member_create(self.request, **form_data)
|
||||
self.assertIsInstance(ret_val, api.lbaas.Member)
|
||||
|
||||
@test.create_stubs({neutronclient: ('list_members', 'list_pools')})
|
||||
def test_member_list(self):
|
||||
members = {'members': self.api_members.list()}
|
||||
pools = {'pools': self.api_pools.list()}
|
||||
|
||||
neutronclient.list_members().AndReturn(members)
|
||||
neutronclient.list_pools().AndReturn(pools)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
ret_val = api.lbaas.member_list(self.request)
|
||||
for v in ret_val:
|
||||
self.assertIsInstance(v, api.lbaas.Member)
|
||||
self.assertTrue(v.id)
|
||||
|
||||
@test.create_stubs({neutronclient: ('show_member', 'show_pool')})
|
||||
def test_member_get(self):
|
||||
member = self.members.first()
|
||||
member_dict = {'member': self.api_members.first()}
|
||||
pool_dict = {'pool': self.api_pools.first()}
|
||||
|
||||
neutronclient.show_member(member.id).AndReturn(member_dict)
|
||||
neutronclient.show_pool(member.pool_id).AndReturn(pool_dict)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
ret_val = api.lbaas.member_get(self.request, member.id)
|
||||
self.assertIsInstance(ret_val, api.lbaas.Member)
|
||||
|
||||
@test.create_stubs({neutronclient: ('update_member',)})
|
||||
def test_member_update(self):
|
||||
form_data = {'pool_id': 'abcdef-c3eb-4fee-9763-12de3338041e',
|
||||
'address': '10.0.1.4',
|
||||
'protocol_port': '80',
|
||||
'weight': '10',
|
||||
'admin_state_up': True
|
||||
}
|
||||
|
||||
member = {'member': {'id': 'abcdef-c3eb-4fee-9763-12de3338041e',
|
||||
'pool_id': 'abcdef-c3eb-4fee-9763-12de3338041e',
|
||||
'address': '10.0.1.2',
|
||||
'protocol_port': '80',
|
||||
'weight': '10',
|
||||
'admin_state_up': True
|
||||
}}
|
||||
|
||||
neutronclient.update_member(member['member']['id'],
|
||||
form_data).AndReturn(member)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
ret_val = api.lbaas.member_update(self.request,
|
||||
member['member']['id'], **form_data)
|
||||
self.assertIsInstance(ret_val, api.lbaas.Member)
|
@ -737,7 +737,6 @@ class NetworkApiNeutronFloatingIpTests(NetworkApiNeutronTestBase):
|
||||
|
||||
@override_settings(
|
||||
OPENSTACK_NEUTRON_NETWORK={
|
||||
'enable_lb': True,
|
||||
'enable_fip_topology_check': True,
|
||||
}
|
||||
)
|
||||
@ -758,8 +757,6 @@ class NetworkApiNeutronFloatingIpTests(NetworkApiNeutronTestBase):
|
||||
filters = {'tenant_id': self.request.user.tenant_id}
|
||||
# api.neutron.is_extension_supported(self.request, 'security-group'). \
|
||||
# AndReturn(True)
|
||||
# api.neutron.is_extension_supported(self.request, 'lbaas'). \
|
||||
# AndReturn(True)
|
||||
self.qclient.list_ports(**filters).AndReturn({'ports': ports})
|
||||
servers = self.servers.list()
|
||||
novaclient = self.stub_novaclient()
|
||||
@ -779,7 +776,6 @@ class NetworkApiNeutronFloatingIpTests(NetworkApiNeutronTestBase):
|
||||
shared_subs = [s for s in self.api_subnets.list()
|
||||
if s['id'] in shared_subnet_ids]
|
||||
self.qclient.list_subnets().AndReturn({'subnets': shared_subs})
|
||||
self.qclient.list_vips().AndReturn({'vips': self.vips.list()})
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
|
@ -167,13 +167,12 @@ OPENSTACK_CINDER_FEATURES = {
|
||||
OPENSTACK_NEUTRON_NETWORK = {
|
||||
'enable_router': True,
|
||||
'enable_quotas': False, # Enabled in specific tests only
|
||||
# Parameters below (enable_lb, enable_firewall, enable_vpn)
|
||||
# Parameters below (enable_firewall, enable_vpn)
|
||||
# control if these panels are displayed or not,
|
||||
# i.e. they only affect the navigation menu.
|
||||
# These panels are registered even if enable_XXX is False,
|
||||
# so we don't need to set them to True in most unit tests
|
||||
# to avoid stubbing neutron extension check calls.
|
||||
'enable_lb': False,
|
||||
'enable_firewall': False,
|
||||
'enable_vpn': False,
|
||||
'profile_support': None,
|
||||
|
@ -17,7 +17,6 @@ import uuid
|
||||
|
||||
from openstack_dashboard.api import base
|
||||
from openstack_dashboard.api import fwaas
|
||||
from openstack_dashboard.api import lbaas
|
||||
from openstack_dashboard.api import neutron
|
||||
from openstack_dashboard.api import vpn
|
||||
from openstack_dashboard.test.test_data import utils
|
||||
@ -583,211 +582,6 @@ def data(TEST):
|
||||
subnetpool = neutron.SubnetPool(subnetpool_dict)
|
||||
TEST.subnetpools.add(subnetpool)
|
||||
|
||||
# LBaaS.
|
||||
|
||||
# 1st pool.
|
||||
pool_dict = {'id': '8913dde8-4915-4b90-8d3e-b95eeedb0d49',
|
||||
'tenant_id': '1',
|
||||
'vip_id': 'abcdef-c3eb-4fee-9763-12de3338041e',
|
||||
'name': 'pool1',
|
||||
'description': 'pool description',
|
||||
'subnet_id': TEST.subnets.first().id,
|
||||
'protocol': 'HTTP',
|
||||
'lb_method': 'ROUND_ROBIN',
|
||||
'health_monitors': TEST.monitors.list(),
|
||||
'members': ['78a46e5e-eb1a-418a-88c7-0e3f5968b08'],
|
||||
'admin_state_up': True,
|
||||
'status': 'ACTIVE',
|
||||
'provider': 'haproxy'}
|
||||
TEST.api_pools.add(pool_dict)
|
||||
TEST.pools.add(lbaas.Pool(pool_dict))
|
||||
|
||||
# 2nd pool.
|
||||
pool_dict = {'id': '8913dde8-4915-4b90-8d3e-b95eeedb0d50',
|
||||
'tenant_id': '1',
|
||||
'vip_id': 'f0881d38-c3eb-4fee-9763-12de3338041d',
|
||||
'name': 'pool2',
|
||||
'description': 'pool description',
|
||||
'subnet_id': TEST.subnets.first().id,
|
||||
'protocol': 'HTTPS',
|
||||
'lb_method': 'ROUND_ROBIN',
|
||||
'health_monitors': TEST.monitors.list()[0:1],
|
||||
'members': [],
|
||||
'status': 'PENDING_CREATE',
|
||||
'admin_state_up': True}
|
||||
TEST.api_pools.add(pool_dict)
|
||||
TEST.pools.add(lbaas.Pool(pool_dict))
|
||||
|
||||
# 1st vip.
|
||||
vip_dict = {'id': 'abcdef-c3eb-4fee-9763-12de3338041e',
|
||||
'name': 'vip1',
|
||||
'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'],
|
||||
'pool_id': pool_dict['id'],
|
||||
'session_persistence': {'type': 'APP_COOKIE',
|
||||
'cookie_name': 'jssessionid'},
|
||||
'connection_limit': 10,
|
||||
'admin_state_up': True}
|
||||
TEST.api_vips.add(vip_dict)
|
||||
TEST.vips.add(lbaas.Vip(vip_dict))
|
||||
setattr(TEST.pools.first(), 'vip', TEST.vips.first())
|
||||
|
||||
# 2nd vip.
|
||||
vip_dict = {'id': 'f0881d38-c3eb-4fee-9763-12de3338041d',
|
||||
'name': 'vip2',
|
||||
'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'],
|
||||
'pool_id': pool_dict['id'],
|
||||
'session_persistence': {'type': 'APP_COOKIE',
|
||||
'cookie_name': 'jssessionid'},
|
||||
'connection_limit': 10,
|
||||
'admin_state_up': True}
|
||||
TEST.api_vips.add(vip_dict)
|
||||
TEST.vips.add(lbaas.Vip(vip_dict))
|
||||
|
||||
# 1st member.
|
||||
member_dict = {'id': '78a46e5e-eb1a-418a-88c7-0e3f5968b08',
|
||||
'tenant_id': '1',
|
||||
'pool_id': pool_dict['id'],
|
||||
'address': '10.0.0.11',
|
||||
'protocol_port': 80,
|
||||
'weight': 10,
|
||||
'status': 'ACTIVE',
|
||||
'admin_state_up': True}
|
||||
TEST.api_members.add(member_dict)
|
||||
TEST.members.add(lbaas.Member(member_dict))
|
||||
|
||||
# 2nd member.
|
||||
member_dict = {'id': '41ac1f8d-6d9c-49a4-a1bf-41955e651f91',
|
||||
'tenant_id': '1',
|
||||
'pool_id': pool_dict['id'],
|
||||
'address': '10.0.0.12',
|
||||
'protocol_port': 80,
|
||||
'weight': 10,
|
||||
'status': 'ACTIVE',
|
||||
'admin_state_up': True}
|
||||
TEST.api_members.add(member_dict)
|
||||
TEST.members.add(lbaas.Member(member_dict))
|
||||
|
||||
# 1st v6 pool.
|
||||
pool_dict = {'id': 'c2983d70-8ac7-11e4-b116-123b93f75cba',
|
||||
'tenant_id': '1',
|
||||
'vip_id': 'b6598a5e-8ab4-11e4-b116-123b93f75cba',
|
||||
'name': 'v6_pool1',
|
||||
'description': 'pool description',
|
||||
'subnet_id': TEST.subnets.get(name='v6_subnet1').id,
|
||||
'protocol': 'HTTP',
|
||||
'lb_method': 'ROUND_ROBIN',
|
||||
'health_monitors': TEST.monitors.list(),
|
||||
'members': ['78a46e5e-eb1a-418a-88c7-0e3f5968b08'],
|
||||
'admin_state_up': True,
|
||||
'status': 'ACTIVE',
|
||||
'provider': 'haproxy'}
|
||||
TEST.api_pools.add(pool_dict)
|
||||
TEST.pools.add(lbaas.Pool(pool_dict))
|
||||
|
||||
# 1st v6 vip.
|
||||
vip_dict = {'id': 'b6598a5e-8ab4-11e4-b116-123b93f75cba',
|
||||
'name': 'v6_vip1',
|
||||
'address': 'ff09::03',
|
||||
'description': 'vip description',
|
||||
'subnet_id': TEST.subnets.get(name="v6_subnet1").id,
|
||||
'port_id': TEST.ports.first().id,
|
||||
'subnet': TEST.subnets.get(name="v6_subnet1").cidr,
|
||||
'protocol_port': 80,
|
||||
'protocol': pool_dict['protocol'],
|
||||
'pool_id': pool_dict['id'],
|
||||
'session_persistence': {'type': 'APP_COOKIE',
|
||||
'cookie_name': 'jssessionid'},
|
||||
'connection_limit': 10,
|
||||
'admin_state_up': True}
|
||||
TEST.api_vips.add(vip_dict)
|
||||
TEST.vips.add(lbaas.Vip(vip_dict))
|
||||
|
||||
# 2nd v6 vip.
|
||||
vip_dict = {'id': 'b6598cc0-8ab4-11e4-b116-123b93f75cba',
|
||||
'name': 'ff09::04',
|
||||
'address': '10.0.0.110',
|
||||
'description': 'vip description',
|
||||
'subnet_id': TEST.subnets.get(name="v6_subnet2").id,
|
||||
'port_id': TEST.ports.list()[0].id,
|
||||
'subnet': TEST.subnets.get(name="v6_subnet2").cidr,
|
||||
'protocol_port': 80,
|
||||
'protocol': pool_dict['protocol'],
|
||||
'pool_id': pool_dict['id'],
|
||||
'session_persistence': {'type': 'APP_COOKIE',
|
||||
'cookie_name': 'jssessionid'},
|
||||
'connection_limit': 10,
|
||||
'admin_state_up': True}
|
||||
TEST.api_vips.add(vip_dict)
|
||||
TEST.vips.add(lbaas.Vip(vip_dict))
|
||||
|
||||
# 1st v6 monitor.
|
||||
monitor_dict = {'id': '0dc936f8-8aca-11e4-b116-123b93f75cba',
|
||||
'type': 'http',
|
||||
'delay': 10,
|
||||
'timeout': 10,
|
||||
'max_retries': 10,
|
||||
'http_method': 'GET',
|
||||
'url_path': '/',
|
||||
'expected_codes': '200',
|
||||
'admin_state_up': True,
|
||||
"pools": [{"pool_id": TEST.pools.get(name='v6_pool1').id}],
|
||||
}
|
||||
TEST.api_monitors.add(monitor_dict)
|
||||
TEST.monitors.add(lbaas.PoolMonitor(monitor_dict))
|
||||
|
||||
# v6 member.
|
||||
member_dict = {'id': '6bc03d1e-8ad0-11e4-b116-123b93f75cba',
|
||||
'tenant_id': '1',
|
||||
'pool_id': TEST.pools.get(name='v6_pool1').id,
|
||||
'address': 'ff09::03',
|
||||
'protocol_port': 80,
|
||||
'weight': 10,
|
||||
'status': 'ACTIVE',
|
||||
'member_type': 'server_list',
|
||||
'admin_state_up': True}
|
||||
TEST.api_members.add(member_dict)
|
||||
TEST.members.add(lbaas.Member(member_dict))
|
||||
|
||||
# 1st monitor.
|
||||
monitor_dict = {'id': 'd4a0500f-db2b-4cc4-afcf-ec026febff96',
|
||||
'type': 'http',
|
||||
'delay': 10,
|
||||
'timeout': 10,
|
||||
'max_retries': 10,
|
||||
'http_method': 'GET',
|
||||
'url_path': '/',
|
||||
'expected_codes': '200',
|
||||
'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))
|
||||
|
||||
# 2nd monitor.
|
||||
monitor_dict = {'id': 'd4a0500f-db2b-4cc4-afcf-ec026febff97',
|
||||
'type': 'ping',
|
||||
'delay': 10,
|
||||
'timeout': 10,
|
||||
'max_retries': 10,
|
||||
'admin_state_up': True,
|
||||
'pools': [],
|
||||
}
|
||||
TEST.api_monitors.add(monitor_dict)
|
||||
TEST.monitors.add(lbaas.PoolMonitor(monitor_dict))
|
||||
|
||||
# Quotas.
|
||||
quota_data = {'network': '10',
|
||||
'subnet': '10',
|
||||
@ -828,15 +622,11 @@ def data(TEST):
|
||||
extension_5 = {"name": "HA Router extension",
|
||||
"alias": "l3-ha",
|
||||
"description": "Add HA capability to routers."}
|
||||
extension_6 = {"name": "LoadBalancing service",
|
||||
"alias": "lbaas",
|
||||
"description": "Extension for LoadBalancing service"}
|
||||
TEST.api_extensions.add(extension_1)
|
||||
TEST.api_extensions.add(extension_2)
|
||||
TEST.api_extensions.add(extension_3)
|
||||
TEST.api_extensions.add(extension_4)
|
||||
TEST.api_extensions.add(extension_5)
|
||||
TEST.api_extensions.add(extension_6)
|
||||
|
||||
# 1st agent.
|
||||
agent_dict = {"binary": "neutron-openvswitch-agent",
|
||||
|
@ -0,0 +1,9 @@
|
||||
---
|
||||
upgrade:
|
||||
- LBaaS v1 dashboard has been removed.
|
||||
LBaaS v1 feature was removed from neutron-lbaas in Newton, but LBaaS v1
|
||||
dashboard in Horizon has been kept only for backward compatibility in
|
||||
Newton release so that operators can upgrade Horizon first.
|
||||
Note that the Dashboard support for LBaaS v2 is provided as
|
||||
a Horizon plugin via `neutron-lbaas-dashboard project
|
||||
<http://git.openstack.org/cgit/openstack/neutron-lbaas-dashboard/>`__.
|
Loading…
Reference in New Issue
Block a user