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