1# Copyright 2012,  Nachi Ueno,  NTT MCL,  Inc.
2# All rights reserved.
3
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8#      http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
16"""
17Views for managing Neutron Routers.
18"""
19import logging
20
21from django.urls import reverse
22from django.urls import reverse_lazy
23from django.utils.translation import ugettext_lazy as _
24
25from horizon import exceptions
26from horizon import forms
27from horizon import messages
28
29from openstack_dashboard import api
30
31LOG = logging.getLogger(__name__)
32
33
34class CreateForm(forms.SelfHandlingForm):
35    name = forms.CharField(max_length=255, label=_("Router Name"),
36                           required=False)
37    admin_state_up = forms.BooleanField(
38        label=_("Enable Admin State"),
39        initial=True,
40        required=False,
41        help_text=_("If checked, the router will be enabled."))
42    external_network = forms.ThemableChoiceField(label=_("External Network"),
43                                                 required=False)
44    enable_snat = forms.BooleanField(label=_("Enable SNAT"),
45                                     initial=True,
46                                     required=False)
47    mode = forms.ThemableChoiceField(label=_("Router Type"))
48    ha = forms.ThemableChoiceField(label=_("High Availability Mode"))
49    az_hints = forms.MultipleChoiceField(
50        label=_("Availability Zone Hints"),
51        required=False,
52        help_text=_("Availability Zones where the router may be scheduled. "
53                    "Leaving this unset is equivalent to selecting all "
54                    "Availability Zones"))
55    failure_url = 'horizon:project:routers:index'
56
57    def __init__(self, request, *args, **kwargs):
58        super().__init__(request, *args, **kwargs)
59        self.dvr_allowed = api.neutron.get_feature_permission(self.request,
60                                                              "dvr", "create")
61        if self.dvr_allowed:
62            mode_choices = [('server_default', _('Use Server Default')),
63                            ('centralized', _('Centralized')),
64                            ('distributed', _('Distributed'))]
65            self.fields['mode'].choices = mode_choices
66        else:
67            del self.fields['mode']
68
69        self.ha_allowed = api.neutron.get_feature_permission(self.request,
70                                                             "l3-ha", "create")
71        if self.ha_allowed:
72            ha_choices = [('server_default', _('Use Server Default')),
73                          ('enabled', _('Enable HA mode')),
74                          ('disabled', _('Disable HA mode'))]
75            self.fields['ha'].choices = ha_choices
76        else:
77            del self.fields['ha']
78        networks = self._get_network_list(request)
79        if networks:
80            self.fields['external_network'].choices = networks
81        else:
82            del self.fields['external_network']
83
84        self.enable_snat_allowed = self.initial['enable_snat_allowed']
85        if (not networks or not self.enable_snat_allowed):
86            del self.fields['enable_snat']
87
88        try:
89            az_supported = api.neutron.is_extension_supported(
90                self.request, 'router_availability_zone')
91
92            if az_supported:
93                zones = api.neutron.list_availability_zones(
94                    self.request, 'router', 'available')
95                self.fields['az_hints'].choices = [(zone['name'], zone['name'])
96                                                   for zone in zones]
97            else:
98                del self.fields['az_hints']
99        except Exception:
100            msg = _("Failed to get availability zone list.")
101            exceptions.handle(self.request, msg)
102            del self.fields['az_hints']
103
104    def _get_network_list(self, request):
105        search_opts = {'router:external': True}
106        try:
107            networks = api.neutron.network_list(request, **search_opts)
108        except Exception as e:
109            LOG.info('Failed to get network list: %s', e)
110            msg = _('Failed to get network list.')
111            messages.warning(request, msg)
112            networks = []
113
114        choices = [(network.id, network.name or network.id)
115                   for network in networks]
116        if choices:
117            choices.insert(0, ("", _("Select network")))
118        return choices
119
120    def handle(self, request, data):
121        try:
122            params = {'name': data['name'],
123                      'admin_state_up': data['admin_state_up']}
124            # NOTE: admin form allows to specify tenant_id.
125            # We have the logic here to simplify the logic.
126            if 'tenant_id' in data and data['tenant_id']:
127                params['tenant_id'] = data['tenant_id']
128            if 'external_network' in data and data['external_network']:
129                params['external_gateway_info'] = {'network_id':
130                                                   data['external_network']}
131                if self.enable_snat_allowed:
132                    params['external_gateway_info']['enable_snat'] = \
133                        data['enable_snat']
134            if 'az_hints' in data and data['az_hints']:
135                params['availability_zone_hints'] = data['az_hints']
136            if (self.dvr_allowed and data['mode'] != 'server_default'):
137                params['distributed'] = (data['mode'] == 'distributed')
138            if (self.ha_allowed and data['ha'] != 'server_default'):
139                params['ha'] = (data['ha'] == 'enabled')
140            router = api.neutron.router_create(request, **params)
141            message = (_('Router %s was successfully created.') %
142                       router.name_or_id)
143            messages.success(request, message)
144            return router
145        except Exception as exc:
146            LOG.info('Failed to create router: %s', exc)
147            if exc.status_code == 409:
148                msg = _('Quota exceeded for resource router.')
149            else:
150                if data["name"]:
151                    msg = _('Failed to create router "%s".') % data['name']
152                else:
153                    msg = _('Failed to create router.')
154            redirect = reverse(self.failure_url)
155            exceptions.handle(request, msg, redirect=redirect)
156            return False
157
158
159class UpdateForm(forms.SelfHandlingForm):
160    name = forms.CharField(label=_("Name"), required=False)
161    admin_state = forms.BooleanField(
162        label=_("Enable Admin State"),
163        required=False,
164        help_text=_("If checked, the router will be enabled."))
165    mode = forms.ThemableChoiceField(label=_("Router Type"))
166    ha = forms.BooleanField(label=_("High Availability Mode"), required=False)
167
168    redirect_url = reverse_lazy('horizon:project:routers:index')
169
170    def __init__(self, request, *args, **kwargs):
171        super().__init__(request, *args, **kwargs)
172        self.dvr_allowed = api.neutron.get_feature_permission(self.request,
173                                                              "dvr", "update")
174        if not self.dvr_allowed:
175            del self.fields['mode']
176        elif self.initial.get('mode') == 'distributed':
177            # Neutron supports only changing from centralized to
178            # distributed now.
179            mode_choices = [('distributed', _('Distributed'))]
180            self.fields['mode'].widget = forms.TextInput(attrs={'readonly':
181                                                                'readonly'})
182            self.fields['mode'].choices = mode_choices
183        else:
184            mode_choices = [('centralized', _('Centralized')),
185                            ('distributed', _('Distributed'))]
186            self.fields['mode'].choices = mode_choices
187
188        # TODO(amotoki): Due to Neutron Bug 1378525, Neutron disables
189        # PUT operation. It will be fixed in Kilo cycle.
190        # self.ha_allowed = api.neutron.get_feature_permission(
191        #     self.request, "l3-ha", "update")
192        self.ha_allowed = False
193        if not self.ha_allowed:
194            del self.fields['ha']
195
196    def handle(self, request, data):
197        try:
198            params = {'admin_state_up': data['admin_state'],
199                      'name': data['name']}
200            if self.dvr_allowed:
201                params['distributed'] = (data['mode'] == 'distributed')
202            if self.ha_allowed:
203                params['ha'] = data['ha']
204            router = api.neutron.router_update(request,
205                                               self.initial['router_id'],
206                                               **params)
207            msg = _('Router %s was successfully updated.') % router.name_or_id
208            messages.success(request, msg)
209            return router
210        except Exception as exc:
211            LOG.info('Failed to update router %(id)s: %(exc)s',
212                     {'id': self.initial['router_id'], 'exc': exc})
213            name_or_id = data['name'] or self.initial['router_id']
214            msg = _('Failed to update router %s') % name_or_id
215            exceptions.handle(request, msg, redirect=self.redirect_url)
216