# 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)