diff --git a/openstack_dashboard/api/lbaas.py b/openstack_dashboard/api/lbaas.py index c8c86a6316..2f86d5465a 100644 --- a/openstack_dashboard/api/lbaas.py +++ b/openstack_dashboard/api/lbaas.py @@ -45,7 +45,8 @@ class Pool(QuantumAPIDictWrapper): pFormatted = {'id': self.id, 'name': self.name, 'description': self.description, - 'protocol': self.protocol} + 'protocol': self.protocol, + 'health_monitors': self.health_monitors} try: pFormatted['subnet_id'] = self.subnet_id pFormatted['subnet_name'] = subnet_get( @@ -204,7 +205,7 @@ def pool_stats(request, pool_id, **kwargs): def pool_health_monitor_create(request, **kwargs): - """Create a health monitor and associate with pool + """Create a health monitor :param request: request context :param type: type of monitor @@ -229,9 +230,7 @@ def pool_health_monitor_create(request, **kwargs): body['health_monitor']['expected_codes'] = kwargs['expected_codes'] 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) @@ -294,3 +293,29 @@ def member_update(request, member_id, **kwargs): def member_delete(request, mem_id): quantumclient(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'], }} + + quantumclient(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 + """ + + quantumclient(request).disassociate_health_monitor( + kwargs['pool_id'], kwargs['monitor_id']) diff --git a/openstack_dashboard/dashboards/project/loadbalancers/tables.py b/openstack_dashboard/dashboards/project/loadbalancers/tables.py index c221ff61f8..caba79b3d4 100644 --- a/openstack_dashboard/dashboards/project/loadbalancers/tables.py +++ b/openstack_dashboard/dashboards/project/loadbalancers/tables.py @@ -19,8 +19,11 @@ from django.core.urlresolvers import reverse from django.utils import http from django.utils.translation import ugettext_lazy as _ +from horizon import exceptions from horizon import tables +from openstack_dashboard import api + import logging @@ -155,6 +158,36 @@ def get_vip_link(pool): args=(http.urlquote(pool.vip_id),)) +class AddPMAssociationLink(tables.LinkAction): + name = "addassociation" + verbose_name = _("Add Health Monitor") + url = "horizon:project:loadbalancers:addassociation" + classes = ("btn-add",) + + def allowed(self, request, datum=None): + try: + monitors = api.lbaas.pool_health_monitors_get(request) + for m in monitors: + if m.id not in datum['health_monitors']: + return True + except: + exceptions.handle(request, + _('Failed to retrieve health monitors.')) + return False + + +class DeletePMAssociationLink(tables.LinkAction): + name = "deleteassociation" + verbose_name = _("Delete Health Monitor") + url = "horizon:project:loadbalancers:deleteassociation" + classes = ("btn-delete", "btn-danger") + + def allowed(self, request, datum=None): + if datum and not datum['health_monitors']: + return False + return True + + class PoolsTable(tables.DataTable): name = tables.Column("name", verbose_name=_("Name"), @@ -170,7 +203,8 @@ class PoolsTable(tables.DataTable): verbose_name = _("Pools") table_actions = (AddPoolLink, DeletePoolLink) row_actions = (UpdatePoolLink, AddVipLink, UpdateVipLink, - DeleteVipLink, DeletePoolLink) + DeleteVipLink, AddPMAssociationLink, + DeletePMAssociationLink, DeletePoolLink) def get_pool_link(member): diff --git a/openstack_dashboard/dashboards/project/loadbalancers/tests.py b/openstack_dashboard/dashboards/project/loadbalancers/tests.py index 28ecc4c297..a3235530c8 100644 --- a/openstack_dashboard/dashboards/project/loadbalancers/tests.py +++ b/openstack_dashboard/dashboards/project/loadbalancers/tests.py @@ -19,8 +19,10 @@ from openstack_dashboard.test import helpers as test from .workflows import AddMember from .workflows import AddMonitor +from .workflows import AddPMAssociation from .workflows import AddPool from .workflows import AddVip +from .workflows import DeletePMAssociation class LoadBalancerTests(test.TestCase): @@ -49,6 +51,9 @@ class LoadBalancerTests(test.TestCase): UPDATEMEMBER_PATH = 'horizon:%s:loadbalancers:updatemember' % DASHBOARD UPDATEMONITOR_PATH = 'horizon:%s:loadbalancers:updatemonitor' % DASHBOARD + ADDASSOC_PATH = 'horizon:%s:loadbalancers:addassociation' % DASHBOARD + DELETEASSOC_PATH = 'horizon:%s:loadbalancers:deleteassociation' % DASHBOARD + def set_up_expect(self): # retrieve pools subnet = self.subnets.first() @@ -346,17 +351,13 @@ class LoadBalancerTests(test.TestCase): expected_objs = ['', ] self.assertQuerysetEqual(workflow.steps, expected_objs) - @test.create_stubs({api.lbaas: ('pools_get', - 'pool_health_monitor_create')}) + @test.create_stubs({api.lbaas: ('pool_health_monitor_create', )}) def test_add_monitor_post(self): monitor = self.monitors.first() - api.lbaas.pools_get(IsA(http.HttpRequest)).AndReturn(self.pools.list()) - api.lbaas.pool_health_monitor_create( IsA(http.HttpRequest), type=monitor.type, - pool_id=monitor.pool_id, delay=monitor.delay, timeout=monitor.timeout, max_retries=monitor.max_retries, @@ -369,7 +370,6 @@ class LoadBalancerTests(test.TestCase): self.mox.ReplayAll() form_data = {'type': monitor.type, - 'pool_id': monitor.pool_id, 'delay': monitor.delay, 'timeout': monitor.timeout, 'max_retries': monitor.max_retries, @@ -383,16 +383,10 @@ class LoadBalancerTests(test.TestCase): self.assertNoFormErrors(res) self.assertRedirectsNoFollow(res, str(self.INDEX_URL)) - @test.create_stubs({api.lbaas: ('pools_get',)}) def test_add_monitor_post_with_error(self): monitor = self.monitors.first() - api.lbaas.pools_get(IsA(http.HttpRequest)).AndReturn(self.pools.list()) - - self.mox.ReplayAll() - form_data = {'type': monitor.type, - 'pool_id': monitor.pool_id, 'delay': 0, 'timeout': 0, 'max_retries': 11, @@ -405,16 +399,10 @@ class LoadBalancerTests(test.TestCase): self.assertFormErrors(res, 3) - @test.create_stubs({api.lbaas: ('pools_get',)}) def test_add_monitor_post_with_httpmethod_error(self): monitor = self.monitors.first() - api.lbaas.pools_get(IsA(http.HttpRequest)).AndReturn(self.pools.list()) - - self.mox.ReplayAll() - form_data = {'type': 'http', - 'pool_id': monitor.pool_id, 'delay': monitor.delay, 'timeout': monitor.timeout, 'max_retries': monitor.max_retries, @@ -427,12 +415,7 @@ class LoadBalancerTests(test.TestCase): self.assertFormErrors(res, 3) - @test.create_stubs({api.lbaas: ('pools_get',)}) def test_add_monitor_get(self): - api.lbaas.pools_get(IsA(http.HttpRequest)).AndReturn(self.pools.list()) - - self.mox.ReplayAll() - res = self.client.get(reverse(self.ADDMONITOR_PATH)) workflow = res.context['workflow'] @@ -708,3 +691,101 @@ class LoadBalancerTests(test.TestCase): self.assertTemplateUsed( res, 'project/loadbalancers/updatemonitor.html') + + @test.create_stubs({api.lbaas: ('pool_get', 'pool_health_monitors_get', + 'pool_monitor_association_create')}) + def test_add_pool_monitor_association_post(self): + pool = self.pools.first() + monitors = self.monitors.list() + monitor = self.monitors.list()[1] + + api.lbaas.pool_get(IsA(http.HttpRequest), pool.id).AndReturn(pool) + api.lbaas.pool_health_monitors_get( + IsA(http.HttpRequest)).AndReturn(monitors) + + api.lbaas.pool_monitor_association_create( + IsA(http.HttpRequest), + monitor_id=monitor.id, + pool_id=pool.id, + pool_monitors=pool.health_monitors, + pool_name=pool.name).AndReturn(None) + + self.mox.ReplayAll() + + form_data = {'monitor_id': monitor.id, + 'pool_id': pool.id, + 'pool_monitors': pool.health_monitors, + 'pool_name': pool.name} + + res = self.client.post( + reverse(self.ADDASSOC_PATH, args=(pool.id,)), form_data) + + self.assertNoFormErrors(res) + self.assertRedirectsNoFollow(res, str(self.INDEX_URL)) + + @test.create_stubs({api.lbaas: ('pool_get', 'pool_health_monitors_get')}) + def test_add_pool_monitor_association_get(self): + pool = self.pools.first() + monitors = self.monitors.list() + + api.lbaas.pool_get(IsA(http.HttpRequest), pool.id).AndReturn(pool) + api.lbaas.pool_health_monitors_get( + IsA(http.HttpRequest)).AndReturn(monitors) + + self.mox.ReplayAll() + + res = self.client.get(reverse(self.ADDASSOC_PATH, args=(pool.id,))) + + workflow = res.context['workflow'] + self.assertTemplateUsed(res, WorkflowView.template_name) + self.assertEqual(workflow.name, AddPMAssociation.name) + + expected_objs = ['', ] + self.assertQuerysetEqual(workflow.steps, expected_objs) + + @test.create_stubs({api.lbaas: ('pool_get', + 'pool_monitor_association_delete')}) + def test_delete_pool_monitor_association_post(self): + pool = self.pools.first() + monitor = self.monitors.first() + + api.lbaas.pool_get(IsA(http.HttpRequest), pool.id).AndReturn(pool) + + api.lbaas.pool_monitor_association_delete( + IsA(http.HttpRequest), + monitor_id=monitor.id, + pool_id=pool.id, + pool_monitors=pool.health_monitors, + pool_name=pool.name).AndReturn(None) + + self.mox.ReplayAll() + + form_data = {'monitor_id': monitor.id, + 'pool_id': pool.id, + 'pool_monitors': pool.health_monitors, + 'pool_name': pool.name} + + res = self.client.post( + reverse(self.DELETEASSOC_PATH, args=(pool.id,)), form_data) + + self.assertNoFormErrors(res) + self.assertRedirectsNoFollow(res, str(self.INDEX_URL)) + + @test.create_stubs({api.lbaas: ('pool_get',)}) + def test_delete_pool_monitor_association_get(self): + pool = self.pools.first() + + api.lbaas.pool_get(IsA(http.HttpRequest), pool.id).AndReturn(pool) + + self.mox.ReplayAll() + + res = self.client.get( + reverse(self.DELETEASSOC_PATH, args=(pool.id,))) + + workflow = res.context['workflow'] + self.assertTemplateUsed(res, WorkflowView.template_name) + self.assertEqual(workflow.name, DeletePMAssociation.name) + + expected_objs = [ + '', ] + self.assertQuerysetEqual(workflow.steps, expected_objs) diff --git a/openstack_dashboard/dashboards/project/loadbalancers/urls.py b/openstack_dashboard/dashboards/project/loadbalancers/urls.py index f1ef7e593a..d5661edfb2 100644 --- a/openstack_dashboard/dashboards/project/loadbalancers/urls.py +++ b/openstack_dashboard/dashboards/project/loadbalancers/urls.py @@ -25,6 +25,10 @@ from openstack_dashboard.dashboards.project.loadbalancers.views import \ AddPoolView from openstack_dashboard.dashboards.project.loadbalancers.views import \ AddVipView +from openstack_dashboard.dashboards.project.loadbalancers.views import \ + AddPMAssociationView +from openstack_dashboard.dashboards.project.loadbalancers.views import \ + DeletePMAssociationView from openstack_dashboard.dashboards.project.loadbalancers.views import \ IndexView from openstack_dashboard.dashboards.project.loadbalancers.views import \ @@ -59,6 +63,10 @@ urlpatterns = patterns( url(r'^addmonitor$', AddMonitorView.as_view(), name='addmonitor'), url(r'^updatemonitor/(?P[^/]+)/$', UpdateMonitorView.as_view(), name='updatemonitor'), + url(r'^association/add/(?P[^/]+)/$', + AddPMAssociationView.as_view(), name='addassociation'), + url(r'^association/delete/(?P[^/]+)/$', + DeletePMAssociationView.as_view(), name='deleteassociation'), url(r'^pool/(?P[^/]+)/$', PoolDetailsView.as_view(), name='pooldetails'), url(r'^vip/(?P[^/]+)/$', diff --git a/openstack_dashboard/dashboards/project/loadbalancers/views.py b/openstack_dashboard/dashboards/project/loadbalancers/views.py index 28a2ab5440..d044e74424 100644 --- a/openstack_dashboard/dashboards/project/loadbalancers/views.py +++ b/openstack_dashboard/dashboards/project/loadbalancers/views.py @@ -47,10 +47,14 @@ from openstack_dashboard.dashboards.project.loadbalancers.workflows import \ AddMember from openstack_dashboard.dashboards.project.loadbalancers.workflows import \ AddMonitor +from openstack_dashboard.dashboards.project.loadbalancers.workflows import \ + AddPMAssociation from openstack_dashboard.dashboards.project.loadbalancers.workflows import \ AddPool from openstack_dashboard.dashboards.project.loadbalancers.workflows import \ AddVip +from openstack_dashboard.dashboards.project.loadbalancers.workflows import \ + DeletePMAssociation import re @@ -300,3 +304,35 @@ class UpdateMonitorView(forms.ModalFormView): 'timeout': monitor['timeout'], 'max_retries': monitor['max_retries'], 'admin_state_up': monitor['admin_state_up']} + + +class AddPMAssociationView(workflows.WorkflowView): + workflow_class = 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: + msg = _('Unable to retrieve pool.') + exceptions.handle(self.request, msg) + return initial + + +class DeletePMAssociationView(workflows.WorkflowView): + workflow_class = 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: + msg = _('Unable to retrieve pool.') + exceptions.handle(self.request, msg) + return initial diff --git a/openstack_dashboard/dashboards/project/loadbalancers/workflows.py b/openstack_dashboard/dashboards/project/loadbalancers/workflows.py index 409272f936..930590842d 100644 --- a/openstack_dashboard/dashboards/project/loadbalancers/workflows.py +++ b/openstack_dashboard/dashboards/project/loadbalancers/workflows.py @@ -358,7 +358,6 @@ class AddMember(workflows.Workflow): class AddMonitorAction(workflows.Action): - pool_id = forms.ChoiceField(label=_("Pool")) type = forms.ChoiceField( label=_("Type"), choices=[('ping', _('PING')), @@ -428,16 +427,6 @@ class AddMonitorAction(workflows.Action): 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) - for p in pools: - pool_id_choices.append((p.id, p.name)) - except: - exceptions.handle(request, - _('Unable to retrieve pools list.')) - self.fields['pool_id'].choices = pool_id_choices - def clean(self): cleaned_data = super(AddMonitorAction, self).clean() type_opt = cleaned_data.get('type') @@ -463,8 +452,8 @@ class AddMonitorAction(workflows.Action): class Meta: name = _("Add New Monitor") permissions = ('openstack.services.network',) - help_text = _("Create a monitor for a pool.\n\n" - "Select target pool and type of monitoring. " + 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 " @@ -473,7 +462,7 @@ class AddMonitorAction(workflows.Action): class AddMonitorStep(workflows.Step): action_class = AddMonitorAction - contributes = ("pool_id", "type", "delay", "timeout", "max_retries", + contributes = ("type", "delay", "timeout", "max_retries", "http_method", "url_path", "expected_codes", "admin_state_up") @@ -500,3 +489,121 @@ class AddMonitor(workflows.Workflow): except: exceptions.handle(request, _("Unable to add monitor.")) return False + + +class AddPMAssociationAction(workflows.Action): + monitor_id = forms.ChoiceField(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: + monitors = api.lbaas.pool_health_monitors_get(request) + for m in monitors: + if m.id not in context['pool_monitors']: + monitor_id_choices.append((m.id, m.id)) + except: + exceptions.handle(request, + _('Unable to retrieve monitors list.')) + self.fields['monitor_id'].choices = monitor_id_choices + + return monitor_id_choices + + class Meta: + 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 = _("Add Association") + finalize_button_name = _("Add") + success_message = _('Added association.') + failure_message = _('Unable to add association.') + 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: + exceptions.handle(request, _("Unable to add association.")) + return False + + +class DeletePMAssociationAction(workflows.Action): + monitor_id = forms.ChoiceField(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: + for m_id in context['pool_monitors']: + monitor_id_choices.append((m_id, m_id)) + except: + exceptions.handle(request, + _('Unable to retrieve monitors list.')) + self.fields['monitor_id'].choices = monitor_id_choices + + return monitor_id_choices + + class Meta: + 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 = _("Delete Association") + finalize_button_name = _("Delete") + success_message = _('Deleted association.') + failure_message = _('Unable to delete association.') + 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: + exceptions.handle(request, _("Unable to delete association.")) + return False diff --git a/openstack_dashboard/test/api_tests/lbaas_tests.py b/openstack_dashboard/test/api_tests/lbaas_tests.py index cda6147daf..9fcb1b9967 100644 --- a/openstack_dashboard/test/api_tests/lbaas_tests.py +++ b/openstack_dashboard/test/api_tests/lbaas_tests.py @@ -196,8 +196,7 @@ class LbaasApiTests(test.APITestCase): pool['pool']['id'], **form_data) self.assertIsInstance(ret_val, api.lbaas.Pool) - @test.create_stubs({quantumclient: ('create_health_monitor', - 'associate_health_monitor')}) + @test.create_stubs({quantumclient: ('create_health_monitor',)}) def test_pool_health_monitor_create(self): form_data = {'type': 'PING', 'delay': '10', @@ -205,13 +204,6 @@ class LbaasApiTests(test.APITestCase): 'max_retries': '10', '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', - 'admin_state_up': True} monitor = {'health_monitor': { 'id': 'abcdef-c3eb-4fee-9763-12de3338041e', 'type': 'PING', @@ -219,16 +211,12 @@ class LbaasApiTests(test.APITestCase): 'timeout': '10', 'max_retries': '10', '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.request, **form_data) self.assertIsInstance(ret_val, api.lbaas.PoolMonitor) @test.create_stubs({quantumclient: ('list_health_monitors',)}) diff --git a/openstack_dashboard/test/test_data/quantum_data.py b/openstack_dashboard/test/test_data/quantum_data.py index 9602890bb1..d021c41e17 100644 --- a/openstack_dashboard/test/test_data/quantum_data.py +++ b/openstack_dashboard/test/test_data/quantum_data.py @@ -264,6 +264,7 @@ def data(TEST): 'subnet_id': TEST.subnets.first().id, 'protocol': 'HTTP', 'lb_method': 'ROUND_ROBIN', + 'health_monitors': ['d4a0500f-db2b-4cc4-afcf-ec026febff96'], 'admin_state_up': True} TEST.api_pools.add(pool_dict) TEST.pools.add(Pool(pool_dict)) @@ -337,6 +338,7 @@ def data(TEST): 'subnet_id': TEST.subnets.first().id, 'protocol': 'HTTPS', 'lb_method': 'ROUND_ROBIN', + 'health_monitors': ['d4a0500f-db2b-4cc4-afcf-ec026febff97'], 'admin_state_up': True} TEST.api_pools.add(pool_dict) TEST.pools.add(Pool(pool_dict)) @@ -344,7 +346,6 @@ def data(TEST): # 1st monitor monitor_dict = {'id': 'd4a0500f-db2b-4cc4-afcf-ec026febff96', 'type': 'ping', - 'pool_id': pool_dict['id'], 'delay': 10, 'timeout': 10, 'max_retries': 10, @@ -358,7 +359,6 @@ def data(TEST): # 2nd monitor monitor_dict = {'id': 'd4a0500f-db2b-4cc4-afcf-ec026febff97', 'type': 'ping', - 'pool_id': pool_dict['id'], 'delay': 10, 'timeout': 10, 'max_retries': 10,