1# Copyright 2012 United States Government as represented by the
2# Administrator of the National Aeronautics and Space Administration.
3# All Rights Reserved.
4#
5# Copyright 2012 OpenStack Foundation
6# Copyright 2012 Nebula, Inc.
7#
8#    Licensed under the Apache License, Version 2.0 (the "License"); you may
9#    not use this file except in compliance with the License. You may obtain
10#    a copy of the License at
11#
12#         http://www.apache.org/licenses/LICENSE-2.0
13#
14#    Unless required by applicable law or agreed to in writing, software
15#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
17#    License for the specific language governing permissions and limitations
18#    under the License.
19
20from django.urls import reverse
21from django.urls import reverse_lazy
22from django.utils.translation import ugettext_lazy as _
23
24from horizon import exceptions
25from horizon import forms
26from horizon import tables
27from horizon.utils import memoized
28
29from openstack_dashboard import api
30
31from openstack_dashboard.dashboards.admin.instances \
32    import forms as project_forms
33from openstack_dashboard.dashboards.admin.instances \
34    import tables as project_tables
35from openstack_dashboard.dashboards.admin.instances import tabs
36from openstack_dashboard.dashboards.project.instances import views
37from openstack_dashboard.dashboards.project.instances.workflows \
38    import update_instance
39from openstack_dashboard.utils import futurist_utils
40from openstack_dashboard.utils import settings as setting_utils
41
42
43# re-use console from project.instances.views to make reflection work
44def console(args, **kvargs):
45    return views.console(args, **kvargs)
46
47
48# re-use vnc from project.instances.views to make reflection work
49def vnc(args, **kvargs):
50    return views.vnc(args, **kvargs)
51
52
53# re-use spice from project.instances.views to make reflection work
54def spice(args, **kvargs):
55    return views.spice(args, **kvargs)
56
57
58# re-use rdp from project.instances.views to make reflection work
59def rdp(args, **kvargs):
60    return views.rdp(args, **kvargs)
61
62
63# re-use mks from project.instances.views to make reflection work
64def mks(args, **kvargs):
65    return views.mks(args, **kvargs)
66
67
68class AdminUpdateView(views.UpdateView):
69    workflow_class = update_instance.AdminUpdateInstance
70    success_url = reverse_lazy("horizon:admin:instances:index")
71
72
73class AdminIndexView(tables.PagedTableMixin, tables.DataTableView):
74    table_class = project_tables.AdminInstancesTable
75    page_title = _("Instances")
76
77    def has_prev_data(self, table):
78        return getattr(self, "_prev", False)
79
80    def has_more_data(self, table):
81        return self._more
82
83    def needs_filter_first(self, table):
84        return self._needs_filter_first
85
86    def _get_tenants(self):
87        # Gather our tenants to correlate against IDs
88        try:
89            tenants, __ = api.keystone.tenant_list(self.request)
90            return dict((t.id, t) for t in tenants)
91        except Exception:
92            msg = _('Unable to retrieve instance project information.')
93            exceptions.handle(self.request, msg)
94            return {}
95
96    def _get_images(self, instances=()):
97        # Gather our images to correlate our instances to them
98        try:
99            # NOTE(aarefiev): request images, instances was booted from.
100            img_ids = (instance.image.get('id') for instance in
101                       instances if isinstance(instance.image, dict))
102            real_img_ids = list(filter(None, img_ids))
103            images = api.glance.image_list_detailed_by_ids(
104                self.request, real_img_ids)
105            image_map = dict((image.id, image) for image in images)
106            return image_map
107        except Exception:
108            exceptions.handle(self.request, ignore=True)
109            return {}
110
111    def _get_images_by_name(self, image_name):
112        result = api.glance.image_list_detailed(
113            self.request, filters={'name': image_name})
114        images = result[0]
115        return dict((image.id, image) for image in images)
116
117    def _get_flavors(self):
118        # Gather our flavors to correlate against IDs
119        try:
120            flavors = api.nova.flavor_list(self.request)
121            return dict((str(flavor.id), flavor) for flavor in flavors)
122        except Exception:
123            msg = _("Unable to retrieve flavor list.")
124            exceptions.handle(self.request, msg)
125            return {}
126
127    def _get_instances(self, search_opts, sort_dir):
128        try:
129            instances, self._more, self._prev = api.nova.server_list_paged(
130                self.request,
131                search_opts=search_opts,
132                sort_dir=sort_dir)
133        except Exception:
134            self._more = self._prev = False
135            instances = []
136            exceptions.handle(self.request,
137                              _('Unable to retrieve instance list.'))
138        return instances
139
140    def get_data(self):
141        marker, sort_dir = self._get_marker()
142        default_search_opts = {'marker': marker,
143                               'paginate': True,
144                               'all_tenants': True}
145
146        search_opts = self.get_filters(default_search_opts.copy())
147
148        # If filter_first is set and if there are not other filters
149        # selected, then search criteria must be provided and return an empty
150        # list
151        if (setting_utils.get_dict_config('FILTER_DATA_FIRST',
152                                          'admin.instances') and
153                len(search_opts) == len(default_search_opts)):
154            self._needs_filter_first = True
155            self._more = False
156            return []
157
158        self._needs_filter_first = False
159
160        results = futurist_utils.call_functions_parallel(
161            self._get_flavors,
162            self._get_tenants)
163        flavor_dict, tenant_dict = results
164
165        non_api_filter_info = [
166            ('project', 'tenant_id', tenant_dict.values()),
167            ('flavor_name', 'flavor', flavor_dict.values()),
168        ]
169
170        filter_by_image_name = 'image_name' in search_opts
171        if filter_by_image_name:
172            image_dict = self._get_images_by_name(search_opts['image_name'])
173            non_api_filter_info.append(
174                ('image_name', 'image', image_dict.values())
175            )
176
177        if not views.process_non_api_filters(search_opts, non_api_filter_info):
178            self._more = False
179            return []
180
181        instances = self._get_instances(search_opts, sort_dir)
182
183        if not filter_by_image_name:
184            image_dict = self._get_images(tuple(instances))
185
186        # Loop through instances to get image, flavor and tenant info.
187        for inst in instances:
188            if hasattr(inst, 'image') and isinstance(inst.image, dict):
189                image_id = inst.image.get('id')
190                if image_id in image_dict:
191                    inst.image = image_dict[image_id]
192                # In case image not found in image_map, set name to empty
193                # to avoid fallback API call to Glance in api/nova.py
194                # until the call is deprecated in api itself
195                else:
196                    inst.image['name'] = _("-")
197
198            flavor_id = inst.flavor["id"]
199            try:
200                if flavor_id in flavor_dict:
201                    inst.full_flavor = flavor_dict[flavor_id]
202                else:
203                    # If the flavor_id is not in flavor_dict list,
204                    # gets it via nova api.
205                    inst.full_flavor = api.nova.flavor_get(
206                        self.request, flavor_id)
207            except Exception:
208                msg = _('Unable to retrieve instance size information.')
209                exceptions.handle(self.request, msg)
210            tenant = tenant_dict.get(inst.tenant_id, None)
211            inst.tenant_name = getattr(tenant, "name", None)
212        return instances
213
214
215class LiveMigrateView(forms.ModalFormView):
216    form_class = project_forms.LiveMigrateForm
217    template_name = 'admin/instances/live_migrate.html'
218    context_object_name = 'instance'
219    success_url = reverse_lazy("horizon:admin:instances:index")
220    page_title = _("Live Migrate")
221    success_label = page_title
222
223    def get_context_data(self, **kwargs):
224        context = super().get_context_data(**kwargs)
225        context["instance_id"] = self.kwargs['instance_id']
226        return context
227
228    @memoized.memoized_method
229    def get_hosts(self, *args, **kwargs):
230        try:
231            services = api.nova.service_list(self.request,
232                                             binary='nova-compute')
233            return [s.host for s in services]
234        except Exception:
235            redirect = reverse("horizon:admin:instances:index")
236            msg = _('Unable to retrieve host information.')
237            exceptions.handle(self.request, msg, redirect=redirect)
238
239    @memoized.memoized_method
240    def get_object(self, *args, **kwargs):
241        instance_id = self.kwargs['instance_id']
242        try:
243            return api.nova.server_get(self.request, instance_id)
244        except Exception:
245            redirect = reverse("horizon:admin:instances:index")
246            msg = _('Unable to retrieve instance details.')
247            exceptions.handle(self.request, msg, redirect=redirect)
248
249    def get_initial(self):
250        initial = super().get_initial()
251        _object = self.get_object()
252        if _object:
253            current_host = getattr(_object, 'OS-EXT-SRV-ATTR:host', '')
254            initial.update({'instance_id': self.kwargs['instance_id'],
255                            'current_host': current_host,
256                            'hosts': self.get_hosts()})
257        return initial
258
259
260class DetailView(views.DetailView):
261    tab_group_class = tabs.AdminInstanceDetailTabs
262    redirect_url = 'horizon:admin:instances:index'
263    image_url = 'horizon:admin:images:detail'
264    volume_url = 'horizon:admin:volumes:detail'
265
266    def _get_actions(self, instance):
267        table = project_tables.AdminInstancesTable(self.request)
268        return table.render_row_actions(instance)
269
270
271class RescueView(views.RescueView):
272    form_class = project_forms.RescueInstanceForm
273    submit_url = "horizon:admin:instances:rescue"
274    success_url = reverse_lazy('horizon:admin:instances:index')
275    template_name = 'admin/instances/rescue.html'
276
277    def get_initial(self):
278        return {'instance_id': self.kwargs["instance_id"]}
279