1# Copyright 2012,  Nachi Ueno,  NTT MCL,  Inc.
2#
3#    Licensed under the Apache License, Version 2.0 (the "License"); you may
4#    not use this file except in compliance with the License. You may obtain
5#    a copy of the License at
6#
7#         http://www.apache.org/licenses/LICENSE-2.0
8#
9#    Unless required by applicable law or agreed to in writing, software
10#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12#    License for the specific language governing permissions and limitations
13#    under the License.
14
15import logging
16
17from django.template import defaultfilters as filters
18from django.urls import reverse
19from django.utils.translation import pgettext_lazy
20from django.utils.translation import ugettext_lazy as _
21from django.utils.translation import ungettext_lazy
22from neutronclient.common import exceptions as neutron_exceptions
23
24from horizon import exceptions
25from horizon import tables
26from horizon.tables import actions
27
28from openstack_dashboard import api
29from openstack_dashboard import policy
30from openstack_dashboard.usage import quotas
31
32
33LOG = logging.getLogger(__name__)
34
35
36class DeleteRouter(policy.PolicyTargetMixin, tables.DeleteAction):
37    @staticmethod
38    def action_present(count):
39        return ungettext_lazy(
40            "Delete Router",
41            "Delete Routers",
42            count
43        )
44
45    @staticmethod
46    def action_past(count):
47        return ungettext_lazy(
48            "Deleted Router",
49            "Deleted Routers",
50            count
51        )
52
53    redirect_url = "horizon:project:routers:index"
54    policy_rules = (("network", "delete_router"),)
55
56    @actions.handle_exception_with_detail_message(
57        # normal_log_message
58        'Failed to delete router %(id)s: %(exc)s',
59        # target_exception
60        neutron_exceptions.NeutronClientException,
61        # target_log_message
62        'Unable to delete router %(id)s: %(exc)s',
63        # target_user_message
64        _('Unable to delete router %(name)s: %(exc)s'),
65        # logger_name
66        __name__)
67    def delete(self, request, obj_id):
68        # detach all interfaces before attempting to delete the router
69        search_opts = {'device_owner': 'network:router_interface',
70                       'device_id': obj_id}
71        ports = api.neutron.port_list(request, **search_opts)
72        for port in ports:
73            api.neutron.router_remove_interface(request, obj_id,
74                                                port_id=port.id)
75        api.neutron.router_delete(request, obj_id)
76
77
78class CreateRouter(tables.LinkAction):
79    name = "create"
80    verbose_name = _("Create Router")
81    url = "horizon:project:routers:create"
82    classes = ("ajax-modal",)
83    icon = "plus"
84    policy_rules = (("network", "create_router"),)
85
86    def allowed(self, request, datum=None):
87        usages = quotas.tenant_quota_usages(request, targets=('router', ))
88        # when Settings.OPENSTACK_NEUTRON_NETWORK['enable_quotas'] = False
89        # usages['router'] is empty
90        if usages.get('router', {}).get('available', 1) <= 0:
91            if "disabled" not in self.classes:
92                self.classes = list(self.classes) + ['disabled']
93                self.verbose_name = _("Create Router (Quota exceeded)")
94        else:
95            self.verbose_name = _("Create Router")
96            self.classes = [c for c in self.classes if c != "disabled"]
97
98        return True
99
100
101class EditRouter(policy.PolicyTargetMixin, tables.LinkAction):
102    name = "update"
103    verbose_name = _("Edit Router")
104    url = "horizon:project:routers:update"
105    classes = ("ajax-modal",)
106    icon = "pencil"
107    policy_rules = (("network", "update_router"),)
108
109
110class SetGateway(policy.PolicyTargetMixin, tables.LinkAction):
111    name = "setgateway"
112    verbose_name = _("Set Gateway")
113    url = "horizon:project:routers:setgateway"
114    classes = ("ajax-modal",)
115    icon = "camera"
116    policy_rules = (("network", "update_router"),)
117
118    def allowed(self, request, datum=None):
119        if datum.external_gateway_info:
120            return False
121        return True
122
123
124class ClearGateway(policy.PolicyTargetMixin, tables.BatchAction):
125    help_text = _("You may reset the gateway later by using the"
126                  " set gateway action, but the gateway IP may change.")
127
128    @staticmethod
129    def action_present(count):
130        return ungettext_lazy(
131            "Clear Gateway",
132            "Clear Gateways",
133            count
134        )
135
136    @staticmethod
137    def action_past(count):
138        return ungettext_lazy(
139            "Cleared Gateway",
140            "Cleared Gateways",
141            count
142        )
143
144    name = "clear"
145    classes = ('btn-cleargateway',)
146    redirect_url = "horizon:project:routers:index"
147    policy_rules = (("network", "update_router"),)
148    action_type = "danger"
149
150    @actions.handle_exception_with_detail_message(
151        # normal_log_message
152        'Unable to clear gateway for router %(id)s: %(exc)s',
153        # target_exception
154        neutron_exceptions.Conflict,
155        # target_log_message
156        'Unable to clear gateway for router %(id)s: %(exc)s',
157        # target_user_message
158        _('Unable to clear gateway for router %(name)s. '
159          'Most possible reason is because the gateway is required '
160          'by one or more floating IPs'),
161        # logger_name
162        __name__)
163    def action(self, request, obj_id):
164        api.neutron.router_remove_gateway(request, obj_id)
165
166    def get_success_url(self, request):
167        return reverse(self.redirect_url)
168
169    def allowed(self, request, datum=None):
170        if datum.external_gateway_info:
171            return True
172        return False
173
174
175class UpdateRow(tables.Row):
176    ajax = True
177
178    def get_data(self, request, router_id):
179        router = api.neutron.router_get(request, router_id)
180        return router
181
182
183def get_external_network(router):
184    if router.external_gateway_info:
185        return router.external_gateway_info['network']
186    return _("-")
187
188
189def get_availability_zones(router):
190    if 'availability_zones' in router and router.availability_zones:
191        return ', '.join(router.availability_zones)
192    return _("-")
193
194
195class RoutersFilterAction(tables.FilterAction):
196    name = 'filter_project_routers'
197    filter_type = 'server'
198    filter_choices = (('name', _("Router Name ="), True),
199                      ('status', _("Status ="), True),
200                      ('admin_state_up', _("Admin State ="), True,
201                       _("e.g. UP / DOWN")))
202
203
204STATUS_DISPLAY_CHOICES = (
205    ("active", pgettext_lazy("current status of router", "Active")),
206    ("error", pgettext_lazy("current status of router", "Error")),
207)
208ADMIN_STATE_DISPLAY_CHOICES = (
209    ("up", pgettext_lazy("Admin state of a Router", "UP")),
210    ("down", pgettext_lazy("Admin state of a Router", "DOWN")),
211)
212
213
214class RoutersTable(tables.DataTable):
215    name = tables.WrappingColumn("name",
216                                 verbose_name=_("Name"),
217                                 link="horizon:project:routers:detail")
218    status = tables.Column("status",
219                           verbose_name=_("Status"),
220                           status=True,
221                           display_choices=STATUS_DISPLAY_CHOICES)
222    distributed = tables.Column("distributed",
223                                filters=(filters.yesno, filters.capfirst),
224                                verbose_name=_("Distributed"))
225    ha = tables.Column("ha",
226                       filters=(filters.yesno, filters.capfirst),
227                       # Translators: High Availability mode of Neutron router
228                       verbose_name=_("HA mode"))
229    ext_net = tables.Column(get_external_network,
230                            verbose_name=_("External Network"))
231    admin_state = tables.Column("admin_state",
232                                verbose_name=_("Admin State"),
233                                display_choices=ADMIN_STATE_DISPLAY_CHOICES)
234    availability_zones = tables.Column(get_availability_zones,
235                                       verbose_name=_("Availability Zones"))
236
237    def __init__(self, request, data=None, needs_form_wrapper=None, **kwargs):
238        super().__init__(request, data=data,
239                         needs_form_wrapper=needs_form_wrapper, **kwargs)
240        if not api.neutron.get_feature_permission(request, "dvr", "get"):
241            del self.columns["distributed"]
242        if not api.neutron.get_feature_permission(request, "l3-ha", "get"):
243            del self.columns["ha"]
244        try:
245            if not api.neutron.is_extension_supported(
246                    request, "router_availability_zone"):
247                del self.columns["availability_zones"]
248        except Exception:
249            msg = _("Unable to check if router availability zone extension "
250                    "is supported")
251            exceptions.handle(self.request, msg)
252            del self.columns['availability_zones']
253
254    def get_object_display(self, obj):
255        return obj.name
256
257    class Meta(object):
258        name = "routers"
259        verbose_name = _("Routers")
260        status_columns = ["status"]
261        row_class = UpdateRow
262        table_actions = (CreateRouter, DeleteRouter,
263                         RoutersFilterAction)
264        row_actions = (SetGateway, ClearGateway, EditRouter, DeleteRouter)
265