blueprint quantum-lbaas

This implements the UI for quantum lbaas. Add/Delete Pool, Vip, Member,
PoolMonitor for lbaas is implemented.

Expected future extensions:
1. Update Vip/Pool/Member
2. Use of floating IP as VIP

Change-Id: Icde452b97a5e02d2381b996350b13aba3d87bb8e
This commit is contained in:
KC Wang 2013-02-07 18:15:16 -08:00
parent 94c186ccf4
commit 4f81c933a1
28 changed files with 2317 additions and 10 deletions

View File

@ -5,6 +5,7 @@
# All Rights Reserved.
#
# Copyright 2012 Nebula, Inc.
# Copyright 2013 Big Switch Networks
#
# 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
@ -32,11 +33,12 @@ In other words, Horizon developers not working on openstack_dashboard.api
shouldn't need to understand the finer details of APIs for
Keystone/Nova/Glance/Swift et. al.
"""
import base
import cinder
import glance
import keystone
import network
import nova
import quantum
import swift
from openstack_dashboard.api import base
from openstack_dashboard.api import cinder
from openstack_dashboard.api import glance
from openstack_dashboard.api import keystone
from openstack_dashboard.api import network
from openstack_dashboard.api import nova
from openstack_dashboard.api import quantum
from openstack_dashboard.api import lbaas
from openstack_dashboard.api import swift

View File

@ -0,0 +1,291 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# 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 openstack_dashboard.api.quantum import QuantumAPIDictWrapper
from openstack_dashboard.api.quantum import quantumclient
from openstack_dashboard.api.quantum import subnet_get
class Vip(QuantumAPIDictWrapper):
"""Wrapper for quantum load balancer vip"""
def __init__(self, apiresource):
super(Vip, self).__init__(apiresource)
class Pool(QuantumAPIDictWrapper):
"""Wrapper for quantum load balancer pool"""
def __init__(self, apiresource):
super(Pool, self).__init__(apiresource)
class AttributeDict(dict):
def __getattr__(self, attr):
return self[attr]
def __setattr__(self, attr, value):
self[attr] = value
def readable(self, request):
pFormatted = {'id': self.id,
'name': self.name,
'description': self.description,
'protocol': self.protocol}
try:
pFormatted['subnet_id'] = self.subnet_id
pFormatted['subnet_name'] = subnet_get(
request, self.subnet_id).cidr
except:
pFormatted['subnet_id'] = self.subnet_id
pFormatted['subnet_name'] = self.subnet_id
if self.vip_id is not None:
try:
pFormatted['vip_id'] = self.vip_id
pFormatted['vip_name'] = vip_get(
request, self.vip_id).name
except:
pFormatted['vip_id'] = self.vip_id
pFormatted['vip_name'] = self.vip_id
else:
pFormatted['vip_id'] = None
pFormatted['vip_name'] = None
return self.AttributeDict(pFormatted)
class Member(QuantumAPIDictWrapper):
"""Wrapper for quantum load balancer member"""
def __init__(self, apiresource):
super(Member, self).__init__(apiresource)
class AttributeDict(dict):
def __getattr__(self, attr):
return self[attr]
def __setattr__(self, attr, value):
self[attr] = value
def readable(self, request):
mFormatted = {'id': self.id,
'address': self.address,
'protocol_port': self.protocol_port}
try:
mFormatted['pool_id'] = self.pool_id
mFormatted['pool_name'] = pool_get(
request, self.pool_id).name
except:
mFormatted['pool_id'] = self.pool_id
mFormatted['pool_name'] = self.pool_id
return self.AttributeDict(mFormatted)
class PoolStats(QuantumAPIDictWrapper):
"""Wrapper for quantum load balancer pool stats"""
def __init__(self, apiresource):
super(PoolStats, self).__init__(apiresource)
class PoolMonitor(QuantumAPIDictWrapper):
"""Wrapper for quantum load balancer pool health monitor"""
def __init__(self, apiresource):
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': {'address': kwargs['address'],
'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'],
'connection_limit': kwargs['connection_limit'],
'admin_state_up': kwargs['admin_state_up']
}}
vip = quantumclient(request).create_vip(body).get('vip')
return Vip(vip)
def vips_get(request, **kwargs):
vips = quantumclient(request).list_vips().get('vips')
return [Vip(v) for v in vips]
def vip_get(request, vip_id):
vip = quantumclient(request).show_vip(vip_id).get('vip')
return Vip(vip)
# not linked to UI yet
def vip_update(request, vip_id, **kwargs):
vip = quantumclient(request).update_vip(vip_id, kwargs).get('vip')
return Vip(vip)
def vip_delete(request, vip_id):
quantumclient(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']
}}
pool = quantumclient(request).create_pool(body).get('pool')
return Pool(pool)
def pools_get(request, **kwargs):
pools = quantumclient(request).list_pools().get('pools')
return [Pool(p) for p in pools]
def pool_get(request, pool_id):
pool = quantumclient(request).show_pool(pool_id).get('pool')
return Pool(pool)
def pool_update(request, pool_id, **kwargs):
pool = quantumclient(request).update_pool(pool_id, kwargs).get('pool')
return Pool(pool)
def pool_delete(request, pool):
quantumclient(request).delete_pool(pool)
# not linked to UI yet
def pool_stats(request, pool_id, **kwargs):
stats = quantumclient(request).retrieve_pool_stats(pool_id, **kwargs)
return PoolStats(stats)
def pool_health_monitor_create(request, **kwargs):
"""Create a health monitor and associate with pool
: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
"""
body = {'health_monitor': {'type': kwargs['type'],
'delay': kwargs['delay'],
'timeout': kwargs['timeout'],
'max_retries': kwargs['max_retries'],
'http_method': kwargs['http_method'],
'url_path': kwargs['url_path'],
'expected_codes': kwargs['expected_codes'],
'admin_state_up': kwargs['admin_state_up']
}}
mon = quantumclient(request).create_health_monitor(body).get(
'health_monitor')
body = {'health_monitor': {'id': mon['id']}}
quantumclient(request).associate_health_monitor(
kwargs['pool_id'], body)
return PoolMonitor(mon)
def pool_health_monitors_get(request, **kwargs):
monitors = quantumclient(request
).list_health_monitors().get('health_monitors')
return [PoolMonitor(m) for m in monitors]
def pool_health_monitor_get(request, monitor_id):
monitor = quantumclient(request
).show_health_monitor(monitor_id
).get('health_monitor')
return PoolMonitor(monitor)
def pool_health_monitor_delete(request, mon_id):
quantumclient(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'],
'weight': kwargs['weight'],
'admin_state_up': kwargs['admin_state_up']
}}
member = quantumclient(request).create_member(body).get('member')
return Member(member)
def members_get(request, **kwargs):
members = quantumclient(request).list_members().get('members')
return [Member(m) for m in members]
def member_get(request, member_id):
member = quantumclient(request).show_member(member_id).get('member')
return Member(member)
# not linked to UI yet
def member_update(request, member_id, **kwargs):
member = quantumclient(request).update_member(member_id, kwargs)
return Member(member)
def member_delete(request, mem_id):
quantumclient(request).delete_member(mem_id)

View File

@ -29,7 +29,8 @@ class BasePanels(horizon.PanelGroup):
'access_and_security',
'networks',
'routers',
'network_topology')
'network_topology',
'loadbalancers')
class ObjectStorePanels(horizon.PanelGroup):

View File

@ -0,0 +1,3 @@
"""
Stub file to work around django bug: https://code.djangoproject.com/ticket/7198
"""

View File

@ -0,0 +1,16 @@
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
import horizon
from openstack_dashboard.dashboards.project import dashboard
class LoadBalancer(horizon.Panel):
name = _("Load Balancers")
slug = "loadbalancers"
permissions = ('openstack.services.network',)
if hasattr(settings, 'OPENSTACK_QUANTUM_NETWORK'):
if getattr(settings, 'OPENSTACK_QUANTUM_NETWORK')['enable_lb']:
dashboard.Project.register(LoadBalancer)

View File

@ -0,0 +1,162 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# 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 import http
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
from horizon import tables
from openstack_dashboard import api
LOG = logging.getLogger(__name__)
class AddPoolLink(tables.LinkAction):
name = "addpool"
verbose_name = _("Add Pool")
url = "horizon:project:loadbalancers:addpool"
classes = ("btn-addpool",)
class AddVipLink(tables.LinkAction):
name = "addvip"
verbose_name = _("Add Vip")
classes = ("btn-addvip",)
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 = ("btn-addmember",)
class AddMonitorLink(tables.LinkAction):
name = "addmonitor"
verbose_name = _("Add Monitor")
url = "horizon:project:loadbalancers:addmonitor"
classes = ("btn-addmonitor",)
class DeleteVipLink(tables.DeleteAction):
name = "deletevip"
action_present = _("Delete")
action_past = _("Scheduled deletion of")
data_type_singular = _("Vip")
data_type_plural = _("Vips")
def allowed(self, request, datum=None):
if datum and not datum.vip_id:
return False
return True
class DeletePoolLink(tables.DeleteAction):
name = "deletepool"
action_present = _("Delete")
action_past = _("Scheduled deletion of")
data_type_singular = _("Pool")
data_type_plural = _("Pools")
class DeleteMonitorLink(tables.DeleteAction):
name = "deletemonitor"
action_present = _("Delete")
action_past = _("Scheduled deletion of")
data_type_singular = _("Monitor")
data_type_plural = _("Monitors")
class DeleteMemberLink(tables.DeleteAction):
name = "deletemember"
action_present = _("Delete")
action_past = _("Scheduled deletion of")
data_type_singular = _("Member")
data_type_plural = _("Members")
def get_vip_link(pool):
return reverse("horizon:project:loadbalancers:vipdetails",
args=(http.urlquote(pool.vip_id),))
class PoolsTable(tables.DataTable):
name = tables.Column("name",
verbose_name=_("Name"),
link="horizon:project:loadbalancers:pooldetails")
description = tables.Column('description', verbose_name=_("Description"))
subnet_name = tables.Column('subnet_name', verbose_name=_("Subnet"))
protocol = tables.Column('protocol', verbose_name=_("Protocol"))
vip_name = tables.Column('vip_name', verbose_name=_("VIP"),
link=get_vip_link)
class Meta:
name = "poolstable"
verbose_name = _("Pools")
table_actions = (AddPoolLink, DeletePoolLink)
row_actions = (AddVipLink, DeleteVipLink, DeletePoolLink)
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 MembersTable(tables.DataTable):
address = tables.Column('address',
verbose_name=_("IP Address"),
link=get_member_link)
protocol_port = tables.Column('protocol_port',
verbose_name=_("Protocol Port"))
pool_name = tables.Column("pool_name",
verbose_name=_("Pool"), link=get_pool_link)
class Meta:
name = "memberstable"
verbose_name = _("Members")
table_actions = (AddMemberLink, DeleteMemberLink)
row_actions = (DeleteMemberLink,)
class MonitorsTable(tables.DataTable):
id = tables.Column("id",
verbose_name=_("ID"),
link="horizon:project:loadbalancers:monitordetails")
monitorType = tables.Column('type', verbose_name=_("Monitor Type"))
class Meta:
name = "monitorstable"
verbose_name = _("Monitors")
table_actions = (AddMonitorLink, DeleteMonitorLink)
row_actions = (DeleteMonitorLink,)

View File

@ -0,0 +1,170 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# 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 re
from django.utils.translation import ugettext as _
from horizon import exceptions
from horizon import tabs
from horizon import tables
from openstack_dashboard import api
from .tables import PoolsTable, MembersTable, MonitorsTable
class PoolsTab(tabs.TableTab):
table_classes = (PoolsTable,)
name = _("Pools")
slug = "pools"
template_name = "horizon/common/_detail_table.html"
def get_poolstable_data(self):
try:
pools = api.lbaas.pools_get(self.tab_group.request)
poolsFormatted = [p.readable(self.tab_group.request) for
p in pools]
except:
poolsFormatted = []
exceptions.handle(self.tab_group.request,
_('Unable to retrieve pools list.'))
return poolsFormatted
class MembersTab(tabs.TableTab):
table_classes = (MembersTable,)
name = _("Members")
slug = "members"
template_name = "horizon/common/_detail_table.html"
def get_memberstable_data(self):
try:
members = api.lbaas.members_get(self.tab_group.request)
membersFormatted = [m.readable(self.tab_group.request) for
m in members]
except:
membersFormatted = []
exceptions.handle(self.tab_group.request,
_('Unable to retrieve member list.'))
return membersFormatted
class MonitorsTab(tabs.TableTab):
table_classes = (MonitorsTable,)
name = _("Monitors")
slug = "monitors"
template_name = "horizon/common/_detail_table.html"
def get_monitorstable_data(self):
try:
monitors = api.lbaas.pool_health_monitors_get(
self.tab_group.request)
except:
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):
pid = self.tab_group.kwargs['pool_id']
try:
pool = api.lbaas.pool_get(request, pid)
except:
pool = []
exceptions.handle(request,
_('Unable to retrieve pool details.'))
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):
vid = self.tab_group.kwargs['vip_id']
try:
vip = api.lbaas.vip_get(request, vid)
except:
vip = []
exceptions.handle(self.tab_group.request,
_('Unable to retrieve vip details.'))
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):
mid = self.tab_group.kwargs['member_id']
try:
member = api.lbaas.member_get(request, mid)
except:
member = []
exceptions.handle(self.tab_group.request,
_('Unable to retrieve member details.'))
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):
mid = self.tab_group.kwargs['monitor_id']
try:
monitor = api.lbaas.pool_health_monitor_get(request, mid)
except:
monitor = []
exceptions.handle(self.tab_group.request,
_('Unable to retrieve monitor details.'))
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,)

View File

@ -0,0 +1,30 @@
{% load i18n sizeformat parse_date %}
<div class="info row-fluid detail">
<hr class="header_rule">
<dl>
<dt>{% trans "ID: " %}</dt>
<dd>{{ member.id }}</dd>
<dt>{% trans "Tenant ID: " %}</dt>
<dd>{{ member.tenant_id }}</dd>
<dt>{% trans "Pool ID: " %}</dt>
<dd>{{ member.pool_id }}</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 }}</dd>
<dt>{% trans "Status: " %}</dt>
<dd>{{ member.status }}</dd>
</dl>
</div>

View File

@ -0,0 +1,5 @@
{% block main %}
{{ table.render }}
{% endblock %}

View File

@ -0,0 +1,39 @@
{% load i18n sizeformat parse_date %}
<div class="info row-fluid detail">
<hr class="header_rule">
<dl>
<dt>{% trans "ID: " %}</dt>
<dd>{{ monitor.id }}</dd>
<dt>{% trans "Tenant 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>
<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>
<dt>{% trans "Admin State Up: " %}</dt>
<dd>{{ monitor.admin_state_up }}</dd>
<dt>{% trans "Status: " %}</dt>
<dd>{{ monitor.status }}</dd>
</dl>
</div>

View File

@ -0,0 +1,5 @@
{% block main %}
{{ table.render }}
{% endblock %}

View File

@ -0,0 +1,42 @@
{% load i18n sizeformat parse_date %}
<div class="info row-fluid detail">
<hr class="header_rule">
<dl>
<dt>{% trans "ID: " %}</dt>
<dd>{{ pool.id }}</dd>
<dt>{% trans "Tenant ID: " %}</dt>
<dd>{{ pool.tenant_id }}</dd>
<dt>{% trans "VIP ID: " %}</dt>
<dd>{{ pool.vip_id }}</dd>
<dt>{% trans "Name: " %}</dt>
<dd>{{ pool.name }}</dd>
<dt>{% trans "Description: " %}</dt>
<dd>{{ pool.description }}</dd>
<dt>{% trans "Subnet ID: " %}</dt>
<dd>{{ pool.subnet_id }}</dd>
<dt>{% trans "Protocol: " %}</dt>
<dd>{{ pool.protocol }}</dd>
<dt>{% trans "Load Balancing Method: " %}</dt>
<dd>{{ pool.lb_method }}</dd>
<dt>{% trans "Members: " %}</dt>
<dd>{{ pool.members }}</dd>
<dt>{% trans "Health Monitors: " %}</dt>
<dd>{{ pool.health_monitors }}</dd>
<dt>{% trans "Admin State Up: " %}</dt>
<dd>{{ pool.admin_state_up }}</dd>
<dt>{% trans "Status: " %}</dt>
<dd>{{ pool.status }}</dd>
</dl>
</div>

View File

@ -0,0 +1,5 @@
{% block main %}
{{ poolstable.render }}
{% endblock %}

View File

@ -0,0 +1,48 @@
{% load i18n sizeformat parse_date %}
<div class="info row-fluid detail">
<hr class="header_rule">
<dl>
<dt>{% trans "ID: " %}</dt>
<dd>{{ vip.id }}</dd>
<dt>{% trans "Tenant ID: " %}</dt>
<dd>{{ vip.tenant_id }}</dd>
<dt>{% trans "Name: " %}</dt>
<dd>{{ vip.name }}</dd>
<dt>{% trans "Description: " %}</dt>
<dd>{{ vip.description }}</dd>
<dt>{% trans "Subnet ID: " %}</dt>
<dd>{{ vip.subnet_id }}</dd>
<dt>{% trans "Address: " %}</dt>
<dd>{{ vip.address }}</dd>
<dt>{% trans "Protocol Port: " %}</dt>
<dd>{{ vip.protocol_port }}</dd>
<dt>{% trans "Protocol: " %}</dt>
<dd>{{ vip.protocol }}</dd>
<dt>{% trans "Pool ID: " %}</dt>
<dd>{{ vip.pool_id }}</dd>
<dt>{% trans "Session Persistence: " %}</dt>
<dd>{% trans "Type: " %}{{ vip.session_persistence.type }}</dd>
{% if vip.session_persistence.cookie_name %}
<dd>{% trans "Cookie Name: " %}{{ vip.session_persistence.cookie_name }}</dd>
{% endif %}
<dt>{% trans "Connection Limit: " %}</dt>
<dd>{{ vip.connection_limit }}</dd>
<dt>{% trans "Admin State Up: " %}</dt>
<dd>{{ vip.admin_state_up }}</dd>
<dt>{% trans "Status: " %}</dt>
<dd>{{ vip.status }}</dd>
</dl>
</div>

View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Add New Member" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Add New Member") %}
{% endblock page_header %}
{% block main %}
{% include 'horizon/common/_workflow.html' %}
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Add New Monitor" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Add New Monitor") %}
{% endblock page_header %}
{% block main %}
{% include 'horizon/common/_workflow.html' %}
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Add New Pool" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Add New Pool") %}
{% endblock page_header %}
{% block main %}
{% include 'horizon/common/_workflow.html' %}
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Specify Vip" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Specify Vip") %}
{% endblock page_header %}
{% block main %}
{% include 'horizon/common/_workflow.html' %}
{% endblock %}

View File

@ -0,0 +1,15 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Load Balancer" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Load Balancer") %}
{% endblock page_header %}
{% block main %}
<div class="row-fluid">
<div class="span12">
{{ tab_group.render }}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,299 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
import json
from mox import IsA
from django import http
from django.core.urlresolvers import reverse
from openstack_dashboard import api
from openstack_dashboard.test import helpers as test
from openstack_dashboard.api.lbaas import Pool, Vip, Member, PoolMonitor
from .tabs import LoadBalancerTabs, MembersTab, PoolsTab, MonitorsTab
from .workflows import AddPool, AddMember, AddMonitor, AddVip
class LoadBalancerTests(test.TestCase):
class AttributeDict(dict):
def __getattr__(self, attr):
return self[attr]
def __setattr__(self, attr, value):
self[attr] = value
DASHBOARD = 'project'
INDEX_URL = reverse('horizon:%s:loadbalancers:index' % DASHBOARD)
ADDPOOL_PATH = 'horizon:%s:loadbalancers:addpool' % DASHBOARD
ADDVIP_PATH = 'horizon:%s:loadbalancers:addvip' % DASHBOARD
ADDMEMBER_PATH = 'horizon:%s:loadbalancers:addmember' % DASHBOARD
ADDMONITOR_PATH = 'horizon:%s:loadbalancers:addmonitor' % DASHBOARD
POOL_DETAIL_PATH = 'horizon:%s:loadbalancers:pooldetails' % DASHBOARD
VIP_DETAIL_PATH = 'horizon:%s:loadbalancers:vipdetails' % DASHBOARD
MEMBER_DETAIL_PATH = 'horizon:%s:loadbalancers:memberdetails' % DASHBOARD
MONITOR_DETAIL_PATH = 'horizon:%s:loadbalancers:monitordetails' % DASHBOARD
def set_up_expect(self):
# retrieve pools
subnet = self.subnets.first()
vip1 = self.vips.first()
vip2 = self.vips.list()[1]
api.lbaas.pools_get(
IsA(http.HttpRequest)).AndReturn(self.pools.list())
api.lbaas.vip_get(IsA(http.HttpRequest), vip1.id).AndReturn(vip1)
api.lbaas.vip_get(IsA(http.HttpRequest), vip2.id).AndReturn(vip2)
# retrieves members
api.lbaas.members_get(
IsA(http.HttpRequest)).AndReturn(self.members.list())
pool1 = self.pools.first()
pool2 = self.pools.list()[1]
api.lbaas.pool_get(IsA(http.HttpRequest),
self.members.list()[0].pool_id).AndReturn(pool1)
api.lbaas.pool_get(IsA(http.HttpRequest),
self.members.list()[1].pool_id).AndReturn(pool2)
# retrieves monitors
api.lbaas.pool_health_monitors_get(
IsA(http.HttpRequest)).AndReturn(self.monitors.list())
def set_up_expect_with_exception(self):
api.lbaas.pools_get(
IsA(http.HttpRequest)).AndRaise(self.exceptions.quantum)
api.lbaas.members_get(
IsA(http.HttpRequest)).AndRaise(self.exceptions.quantum)
api.lbaas.pool_health_monitors_get(
IsA(http.HttpRequest)).AndRaise(self.exceptions.quantum)
@test.create_stubs({api.lbaas: ('pools_get', 'vip_get',
'members_get', 'pool_get',
'pool_health_monitors_get'),
api.quantum: ('subnet_get',)})
def test_index_pools(self):
self.set_up_expect()
self.mox.ReplayAll()
res = self.client.get(self.INDEX_URL)
self.assertTemplateUsed(res, '%s/loadbalancers/details_tabs.html'
% self.DASHBOARD)
self.assertTemplateUsed(res, 'horizon/common/_detail_table.html')
self.assertEqual(len(res.context['table'].data),
len(self.pools.list()))
@test.create_stubs({api.lbaas: ('pools_get', 'vip_get',
'members_get', 'pool_get',
'pool_health_monitors_get'),
api.quantum: ('subnet_get',)})
def test_index_members(self):
self.set_up_expect()
self.mox.ReplayAll()
res = self.client.get(self.INDEX_URL + '?tab=lbtabs__members')
self.assertTemplateUsed(res, '%s/loadbalancers/details_tabs.html'
% self.DASHBOARD)
self.assertTemplateUsed(res, 'horizon/common/_detail_table.html')
self.assertEqual(len(res.context['memberstable_table'].data),
len(self.members.list()))
@test.create_stubs({api.lbaas: ('pools_get', 'vip_get',
'pool_health_monitors_get',
'members_get', 'pool_get'),
api.quantum: ('subnet_get',)})
def test_index_monitors(self):
self.set_up_expect()
self.mox.ReplayAll()
res = self.client.get(self.INDEX_URL + '?tab=lbtabs__monitors')
self.assertTemplateUsed(res, '%s/loadbalancers/details_tabs.html'
% self.DASHBOARD)
self.assertTemplateUsed(res, 'horizon/common/_detail_table.html')
self.assertEqual(len(res.context['monitorstable_table'].data),
len(self.monitors.list()))
@test.create_stubs({api.lbaas: ('pools_get', 'members_get',
'pool_health_monitors_get')})
def test_index_exception_pools(self):
self.set_up_expect_with_exception()
self.mox.ReplayAll()
res = self.client.get(self.INDEX_URL)
self.assertTemplateUsed(res,
'%s/loadbalancers/details_tabs.html'
% self.DASHBOARD)
self.assertTemplateUsed(res,
'horizon/common/_detail_table.html')
self.assertEqual(len(res.context['table'].data), 0)
@test.create_stubs({api.lbaas: ('pools_get', 'members_get',
'pool_health_monitors_get')})
def test_index_exception_members(self):
self.set_up_expect_with_exception()
self.mox.ReplayAll()
res = self.client.get(self.INDEX_URL + '?tab=lbtabs__members')
self.assertTemplateUsed(res,
'%s/loadbalancers/details_tabs.html'
% self.DASHBOARD)
self.assertTemplateUsed(res,
'horizon/common/_detail_table.html')
self.assertEqual(len(res.context['memberstable_table'].data), 0)
@test.create_stubs({api.lbaas: ('pools_get', 'members_get',
'pool_health_monitors_get')})
def test_index_exception_monitors(self):
self.set_up_expect_with_exception()
self.mox.ReplayAll()
res = self.client.get(self.INDEX_URL + '?tab=lbtabs__monitors')
self.assertTemplateUsed(res,
'%s/loadbalancers/details_tabs.html'
% self.DASHBOARD)
self.assertTemplateUsed(res,
'horizon/common/_detail_table.html')
self.assertEqual(len(res.context['monitorstable_table'].data), 0)
@test.create_stubs({api.quantum: ('network_list_for_tenant',),
api.lbaas: ('pool_create', )})
def test_add_pool_post(self):
pool = self.pools.first()
subnet = self.subnets.first()
networks = [{'subnets': [subnet, ]}, ]
api.quantum.network_list_for_tenant(
IsA(http.HttpRequest), subnet.tenant_id).AndReturn(networks)
api.lbaas.pool_create(
IsA(http.HttpRequest),
name=pool.name,
description=pool.description,
subnet_id=pool.subnet_id,
protocol=pool.protocol,
lb_method=pool.lb_method,
admin_state_up=pool.admin_state_up).AndReturn(Pool(pool))
self.mox.ReplayAll()
form_data = {'name': pool.name,
'description': pool.description,
'subnet_id': pool.subnet_id,
'protocol': pool.protocol,
'lb_method': pool.lb_method,
'admin_state_up': pool.admin_state_up}
res = self.client.post(reverse(self.ADDPOOL_PATH), form_data)
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, self.INDEX_URL)
@test.create_stubs({api.quantum: ('network_list_for_tenant',)})
def test_add_pool_get(self):
subnet = self.subnets.first()
networks = [{'subnets': [subnet, ]}, ]
api.quantum.network_list_for_tenant(
IsA(http.HttpRequest), subnet.tenant_id).AndReturn(networks)
self.mox.ReplayAll()
res = self.client.get(reverse(self.ADDPOOL_PATH))
workflow = res.context['workflow']
self.assertTemplateUsed(res, 'project/loadbalancers/addpool.html')
self.assertEqual(workflow.name, AddPool.name)
expected_objs = ['<AddPoolStep: addpoolaction>', ]
self.assertQuerysetEqual(workflow.steps, expected_objs)
@test.create_stubs({api.lbaas: ('pools_get', 'member_create'),
api.quantum: ('port_list',),
api.nova: ('server_list',)})
def test_add_member_post(self):
member = self.members.first()
server1 = self.AttributeDict({'id':
'12381d38-c3eb-4fee-9763-12de3338042e',
'name': 'vm1'})
server2 = self.AttributeDict({'id':
'12381d38-c3eb-4fee-9763-12de3338043e',
'name': 'vm2'})
port1 = self.AttributeDict(
{'fixed_ips': [{'ip_address': member.address}]})
api.lbaas.pools_get(IsA(http.HttpRequest)).AndReturn(self.pools.list())
api.nova.server_list(IsA(http.HttpRequest)).AndReturn([server1,
server2])
api.quantum.port_list(IsA(http.HttpRequest),
device_id=server1.id).AndReturn([port1, ])
api.lbaas.member_create(
IsA(http.HttpRequest),
pool_id=member.pool_id,
address=member.address,
protocol_port=member.protocol_port,
weight=member.weight,
members=[server1.id],
admin_state_up=member.admin_state_up).AndReturn(Member(member))
self.mox.ReplayAll()
form_data = {'pool_id': member.pool_id,
'address': member.address,
'protocol_port': member.protocol_port,
'weight': member.weight,
'members': [server1.id],
'admin_state_up': member.admin_state_up}
res = self.client.post(reverse(self.ADDMEMBER_PATH), form_data)
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, self.INDEX_URL)
@test.create_stubs({api.lbaas: ('pools_get',),
api.nova: ('server_list',)})
def test_add_member_get(self):
server1 = self.AttributeDict({'id':
'12381d38-c3eb-4fee-9763-12de3338042e',
'name': 'vm1'})
server2 = self.AttributeDict({'id':
'12381d38-c3eb-4fee-9763-12de3338043e',
'name': 'vm2'})
api.lbaas.pools_get(IsA(http.HttpRequest)).AndReturn(self.pools.list())
api.nova.server_list(
IsA(http.HttpRequest)).AndReturn([server1, server2])
self.mox.ReplayAll()
res = self.client.get(reverse(self.ADDMEMBER_PATH))
workflow = res.context['workflow']
self.assertTemplateUsed(res, 'project/loadbalancers/addmember.html')
self.assertEqual(workflow.name, AddMember.name)
expected_objs = ['<AddMemberStep: addmemberaction>', ]
self.assertQuerysetEqual(workflow.steps, expected_objs)

View File

@ -0,0 +1,38 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# 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.defaults import url, patterns
from .views import IndexView
from .views import AddPoolView, AddMemberView, AddMonitorView, AddVipView
from .views import PoolDetailsView, VipDetailsView
from .views import MemberDetailsView, MonitorDetailsView
urlpatterns = patterns(
'openstack_dashboard.dashboards.project.loadbalancers.views',
url(r'^$', IndexView.as_view(), name='index'),
url(r'^addpool$', AddPoolView.as_view(), name='addpool'),
url(r'^addvip/(?P<pool_id>[^/]+)/$', AddVipView.as_view(), name='addvip'),
url(r'^addmember$', AddMemberView.as_view(), name='addmember'),
url(r'^addmonitor$', AddMonitorView.as_view(), name='addmonitor'),
url(r'^pool/(?P<pool_id>[^/]+)/$',
PoolDetailsView.as_view(), name='pooldetails'),
url(r'^vip/(?P<vip_id>[^/]+)/$',
VipDetailsView.as_view(), name='vipdetails'),
url(r'^member/(?P<member_id>[^/]+)/$',
MemberDetailsView.as_view(), name='memberdetails'),
url(r'^monitor/(?P<monitor_id>[^/]+)/$',
MonitorDetailsView.as_view(), name='monitordetails'))

View File

@ -0,0 +1,152 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# 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
import re
from django import http
from django.utils.translation import ugettext as _
from horizon import exceptions
from horizon import tables
from horizon import tabs
from horizon import workflows
from openstack_dashboard import api
from .workflows import AddPool, AddMember, AddMonitor, AddVip
from .tabs import LoadBalancerTabs, PoolDetailsTabs, VipDetailsTabs
from .tabs import MemberDetailsTabs, MonitorDetailsTabs
from .tables import DeleteMonitorLink
LOG = logging.getLogger(__name__)
class IndexView(tabs.TabView):
tab_group_class = (LoadBalancerTabs)
template_name = 'project/loadbalancers/details_tabs.html'
def post(self, request, *args, **kwargs):
obj_ids = request.POST.getlist('object_ids')
action = request.POST['action']
m = re.search('.delete([a-z]+)', action).group(1)
if obj_ids == []:
obj_ids.append(re.search('([0-9a-z-]+)$', action).group(1))
if m == 'monitor':
for obj_id in obj_ids:
try:
api.lbaas.pool_health_monitor_delete(request, obj_id)
except:
exceptions.handle(request,
_('Unable to delete monitor.'))
if m == 'pool':
for obj_id in obj_ids:
try:
api.lbaas.pool_delete(request, obj_id)
except:
exceptions.handle(request,
_('Must delete Vip first.'))
if m == 'member':
for obj_id in obj_ids:
try:
api.lbaas.member_delete(request, obj_id)
except:
exceptions.handle(request,
_('Unable to delete member.'))
if m == 'vip':
for obj_id in obj_ids:
try:
vip_id = api.lbaas.pool_get(request, obj_id).vip_id
except:
exceptions.handle(request,
_('Unable to locate vip to delete.'))
if vip_id is not None:
try:
api.lbaas.vip_delete(request, vip_id)
except:
exceptions.handle(request,
_('Unable to delete vip.'))
return self.get(request, *args, **kwargs)
class AddPoolView(workflows.WorkflowView):
workflow_class = AddPool
template_name = "project/loadbalancers/addpool.html"
def get_initial(self):
initial = super(AddPoolView, self).get_initial()
return initial
class AddVipView(workflows.WorkflowView):
workflow_class = AddVip
template_name = "project/loadbalancers/addvip.html"
def get_context_data(self, **kwargs):
context = super(AddVipView, self).get_context_data(**kwargs)
return context
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.quantum.subnet_get(
self.request, pool.subnet_id).cidr
except:
initial['subnet'] = ''
msg = _('Unable to retrieve pool subnet.')
exceptions.handle(self.request, msg)
return initial
class AddMemberView(workflows.WorkflowView):
workflow_class = AddMember
template_name = "project/loadbalancers/addmember.html"
def get_initial(self):
initial = super(AddMemberView, self).get_initial()
return initial
class AddMonitorView(workflows.WorkflowView):
workflow_class = AddMonitor
template_name = "project/loadbalancers/addmonitor.html"
def get_initial(self):
initial = super(AddMonitorView, self).get_initial()
return initial
class PoolDetailsView(tabs.TabView):
tab_group_class = (PoolDetailsTabs)
template_name = 'project/loadbalancers/details_tabs.html'
class VipDetailsView(tabs.TabView):
tab_group_class = (VipDetailsTabs)
template_name = 'project/loadbalancers/details_tabs.html'
class MemberDetailsView(tabs.TabView):
tab_group_class = (MemberDetailsTabs)
template_name = 'project/loadbalancers/details_tabs.html'
class MonitorDetailsView(tabs.TabView):
tab_group_class = (MonitorDetailsTabs)
template_name = 'project/loadbalancers/details_tabs.html'

View File

@ -0,0 +1,448 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# 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
import re
from django.utils.translation import ugettext as _
from horizon import exceptions
from horizon import forms
from horizon.utils import fields
from horizon import workflows
from openstack_dashboard import api
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"))
subnet_id = forms.ChoiceField(label=_("Subnet"))
protocol = forms.ChoiceField(label=_("Protocol"))
lb_method = forms.ChoiceField(label=_("Load Balancing Method"))
admin_state_up = forms.BooleanField(label=_("Admin State"),
initial=True, required=False)
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.quantum.network_list_for_tenant(request, tenant_id)
except:
exceptions.handle(request,
_('Unable to retrieve networks list.'))
for n in networks:
for s in n['subnets']:
subnet_id_choices.append((s.id, s.cidr))
self.fields['subnet_id'].choices = subnet_id_choices
protocol_choices = [('', _("Select a Protocol"))]
protocol_choices.append(('HTTP', 'HTTP'))
protocol_choices.append(('HTTPS', 'HTTPS'))
self.fields['protocol'].choices = protocol_choices
lb_method_choices = [('', _("Select a Protocol"))]
lb_method_choices.append(('ROUND_ROBIN', 'ROUND_ROBIN'))
lb_method_choices.append(('LEAST_CONNECTIONS', 'LEAST_CONNECTIONS'))
lb_method_choices.append(('SOURCE_IP', 'SOURCE_IP'))
self.fields['lb_method'].choices = lb_method_choices
class Meta:
name = _("PoolDetails")
permissions = ('openstack.services.network',)
help_text = _("Create Pool for current tenant.\n\n"
"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 defaul.t")
class AddPoolStep(workflows.Step):
action_class = AddPoolAction
contributes = ("name", "description", "subnet_id",
"protocol", "lb_method", "admin_state_up")
def contribute(self, data, context):
context = super(AddPoolStep, self).contribute(data, context)
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:
pool = api.lbaas.pool_create(request, **context)
context['name']
return True
except:
exceptions.handle(request,
self.failure_message)
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"))
floatip_address = forms.ChoiceField(
label=_("Vip Address from Floating IPs"),
widget=forms.Select(attrs={'disabled': 'disabled'}),
required=False)
other_address = fields.IPField(required=False,
initial="",
version=fields.IPv4,
mask=False)
protocol_port = forms.CharField(max_length=80, label=_("Protocol Port"))
protocol = forms.ChoiceField(label=_("Protocol"))
session_persistence = forms.ChoiceField(
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.CharField(
max_length=80, label=_("Connection Limit"))
admin_state_up = forms.BooleanField(
label=_("Admin State"), initial=True, required=False)
def __init__(self, request, *args, **kwargs):
super(AddVipAction, self).__init__(request, *args, **kwargs)
self.fields['other_address'].label = _("Specify a free IP address"
" from %s" %
args[0]['subnet'])
protocol_choices = [('', _("Select a Protocol"))]
protocol_choices.append(('HTTP', 'HTTP'))
protocol_choices.append(('HTTPS', 'HTTPS'))
self.fields['protocol'].choices = protocol_choices
session_persistence_choices = [('', _("Set Session Persistence"))]
for mode in ('SOURCE_IP', 'HTTP_COOKIE', 'APP_COOKIE'):
session_persistence_choices.append((mode, mode))
self.fields[
'session_persistence'].choices = session_persistence_choices
floatip_address_choices = [('', _("Currently Not Supported"))]
self.fields['floatip_address'].choices = floatip_address_choices
class Meta:
name = _("AddVip")
permissions = ('openstack.services.network',)
help_text = _("Create a vip (virtual IP) for this pool. "
"Assign a name and description for the vip. "
"Specify an IP address and port for the vip. "
"Choose the protocol and session persistence "
"method for the vip."
"Specify the max connections allowed. "
"Admin State is UP (checked) by default.")
class AddVipStep(workflows.Step):
action_class = AddVipAction
depends_on = ("pool_id", "subnet")
contributes = ("name", "description", "floatip_address",
"other_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)
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['other_address'] == '':
context['address'] = context['floatip_address']
else:
if not context['floatip_address'] == '':
self.failure_message = _('Only one address can be specified.'
'Unable to add Vip %s.')
return False
else:
context['address'] = context['other_address']
try:
pool = api.lbaas.pool_get(request, context['pool_id'])
context['subnet_id'] = pool['subnet_id']
except:
context['subnet_id'] = None
exceptions.handle(request,
_('Unable to retrieve pool.'))
return False
if context['session_persistence']:
stype = context['session_persistence']
if stype == 'APP_COOKIE':
if context['cookie_name'] == "":
self.failure_message = _('Cookie name must be specified '
'with APP_COOKIE persistence.')
return False
else:
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:
exceptions.handle(request,
self.failure_message)
return False
class AddMemberAction(workflows.Action):
pool_id = forms.ChoiceField(label=_("Pool"))
members = forms.MultipleChoiceField(
label=_("Member(s)"),
required=True,
initial=["default"],
widget=forms.CheckboxSelectMultiple(),
help_text=_("Select members for this pool "))
weight = forms.CharField(max_length=80, label=_("Weight"))
protocol_port = forms.CharField(max_length=80, label=_("Protocol Port"))
admin_state_up = forms.BooleanField(label=_("Admin State"),
initial=True, required=False)
def __init__(self, request, *args, **kwargs):
super(AddMemberAction, self).__init__(request, *args, **kwargs)
pool_id_choices = [('', _("Select a Pool"))]
try:
pools = api.lbaas.pools_get(request)
except:
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 = api.nova.server_list(request)
except:
servers = []
exceptions.handle(request,
_('Unable to retrieve instances list.'))
if len(servers) == 0:
self.fields['members'].label = _("No servers available. "
"Click Add to cancel.")
self.fields['members'].required = False
self.fields['members'].help_text = _("Select members "
"for this pool ")
self.fields['pool_id'].required = False
self.fields['weight'].required = False
self.fields['protocol_port'].required = False
return
for m in servers:
members_choices.append((m.id, m.name))
self.fields['members'].choices = sorted(
members_choices,
key=lambda member: member[1])
class Meta:
name = _("MemberDetails")
permissions = ('openstack.services.network',)
help_text = _("Add member to selected pool.\n\n"
"Choose one or more listed instances to be "
"added to the pool as member(s). "
"Assign a numeric weight for this member "
"Specify the port number the member(s) "
"operate on; e.g., 80.")
class AddMemberStep(workflows.Step):
action_class = AddMemberAction
contributes = ("pool_id", "members", "protocol_port", "weight",
"admin_state_up")
def contribute(self, data, context):
context = super(AddMemberStep, self).contribute(data, context)
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 format_status_message(self, message):
member_id = self.context.get('member_id')
return message % member_id
def handle(self, request, context):
if context['members'] == []:
self.failure_message = _('No instances available.%s')
context['member_id'] = ''
return False
for m in context['members']:
params = {'device_id': m}
try:
plist = api.quantum.port_list(request, **params)
except:
plist = []
exceptions.handle(request,
_('Unable to retrieve ports list.'))
return False
if plist:
context['address'] = plist[0].fixed_ips[0]['ip_address']
try:
context['member_id'] = api.lbaas.member_create(
request, **context).id
except:
exceptions.handle(request,
self.failure_message)
return False
return True
class AddMonitorAction(workflows.Action):
pool_id = forms.ChoiceField(label=_("Pool"))
type = forms.ChoiceField(label=_("Type"))
delay = forms.CharField(max_length=80, label=_("Delay"))
timeout = forms.CharField(max_length=80, label=_("Timeout"))
max_retries = forms.CharField(max_length=80,
label=_("Max Retries (1~10)"))
http_method = forms.ChoiceField(
initial="GET", required=False, label=_("HTTP Method"))
url_path = forms.CharField(
initial="/", required=False, max_length=80, label=_("URL"))
expected_codes = forms.CharField(
initial="200", required=False, max_length=80,
label=_("Expected HTTP Status Codes"))
admin_state_up = forms.BooleanField(label=_("Admin State"),
initial=True, required=False)
def __init__(self, request, *args, **kwargs):
super(AddMonitorAction, self).__init__(request, *args, **kwargs)
pool_id_choices = [('', _("Select a Pool"))]
try:
pools = api.lbaas.pools_get(request)
except:
exceptions.handle(request,
_('Unable to retrieve pools list.'))
for p in pools:
pool_id_choices.append((p.id, p.name))
self.fields['pool_id'].choices = pool_id_choices
type_choices = [('', _("Select Type"))]
type_choices.append(('PING', 'PING'))
type_choices.append(('TCP', 'TCP'))
type_choices.append(('HTTP', 'HTTP'))
type_choices.append(('HTTPS', 'HTTPS'))
self.fields['type'].choices = type_choices
http_method_choices = [('', _("Select HTTP Method"))]
http_method_choices.append(('GET', 'GET'))
self.fields['http_method'].choices = http_method_choices
class Meta:
name = _("MonitorDetails")
permissions = ('openstack.services.network',)
help_text = _("Create a monitor for a pool.\n\n"
"Select target pool and 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 = ("pool_id", "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)
if data:
return context
class AddMonitor(workflows.Workflow):
slug = "addmonitor"
name = _("Add Monitor")
finalize_button_name = _("Add")
success_message = _('Added Monitor "%s".')
failure_message = _('Unable to add Monitor "%s".')
success_url = "horizon:project:loadbalancers:index"
default_steps = (AddMonitorStep,)
def format_status_message(self, message):
monitor_id = self.context.get('monitor_id')
return message % monitor_id
def handle(self, request, context):
try:
context['monitor_id'] = api.lbaas.pool_health_monitor_create(
request, **context).get('id')
return True
except:
exceptions.handle(request,
self.failure_message)
return False

View File

@ -104,6 +104,13 @@ OPENSTACK_HYPERVISOR_FEATURES = {
'can_encrypt_volumes': False
}
# The OPENSTACK_QUANTUM_NETWORK settings can be used to enable optional
# services provided by quantum. Currently only the load balancer service
# is available.
OPENSTACK_QUANTUM_NETWORK = {
'enable_lb': True
}
# OPENSTACK_ENDPOINT_TYPE specifies the endpoint type to use for the endpoints
# in the Keystone service catalog. Use this setting when Horizon is running
# external to the OpenStack environment. The default is 'internalURL'.

View File

@ -0,0 +1,363 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# 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 mox import IsA
from django import http
from openstack_dashboard import api
from openstack_dashboard.test import helpers as test
from openstack_dashboard.api.lbaas import Vip, Pool, Member, PoolMonitor
from quantumclient.v2_0.client import Client as quantumclient
class LbaasApiTests(test.APITestCase):
@test.create_stubs({quantumclient: ('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()}
quantumclient.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({quantumclient: ('list_vips',)})
def test_vips_get(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
}, ]}
quantumclient.list_vips().AndReturn(vips)
self.mox.ReplayAll()
ret_val = api.lbaas.vips_get(self.request)
for v in ret_val:
self.assertIsInstance(v, api.lbaas.Vip)
@test.create_stubs({quantumclient: ('show_vip',)})
def test_vip_get(self):
vip = {'vip': {'id': 'abcdef-c3eb-4fee-9763-12de3338041e',
'address': '10.0.0.100',
'name': 'vip1name',
'description': 'vip1description',
'subnet_id': '12381d38-c3eb-4fee-9763-12de3338041e',
'protocol_port': '80',
'protocol': 'HTTP',
'pool_id': '8913dde8-4915-4b90-8d3e-b95eeedb0d49',
'connection_limit': '10',
'admin_state_up': True
}}
quantumclient.show_vip(vip['vip']['id']).AndReturn(vip)
self.mox.ReplayAll()
ret_val = api.lbaas.vip_get(self.request, vip['vip']['id'])
self.assertIsInstance(ret_val, api.lbaas.Vip)
@test.create_stubs({quantumclient: ('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
}}
quantumclient.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({quantumclient: ('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
}
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
}}
quantumclient.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({quantumclient: ('list_pools',)})
def test_pools_get(self):
pools = {'pools': [{
'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}, ]}
quantumclient.list_pools().AndReturn(pools)
self.mox.ReplayAll()
ret_val = api.lbaas.pools_get(self.request)
for v in ret_val:
self.assertIsInstance(v, api.lbaas.Pool)
@test.create_stubs({quantumclient: ('show_pool',)})
def test_pool_get(self):
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
}}
quantumclient.show_pool(pool['pool']['id']).AndReturn(pool)
self.mox.ReplayAll()
ret_val = api.lbaas.pool_get(self.request, pool['pool']['id'])
self.assertIsInstance(ret_val, api.lbaas.Pool)
@test.create_stubs({quantumclient: ('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
}}
quantumclient.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({quantumclient: ('create_health_monitor',
'associate_health_monitor')})
def test_pool_health_monitor_create(self):
form_data = {'type': 'PING',
'delay': '10',
'timeout': '10',
'max_retries': '10',
'http_method': 'GET',
'url_path': '/monitor',
'expected_codes': '200',
'admin_state_up': True
}
form_data_with_pool_id = {
'pool_id': 'abcdef-c3eb-4fee-9763-12de3338041e',
'type': 'PING',
'delay': '10',
'timeout': '10',
'max_retries': '10',
'http_method': 'GET',
'url_path': '/monitor',
'expected_codes': '200',
'admin_state_up': True}
monitor = {'health_monitor': {
'id': 'abcdef-c3eb-4fee-9763-12de3338041e',
'type': 'PING',
'delay': '10',
'timeout': '10',
'max_retries': '10',
'http_method': 'GET',
'url_path': '/monitor',
'expected_codes': '200',
'admin_state_up': True}}
monitor_id = {'health_monitor': {
'id': 'abcdef-c3eb-4fee-9763-12de3338041e'}}
quantumclient.create_health_monitor({
'health_monitor': form_data}).AndReturn(monitor)
quantumclient.associate_health_monitor(
form_data_with_pool_id['pool_id'], monitor_id)
self.mox.ReplayAll()
ret_val = api.lbaas.pool_health_monitor_create(
self.request, **form_data_with_pool_id)
self.assertIsInstance(ret_val, api.lbaas.PoolMonitor)
@test.create_stubs({quantumclient: ('list_health_monitors',)})
def test_pool_health_monitors_get(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}, ]}
quantumclient.list_health_monitors().AndReturn(monitors)
self.mox.ReplayAll()
ret_val = api.lbaas.pool_health_monitors_get(self.request)
for v in ret_val:
self.assertIsInstance(v, api.lbaas.PoolMonitor)
@test.create_stubs({quantumclient: ('show_health_monitor',)})
def test_pool_health_monitor_get(self):
monitor = {'health_monitor':
{'id': 'abcdef-c3eb-4fee-9763-12de3338041e',
'type': 'PING',
'delay': '10',
'timeout': '10',
'max_retries': '10',
'http_method': 'GET',
'url_path': '/monitor',
'expected_codes': '200',
'admin_state_up': True}}
quantumclient.show_health_monitor(
monitor['health_monitor']['id']).AndReturn(monitor)
self.mox.ReplayAll()
ret_val = api.lbaas.pool_health_monitor_get(
self.request, monitor['health_monitor']['id'])
self.assertIsInstance(ret_val, api.lbaas.PoolMonitor)
@test.create_stubs({quantumclient: ('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}}
quantumclient.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({quantumclient: ('list_members',)})
def test_members_get(self):
members = {'members': [
{'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
}, ]}
quantumclient.list_members().AndReturn(members)
self.mox.ReplayAll()
ret_val = api.lbaas.members_get(self.request)
for v in ret_val:
self.assertIsInstance(v, api.lbaas.Member)
@test.create_stubs({quantumclient: ('show_member',)})
def test_member_get(self):
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}}
quantumclient.show_member(member['member']['id']).AndReturn(member)
self.mox.ReplayAll()
ret_val = api.lbaas.member_get(self.request, member['member']['id'])
self.assertIsInstance(ret_val, api.lbaas.Member)
@test.create_stubs({quantumclient: ('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
}}
quantumclient.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)

View File

@ -74,6 +74,10 @@ OPENSTACK_KEYSTONE_BACKEND = {
'can_edit_project': True
}
OPENSTACK_QUANTUM_NETWORK = {
'enable_lb': True
}
OPENSTACK_HYPERVISOR_FEATURES = {
'can_set_mount_point': True,

View File

@ -16,7 +16,8 @@ import copy
from openstack_dashboard.api.quantum import (Network, Subnet, Port,
Router, FloatingIp)
from openstack_dashboard.api.lbaas import (Pool, Vip, Member,
PoolMonitor)
from .utils import TestDataContainer
@ -27,6 +28,10 @@ def data(TEST):
TEST.ports = TestDataContainer()
TEST.routers = TestDataContainer()
TEST.q_floating_ips = TestDataContainer()
TEST.pools = TestDataContainer()
TEST.vips = TestDataContainer()
TEST.members = TestDataContainer()
TEST.monitors = TestDataContainer()
# data return by quantumclient
TEST.api_networks = TestDataContainer()
@ -34,6 +39,10 @@ def data(TEST):
TEST.api_ports = TestDataContainer()
TEST.api_routers = TestDataContainer()
TEST.api_q_floating_ips = TestDataContainer()
TEST.api_pools = TestDataContainer()
TEST.api_vips = TestDataContainer()
TEST.api_members = TestDataContainer()
TEST.api_monitors = TestDataContainer()
#------------------------------------------------------------
# 1st network
@ -231,3 +240,112 @@ def data(TEST):
'router_id': router_dict['id']}
TEST.api_q_floating_ips.add(fip_dict)
TEST.q_floating_ips.add(FloatingIp(fip_dict))
#------------------------------------------------------------
# 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',
'admin_state_up': True}
TEST.api_pools.add(pool_dict)
TEST.pools.add(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,
'protocol_port': '80',
'protocol': pool_dict['protocol'],
'pool_id': pool_dict['id'],
'session_persistence': {'type': 'SOURCE_IP',
'cookie_name': 'jssessionid'},
'connection_limit': '10',
'admin_state_up': True}
TEST.api_vips.add(vip_dict)
TEST.vips.add(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',
'admin_state_up': True}
TEST.api_members.add(member_dict)
TEST.members.add(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',
'admin_state_up': True}
TEST.api_members.add(member_dict)
TEST.members.add(Member(member_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',
'admin_state_up': True}
TEST.api_pools.add(pool_dict)
TEST.pools.add(Pool(pool_dict))
# 1st 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,
'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(Vip(vip_dict))
# 1st monitor
monitor_dict = {'id': 'd4a0500f-db2b-4cc4-afcf-ec026febff96',
'type': 'PING',
'delay': '10',
'timeout': '10',
'max_retries': '10',
'http_method': 'GET',
'url_path': '/',
'expected_codes': '200',
'admin_state_up': True}
TEST.api_monitors.add(monitor_dict)
TEST.monitors.add(PoolMonitor(monitor_dict))
# 2nd monitor
monitor_dict = {'id': 'd4a0500f-db2b-4cc4-afcf-ec026febff97',
'type': 'PING',
'delay': '10',
'timeout': '10',
'max_retries': '10',
'http_method': 'GET',
'url_path': '/',
'expected_codes': '200',
'admin_state_up': True}
TEST.api_monitors.add(monitor_dict)
TEST.monitors.add(PoolMonitor(monitor_dict))