OpenStack Dashboard (Horizon)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1081 lines
36KB

  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. # Copyright (c) 2012 X.commerce, a business unit of eBay Inc.
  8. #
  9. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  10. # not use this file except in compliance with the License. You may obtain
  11. # a copy of the License at
  12. #
  13. # http://www.apache.org/licenses/LICENSE-2.0
  14. #
  15. # Unless required by applicable law or agreed to in writing, software
  16. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  17. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  18. # License for the specific language governing permissions and limitations
  19. # under the License.
  20. from __future__ import absolute_import
  21. import collections
  22. import logging
  23. from operator import attrgetter
  24. from django.conf import settings
  25. from django.utils.translation import ugettext_lazy as _
  26. from novaclient import api_versions
  27. from novaclient import exceptions as nova_exceptions
  28. from novaclient.v2 import instance_action as nova_instance_action
  29. from novaclient.v2 import list_extensions as nova_list_extensions
  30. from novaclient.v2 import servers as nova_servers
  31. from horizon import exceptions as horizon_exceptions
  32. from horizon.utils import functions as utils
  33. from horizon.utils import memoized
  34. from openstack_dashboard.api import _nova
  35. from openstack_dashboard.api import base
  36. from openstack_dashboard.api import cinder
  37. from openstack_dashboard.contrib.developer.profiler import api as profiler
  38. LOG = logging.getLogger(__name__)
  39. # API static values
  40. INSTANCE_ACTIVE_STATE = 'ACTIVE'
  41. VOLUME_STATE_AVAILABLE = "available"
  42. DEFAULT_QUOTA_NAME = 'default'
  43. get_microversion = _nova.get_microversion
  44. server_get = _nova.server_get
  45. Server = _nova.Server
  46. def is_feature_available(request, features):
  47. return bool(get_microversion(request, features))
  48. class VolumeMultiattachNotSupported(horizon_exceptions.HorizonException):
  49. status_code = 400
  50. class VNCConsole(base.APIDictWrapper):
  51. """Wrapper for the "console" dictionary.
  52. Returned by the novaclient.servers.get_vnc_console method.
  53. """
  54. _attrs = ['url', 'type']
  55. class SPICEConsole(base.APIDictWrapper):
  56. """Wrapper for the "console" dictionary.
  57. Returned by the novaclient.servers.get_spice_console method.
  58. """
  59. _attrs = ['url', 'type']
  60. class RDPConsole(base.APIDictWrapper):
  61. """Wrapper for the "console" dictionary.
  62. Returned by the novaclient.servers.get_rdp_console method.
  63. """
  64. _attrs = ['url', 'type']
  65. class SerialConsole(base.APIDictWrapper):
  66. """Wrapper for the "console" dictionary.
  67. Returned by the novaclient.servers.get_serial_console method.
  68. """
  69. _attrs = ['url', 'type']
  70. class MKSConsole(base.APIDictWrapper):
  71. """Wrapper for the "console" dictionary.
  72. Returned by the novaclient.servers.get_mks_console method.
  73. """
  74. _attrs = ['url', 'type']
  75. class Hypervisor(base.APIDictWrapper):
  76. """Simple wrapper around novaclient.hypervisors.Hypervisor."""
  77. _attrs = ['manager', '_loaded', '_info', 'hypervisor_hostname', 'id',
  78. 'servers']
  79. @property
  80. def servers(self):
  81. # if hypervisor doesn't have servers, the attribute is not present
  82. servers = []
  83. try:
  84. servers = self._apidict.servers
  85. except Exception:
  86. pass
  87. return servers
  88. class NovaUsage(base.APIResourceWrapper):
  89. """Simple wrapper around contrib/simple_usage.py."""
  90. _attrs = ['start', 'server_usages', 'stop', 'tenant_id',
  91. 'total_local_gb_usage', 'total_memory_mb_usage',
  92. 'total_vcpus_usage', 'total_hours']
  93. def get_summary(self):
  94. return {'instances': self.total_active_instances,
  95. 'memory_mb': self.memory_mb,
  96. 'vcpus': self.vcpus,
  97. 'vcpu_hours': self.vcpu_hours,
  98. 'local_gb': self.local_gb,
  99. 'disk_gb_hours': self.disk_gb_hours,
  100. 'memory_mb_hours': self.memory_mb_hours}
  101. @property
  102. def total_active_instances(self):
  103. return sum(1 for s in self.server_usages if s['ended_at'] is None)
  104. @property
  105. def vcpus(self):
  106. return sum(s['vcpus'] for s in self.server_usages
  107. if s['ended_at'] is None)
  108. @property
  109. def vcpu_hours(self):
  110. return getattr(self, "total_vcpus_usage", 0)
  111. @property
  112. def local_gb(self):
  113. return sum(s['local_gb'] for s in self.server_usages
  114. if s['ended_at'] is None)
  115. @property
  116. def memory_mb(self):
  117. return sum(s['memory_mb'] for s in self.server_usages
  118. if s['ended_at'] is None)
  119. @property
  120. def disk_gb_hours(self):
  121. return getattr(self, "total_local_gb_usage", 0)
  122. @property
  123. def memory_mb_hours(self):
  124. return getattr(self, "total_memory_mb_usage", 0)
  125. class FlavorExtraSpec(object):
  126. def __init__(self, flavor_id, key, val):
  127. self.flavor_id = flavor_id
  128. self.id = key
  129. self.key = key
  130. self.value = val
  131. class QuotaSet(base.QuotaSet):
  132. # We don't support nova-network, so we exclude nova-network relatd
  133. # quota fields from the response.
  134. ignore_quotas = {
  135. "floating_ips",
  136. "fixed_ips",
  137. "security_groups",
  138. "security_group_rules",
  139. }
  140. def upgrade_api(request, client, version):
  141. """Ugrade the nova API to the specified version if possible."""
  142. min_ver, max_ver = api_versions._get_server_version_range(client)
  143. if min_ver <= api_versions.APIVersion(version) <= max_ver:
  144. client = _nova.novaclient(request, version)
  145. return client
  146. @profiler.trace
  147. def server_vnc_console(request, instance_id, console_type='novnc'):
  148. nc = _nova.novaclient(request)
  149. console = nc.servers.get_vnc_console(instance_id, console_type)
  150. return VNCConsole(console['console'])
  151. @profiler.trace
  152. def server_spice_console(request, instance_id, console_type='spice-html5'):
  153. nc = _nova.novaclient(request)
  154. console = nc.servers.get_spice_console(instance_id, console_type)
  155. return SPICEConsole(console['console'])
  156. @profiler.trace
  157. def server_rdp_console(request, instance_id, console_type='rdp-html5'):
  158. nc = _nova.novaclient(request)
  159. console = nc.servers.get_rdp_console(instance_id, console_type)
  160. return RDPConsole(console['console'])
  161. @profiler.trace
  162. def server_serial_console(request, instance_id, console_type='serial'):
  163. nc = _nova.novaclient(request)
  164. console = nc.servers.get_serial_console(instance_id, console_type)
  165. return SerialConsole(console['console'])
  166. @profiler.trace
  167. def server_mks_console(request, instance_id, console_type='mks'):
  168. microver = get_microversion(request, "remote_console_mks")
  169. nc = _nova.novaclient(request, microver)
  170. console = nc.servers.get_mks_console(instance_id, console_type)
  171. return MKSConsole(console['remote_console'])
  172. @profiler.trace
  173. def flavor_create(request, name, memory, vcpu, disk, flavorid='auto',
  174. ephemeral=0, swap=0, metadata=None, is_public=True,
  175. rxtx_factor=1):
  176. flavor = _nova.novaclient(request).flavors.create(name, memory, vcpu, disk,
  177. flavorid=flavorid,
  178. ephemeral=ephemeral,
  179. swap=swap,
  180. is_public=is_public,
  181. rxtx_factor=rxtx_factor)
  182. if (metadata):
  183. flavor_extra_set(request, flavor.id, metadata)
  184. return flavor
  185. @profiler.trace
  186. def flavor_delete(request, flavor_id):
  187. _nova.novaclient(request).flavors.delete(flavor_id)
  188. @profiler.trace
  189. def flavor_get(request, flavor_id, get_extras=False):
  190. flavor = _nova.novaclient(request).flavors.get(flavor_id)
  191. if get_extras:
  192. flavor.extras = flavor_get_extras(request, flavor.id, True, flavor)
  193. return flavor
  194. @profiler.trace
  195. @memoized.memoized
  196. def flavor_list(request, is_public=True, get_extras=False):
  197. """Get the list of available instance sizes (flavors)."""
  198. flavors = _nova.novaclient(request).flavors.list(is_public=is_public)
  199. if get_extras:
  200. for flavor in flavors:
  201. flavor.extras = flavor_get_extras(request, flavor.id, True, flavor)
  202. return flavors
  203. @profiler.trace
  204. def update_pagination(entities, page_size, marker, reversed_order=False):
  205. has_more_data = has_prev_data = False
  206. if len(entities) > page_size:
  207. has_more_data = True
  208. entities.pop()
  209. if marker is not None:
  210. has_prev_data = True
  211. # first page condition when reached via prev back
  212. elif reversed_order and marker is not None:
  213. has_more_data = True
  214. # last page condition
  215. elif marker is not None:
  216. has_prev_data = True
  217. # restore the original ordering here
  218. if reversed_order:
  219. entities.reverse()
  220. return entities, has_more_data, has_prev_data
  221. @profiler.trace
  222. @memoized.memoized
  223. def flavor_list_paged(request, is_public=True, get_extras=False, marker=None,
  224. paginate=False, sort_key="name", sort_dir="desc",
  225. reversed_order=False):
  226. """Get the list of available instance sizes (flavors)."""
  227. has_more_data = False
  228. has_prev_data = False
  229. if paginate:
  230. if reversed_order:
  231. sort_dir = 'desc' if sort_dir == 'asc' else 'asc'
  232. page_size = utils.get_page_size(request)
  233. flavors = _nova.novaclient(request).flavors.list(is_public=is_public,
  234. marker=marker,
  235. limit=page_size + 1,
  236. sort_key=sort_key,
  237. sort_dir=sort_dir)
  238. flavors, has_more_data, has_prev_data = update_pagination(
  239. flavors, page_size, marker, reversed_order)
  240. else:
  241. flavors = _nova.novaclient(request).flavors.list(is_public=is_public)
  242. if get_extras:
  243. for flavor in flavors:
  244. flavor.extras = flavor_get_extras(request, flavor.id, True, flavor)
  245. return (flavors, has_more_data, has_prev_data)
  246. @profiler.trace
  247. @memoized.memoized
  248. def flavor_access_list(request, flavor=None):
  249. """Get the list of access instance sizes (flavors)."""
  250. return _nova.novaclient(request).flavor_access.list(flavor=flavor)
  251. @profiler.trace
  252. def add_tenant_to_flavor(request, flavor, tenant):
  253. """Add a tenant to the given flavor access list."""
  254. return _nova.novaclient(request).flavor_access.add_tenant_access(
  255. flavor=flavor, tenant=tenant)
  256. @profiler.trace
  257. def remove_tenant_from_flavor(request, flavor, tenant):
  258. """Remove a tenant from the given flavor access list."""
  259. return _nova.novaclient(request).flavor_access.remove_tenant_access(
  260. flavor=flavor, tenant=tenant)
  261. @profiler.trace
  262. def flavor_get_extras(request, flavor_id, raw=False, flavor=None):
  263. """Get flavor extra specs."""
  264. if flavor is None:
  265. flavor = _nova.novaclient(request).flavors.get(flavor_id)
  266. extras = flavor.get_keys()
  267. if raw:
  268. return extras
  269. return [FlavorExtraSpec(flavor_id, key, value) for
  270. key, value in extras.items()]
  271. @profiler.trace
  272. def flavor_extra_delete(request, flavor_id, keys):
  273. """Unset the flavor extra spec keys."""
  274. flavor = _nova.novaclient(request).flavors.get(flavor_id)
  275. return flavor.unset_keys(keys)
  276. @profiler.trace
  277. def flavor_extra_set(request, flavor_id, metadata):
  278. """Set the flavor extra spec keys."""
  279. flavor = _nova.novaclient(request).flavors.get(flavor_id)
  280. if (not metadata): # not a way to delete keys
  281. return None
  282. return flavor.set_keys(metadata)
  283. @profiler.trace
  284. def snapshot_create(request, instance_id, name):
  285. return _nova.novaclient(request).servers.create_image(instance_id, name)
  286. @profiler.trace
  287. def keypair_create(request, name, key_type='ssh'):
  288. microversion = get_microversion(request, 'key_types')
  289. return _nova.novaclient(request, microversion).\
  290. keypairs.create(name, key_type=key_type)
  291. @profiler.trace
  292. def keypair_import(request, name, public_key, key_type='ssh'):
  293. microversion = get_microversion(request, 'key_types')
  294. return _nova.novaclient(request, microversion).\
  295. keypairs.create(name, public_key, key_type)
  296. @profiler.trace
  297. def keypair_delete(request, name):
  298. _nova.novaclient(request).keypairs.delete(name)
  299. @profiler.trace
  300. def keypair_list(request):
  301. microversion = get_microversion(request, 'key_type_list')
  302. return _nova.novaclient(request, microversion).keypairs.list()
  303. @profiler.trace
  304. def keypair_get(request, name):
  305. return _nova.novaclient(request).keypairs.get(name)
  306. @profiler.trace
  307. def server_create(request, name, image, flavor, key_name, user_data,
  308. security_groups, block_device_mapping=None,
  309. block_device_mapping_v2=None, nics=None,
  310. availability_zone=None, instance_count=1, admin_pass=None,
  311. disk_config=None, config_drive=None, meta=None,
  312. scheduler_hints=None, description=None):
  313. microversion = get_microversion(request, ("instance_description",
  314. "auto_allocated_network"))
  315. nova_client = _nova.novaclient(request, version=microversion)
  316. # NOTE(amotoki): Handling auto allocated network
  317. # Nova API 2.37 or later, it accepts a special string 'auto' for nics
  318. # which means nova uses a network that is available for a current project
  319. # if one exists and otherwise it creates a network automatically.
  320. # This special handling is processed here as JS side assumes 'nics'
  321. # is a list and it is easiest to handle it here.
  322. if nics:
  323. is_auto_allocate = any(nic.get('net-id') == '__auto_allocate__'
  324. for nic in nics)
  325. if is_auto_allocate:
  326. nics = 'auto'
  327. kwargs = {}
  328. if description is not None:
  329. kwargs['description'] = description
  330. return Server(nova_client.servers.create(
  331. name.strip(), image, flavor, userdata=user_data,
  332. security_groups=security_groups,
  333. key_name=key_name, block_device_mapping=block_device_mapping,
  334. block_device_mapping_v2=block_device_mapping_v2,
  335. nics=nics, availability_zone=availability_zone,
  336. min_count=instance_count, admin_pass=admin_pass,
  337. disk_config=disk_config, config_drive=config_drive,
  338. meta=meta, scheduler_hints=scheduler_hints, **kwargs), request)
  339. @profiler.trace
  340. def server_delete(request, instance_id):
  341. _nova.novaclient(request).servers.delete(instance_id)
  342. # Session is available and consistent for the current view
  343. # among Horizon django servers even in load-balancing setup,
  344. # so only the view listing the servers will recognize it as
  345. # own DeleteInstance action performed. Note that dict is passed
  346. # by reference in python. Quote from django's developer manual:
  347. # " You can read it and write to request.session at any point
  348. # in your view. You can edit it multiple times."
  349. request.session['server_deleted'] = instance_id
  350. def get_novaclient_with_locked_status(request):
  351. microversion = get_microversion(request, "locked_attribute")
  352. return _nova.novaclient(request, version=microversion)
  353. @profiler.trace
  354. def server_list_paged(request,
  355. search_opts=None,
  356. detailed=True,
  357. sort_dir="desc"):
  358. has_more_data = False
  359. has_prev_data = False
  360. nova_client = get_novaclient_with_locked_status(request)
  361. page_size = utils.get_page_size(request)
  362. search_opts = {} if search_opts is None else search_opts
  363. marker = search_opts.get('marker', None)
  364. if not search_opts.get('all_tenants', False):
  365. search_opts['project_id'] = request.user.tenant_id
  366. if search_opts.pop('paginate', False):
  367. reversed_order = sort_dir == "asc"
  368. LOG.debug("Notify received on deleted server: %r",
  369. ('server_deleted' in request.session))
  370. deleted = request.session.pop('server_deleted',
  371. None)
  372. view_marker = 'possibly_deleted' if deleted and marker else 'ok'
  373. search_opts['marker'] = deleted if deleted else marker
  374. search_opts['limit'] = page_size + 1
  375. # NOTE(amotoki): It looks like the 'sort_keys' must be unique to make
  376. # the pagination in the nova API works as expected. Multiple servers
  377. # can have a same 'created_at' as its resolution is a second.
  378. # To ensure the uniqueness we add 'uuid' to the sort keys.
  379. # 'display_name' is added before 'uuid' to list servers in the
  380. # alphabetical order.
  381. sort_keys = ['created_at', 'display_name', 'uuid']
  382. servers = [Server(s, request)
  383. for s in nova_client.servers.list(detailed, search_opts,
  384. sort_keys=sort_keys,
  385. sort_dirs=[sort_dir] * 3)]
  386. if view_marker == 'possibly_deleted':
  387. if not servers:
  388. view_marker = 'head_deleted'
  389. reversed_order = False
  390. servers = [Server(s, request)
  391. for s in
  392. nova_client.servers.list(detailed,
  393. search_opts,
  394. sort_keys=sort_keys,
  395. sort_dirs=['desc'] * 3)]
  396. if not servers:
  397. view_marker = 'tail_deleted'
  398. reversed_order = True
  399. servers = [Server(s, request)
  400. for s in
  401. nova_client.servers.list(detailed,
  402. search_opts,
  403. sort_keys=sort_keys,
  404. sort_dirs=['asc'] * 3)]
  405. (servers, has_more_data, has_prev_data) = update_pagination(
  406. servers, page_size, marker, reversed_order)
  407. has_prev_data = (False
  408. if view_marker == 'head_deleted'
  409. else has_prev_data)
  410. has_more_data = (False
  411. if view_marker == 'tail_deleted'
  412. else has_more_data)
  413. else:
  414. servers = [Server(s, request)
  415. for s in nova_client.servers.list(detailed, search_opts)]
  416. return (servers, has_more_data, has_prev_data)
  417. @profiler.trace
  418. def server_list(request, search_opts=None, detailed=True):
  419. (servers, has_more_data, _) = server_list_paged(request,
  420. search_opts,
  421. detailed)
  422. return (servers, has_more_data)
  423. @profiler.trace
  424. def server_console_output(request, instance_id, tail_length=None):
  425. """Gets console output of an instance."""
  426. nc = _nova.novaclient(request)
  427. return nc.servers.get_console_output(instance_id, length=tail_length)
  428. @profiler.trace
  429. def server_pause(request, instance_id):
  430. _nova.novaclient(request).servers.pause(instance_id)
  431. @profiler.trace
  432. def server_unpause(request, instance_id):
  433. _nova.novaclient(request).servers.unpause(instance_id)
  434. @profiler.trace
  435. def server_suspend(request, instance_id):
  436. _nova.novaclient(request).servers.suspend(instance_id)
  437. @profiler.trace
  438. def server_resume(request, instance_id):
  439. _nova.novaclient(request).servers.resume(instance_id)
  440. @profiler.trace
  441. def server_shelve(request, instance_id):
  442. _nova.novaclient(request).servers.shelve(instance_id)
  443. @profiler.trace
  444. def server_unshelve(request, instance_id):
  445. _nova.novaclient(request).servers.unshelve(instance_id)
  446. @profiler.trace
  447. def server_reboot(request, instance_id, soft_reboot=False):
  448. hardness = nova_servers.REBOOT_HARD
  449. if soft_reboot:
  450. hardness = nova_servers.REBOOT_SOFT
  451. _nova.novaclient(request).servers.reboot(instance_id, hardness)
  452. @profiler.trace
  453. def server_rebuild(request, instance_id, image_id, password=None,
  454. disk_config=None, description=None):
  455. kwargs = {}
  456. if description:
  457. kwargs['description'] = description
  458. nc = _nova.get_novaclient_with_instance_desc(request)
  459. return nc.servers.rebuild(instance_id, image_id, password, disk_config,
  460. **kwargs)
  461. @profiler.trace
  462. def server_update(request, instance_id, name, description=None):
  463. nc = _nova.get_novaclient_with_instance_desc(request)
  464. return nc.servers.update(instance_id, name=name.strip(),
  465. description=description)
  466. @profiler.trace
  467. def server_migrate(request, instance_id):
  468. _nova.novaclient(request).servers.migrate(instance_id)
  469. @profiler.trace
  470. def server_live_migrate(request, instance_id, host, block_migration=False,
  471. disk_over_commit=False):
  472. _nova.novaclient(request).servers.live_migrate(instance_id, host,
  473. block_migration,
  474. disk_over_commit)
  475. @profiler.trace
  476. def server_resize(request, instance_id, flavor, disk_config=None, **kwargs):
  477. _nova.novaclient(request).servers.resize(instance_id, flavor,
  478. disk_config, **kwargs)
  479. @profiler.trace
  480. def server_confirm_resize(request, instance_id):
  481. _nova.novaclient(request).servers.confirm_resize(instance_id)
  482. @profiler.trace
  483. def server_revert_resize(request, instance_id):
  484. _nova.novaclient(request).servers.revert_resize(instance_id)
  485. @profiler.trace
  486. def server_start(request, instance_id):
  487. _nova.novaclient(request).servers.start(instance_id)
  488. @profiler.trace
  489. def server_stop(request, instance_id):
  490. _nova.novaclient(request).servers.stop(instance_id)
  491. @profiler.trace
  492. def server_lock(request, instance_id):
  493. microversion = get_microversion(request, "locked_attribute")
  494. _nova.novaclient(request, version=microversion).servers.lock(instance_id)
  495. @profiler.trace
  496. def server_unlock(request, instance_id):
  497. microversion = get_microversion(request, "locked_attribute")
  498. _nova.novaclient(request, version=microversion).servers.unlock(instance_id)
  499. @profiler.trace
  500. def server_metadata_update(request, instance_id, metadata):
  501. _nova.novaclient(request).servers.set_meta(instance_id, metadata)
  502. @profiler.trace
  503. def server_metadata_delete(request, instance_id, keys):
  504. _nova.novaclient(request).servers.delete_meta(instance_id, keys)
  505. @profiler.trace
  506. def server_rescue(request, instance_id, password=None, image=None):
  507. _nova.novaclient(request).servers.rescue(instance_id,
  508. password=password,
  509. image=image)
  510. @profiler.trace
  511. def server_unrescue(request, instance_id):
  512. _nova.novaclient(request).servers.unrescue(instance_id)
  513. @profiler.trace
  514. def tenant_quota_get(request, tenant_id):
  515. return QuotaSet(_nova.novaclient(request).quotas.get(tenant_id))
  516. @profiler.trace
  517. def tenant_quota_update(request, tenant_id, **kwargs):
  518. if kwargs:
  519. _nova.novaclient(request).quotas.update(tenant_id, **kwargs)
  520. @profiler.trace
  521. def default_quota_get(request, tenant_id):
  522. return QuotaSet(_nova.novaclient(request).quotas.defaults(tenant_id))
  523. @profiler.trace
  524. def default_quota_update(request, **kwargs):
  525. _nova.novaclient(request).quota_classes.update(DEFAULT_QUOTA_NAME,
  526. **kwargs)
  527. def _get_usage_marker(usage):
  528. marker = None
  529. if hasattr(usage, 'server_usages') and usage.server_usages:
  530. marker = usage.server_usages[-1].get('instance_id')
  531. return marker
  532. def _get_usage_list_marker(usage_list):
  533. marker = None
  534. if usage_list:
  535. marker = _get_usage_marker(usage_list[-1])
  536. return marker
  537. def _merge_usage(usage, next_usage):
  538. usage.server_usages.extend(next_usage.server_usages)
  539. usage.total_hours += next_usage.total_hours
  540. usage.total_memory_mb_usage += next_usage.total_memory_mb_usage
  541. usage.total_vcpus_usage += next_usage.total_vcpus_usage
  542. usage.total_local_gb_usage += next_usage.total_local_gb_usage
  543. def _merge_usage_list(usages, next_usage_list):
  544. for next_usage in next_usage_list:
  545. if next_usage.tenant_id in usages:
  546. _merge_usage(usages[next_usage.tenant_id], next_usage)
  547. else:
  548. usages[next_usage.tenant_id] = next_usage
  549. @profiler.trace
  550. def usage_get(request, tenant_id, start, end):
  551. client = upgrade_api(request, _nova.novaclient(request), '2.40')
  552. usage = client.usage.get(tenant_id, start, end)
  553. if client.api_version >= api_versions.APIVersion('2.40'):
  554. # If the number of instances used to calculate the usage is greater
  555. # than max_limit, the usage will be split across multiple requests
  556. # and the responses will need to be merged back together.
  557. marker = _get_usage_marker(usage)
  558. while marker:
  559. next_usage = client.usage.get(tenant_id, start, end, marker=marker)
  560. marker = _get_usage_marker(next_usage)
  561. if marker:
  562. _merge_usage(usage, next_usage)
  563. return NovaUsage(usage)
  564. @profiler.trace
  565. def usage_list(request, start, end):
  566. client = upgrade_api(request, _nova.novaclient(request), '2.40')
  567. usage_list = client.usage.list(start, end, True)
  568. if client.api_version >= api_versions.APIVersion('2.40'):
  569. # If the number of instances used to calculate the usage is greater
  570. # than max_limit, the usage will be split across multiple requests
  571. # and the responses will need to be merged back together.
  572. usages = collections.OrderedDict()
  573. _merge_usage_list(usages, usage_list)
  574. marker = _get_usage_list_marker(usage_list)
  575. while marker:
  576. next_usage_list = client.usage.list(start, end, True,
  577. marker=marker)
  578. marker = _get_usage_list_marker(next_usage_list)
  579. if marker:
  580. _merge_usage_list(usages, next_usage_list)
  581. usage_list = usages.values()
  582. return [NovaUsage(u) for u in usage_list]
  583. @profiler.trace
  584. def get_password(request, instance_id, private_key=None):
  585. return _nova.novaclient(request).servers.get_password(instance_id,
  586. private_key)
  587. @profiler.trace
  588. def instance_volume_attach(request, volume_id, instance_id, device):
  589. # If we have a multiattach volume, we need to use microversion>=2.60.
  590. volume = cinder.volume_get(request, volume_id)
  591. if volume.multiattach:
  592. version = get_microversion(request, 'multiattach')
  593. if version:
  594. client = _nova.novaclient(request, version)
  595. else:
  596. raise VolumeMultiattachNotSupported(
  597. _('Multiattach volumes are not yet supported.'))
  598. else:
  599. client = _nova.novaclient(request)
  600. return client.volumes.create_server_volume(
  601. instance_id, volume_id, device)
  602. @profiler.trace
  603. def instance_volume_detach(request, instance_id, att_id):
  604. return _nova.novaclient(request).volumes.delete_server_volume(instance_id,
  605. att_id)
  606. @profiler.trace
  607. def instance_volumes_list(request, instance_id):
  608. volumes = _nova.novaclient(request).volumes.get_server_volumes(instance_id)
  609. for volume in volumes:
  610. volume_data = cinder.cinderclient(request).volumes.get(volume.id)
  611. volume.name = cinder.Volume(volume_data).name
  612. return volumes
  613. @profiler.trace
  614. def hypervisor_list(request):
  615. return _nova.novaclient(request).hypervisors.list()
  616. @profiler.trace
  617. def hypervisor_stats(request):
  618. return _nova.novaclient(request).hypervisors.statistics()
  619. @profiler.trace
  620. def hypervisor_search(request, query, servers=True):
  621. return _nova.novaclient(request).hypervisors.search(query, servers)
  622. @profiler.trace
  623. def evacuate_host(request, host, target=None, on_shared_storage=False):
  624. # TODO(jmolle) This should be change for nova atomic api host_evacuate
  625. hypervisors = _nova.novaclient(request).hypervisors.search(host, True)
  626. response = []
  627. err_code = None
  628. for hypervisor in hypervisors:
  629. hyper = Hypervisor(hypervisor)
  630. # if hypervisor doesn't have servers, the attribute is not present
  631. for server in hyper.servers:
  632. try:
  633. _nova.novaclient(request).servers.evacuate(server['uuid'],
  634. target,
  635. on_shared_storage)
  636. except nova_exceptions.ClientException as err:
  637. err_code = err.code
  638. msg = _("Name: %(name)s ID: %(uuid)s")
  639. msg = msg % {'name': server['name'], 'uuid': server['uuid']}
  640. response.append(msg)
  641. if err_code:
  642. msg = _('Failed to evacuate instances: %s') % ', '.join(response)
  643. raise nova_exceptions.ClientException(err_code, msg)
  644. return True
  645. @profiler.trace
  646. def migrate_host(request, host, live_migrate=False, disk_over_commit=False,
  647. block_migration=False):
  648. nc = _nova.novaclient(request)
  649. hypervisors = nc.hypervisors.search(host, True)
  650. response = []
  651. err_code = None
  652. for hyper in hypervisors:
  653. for server in getattr(hyper, "servers", []):
  654. try:
  655. if live_migrate:
  656. instance = server_get(request, server['uuid'])
  657. # Checking that instance can be live-migrated
  658. if instance.status in ["ACTIVE", "PAUSED"]:
  659. nc.servers.live_migrate(
  660. server['uuid'],
  661. None,
  662. block_migration,
  663. disk_over_commit
  664. )
  665. else:
  666. nc.servers.migrate(server['uuid'])
  667. else:
  668. nc.servers.migrate(server['uuid'])
  669. except nova_exceptions.ClientException as err:
  670. err_code = err.code
  671. msg = _("Name: %(name)s ID: %(uuid)s")
  672. msg = msg % {'name': server['name'], 'uuid': server['uuid']}
  673. response.append(msg)
  674. if err_code:
  675. msg = _('Failed to migrate instances: %s') % ', '.join(response)
  676. raise nova_exceptions.ClientException(err_code, msg)
  677. return True
  678. @profiler.trace
  679. def tenant_absolute_limits(request, reserved=False, tenant_id=None):
  680. # Nova does not allow to specify tenant_id for non-admin users
  681. # even if tenant_id matches a tenant_id of the user.
  682. if tenant_id == request.user.tenant_id:
  683. tenant_id = None
  684. limits = _nova.novaclient(request).limits.get(reserved=reserved,
  685. tenant_id=tenant_id).absolute
  686. limits_dict = {}
  687. for limit in limits:
  688. if limit.value < 0:
  689. # Workaround for nova bug 1370867 that absolute_limits
  690. # returns negative value for total.*Used instead of 0.
  691. # For such case, replace negative values with 0.
  692. if limit.name.startswith('total') and limit.name.endswith('Used'):
  693. limits_dict[limit.name] = 0
  694. else:
  695. # -1 is used to represent unlimited quotas
  696. limits_dict[limit.name] = float("inf")
  697. else:
  698. limits_dict[limit.name] = limit.value
  699. return limits_dict
  700. @profiler.trace
  701. def availability_zone_list(request, detailed=False):
  702. nc = _nova.novaclient(request)
  703. zones = nc.availability_zones.list(detailed=detailed)
  704. zones.sort(key=attrgetter('zoneName'))
  705. return zones
  706. @profiler.trace
  707. def server_group_list(request):
  708. return _nova.novaclient(request).server_groups.list()
  709. @profiler.trace
  710. def server_group_create(request, **kwargs):
  711. microversion = get_microversion(request, "servergroup_soft_policies")
  712. nc = _nova.novaclient(request, version=microversion)
  713. return nc.server_groups.create(**kwargs)
  714. @profiler.trace
  715. def server_group_delete(request, servergroup_id):
  716. _nova.novaclient(request).server_groups.delete(servergroup_id)
  717. @profiler.trace
  718. def server_group_get(request, servergroup_id):
  719. microversion = get_microversion(request, "servergroup_user_info")
  720. return _nova.novaclient(request, version=microversion).server_groups.get(
  721. servergroup_id)
  722. @profiler.trace
  723. def service_list(request, binary=None):
  724. return _nova.novaclient(request).services.list(binary=binary)
  725. @profiler.trace
  726. def service_enable(request, host, binary):
  727. return _nova.novaclient(request).services.enable(host, binary)
  728. @profiler.trace
  729. def service_disable(request, host, binary, reason=None):
  730. if reason:
  731. return _nova.novaclient(request).services.disable_log_reason(
  732. host, binary, reason)
  733. else:
  734. return _nova.novaclient(request).services.disable(host, binary)
  735. @profiler.trace
  736. def aggregate_details_list(request):
  737. result = []
  738. c = _nova.novaclient(request)
  739. for aggregate in c.aggregates.list():
  740. result.append(c.aggregates.get_details(aggregate.id))
  741. return result
  742. @profiler.trace
  743. def aggregate_create(request, name, availability_zone=None):
  744. return _nova.novaclient(request).aggregates.create(name, availability_zone)
  745. @profiler.trace
  746. def aggregate_delete(request, aggregate_id):
  747. return _nova.novaclient(request).aggregates.delete(aggregate_id)
  748. @profiler.trace
  749. def aggregate_get(request, aggregate_id):
  750. return _nova.novaclient(request).aggregates.get(aggregate_id)
  751. @profiler.trace
  752. def aggregate_update(request, aggregate_id, values):
  753. _nova.novaclient(request).aggregates.update(aggregate_id, values)
  754. @profiler.trace
  755. def aggregate_set_metadata(request, aggregate_id, metadata):
  756. return _nova.novaclient(request).aggregates.set_metadata(aggregate_id,
  757. metadata)
  758. @profiler.trace
  759. def add_host_to_aggregate(request, aggregate_id, host):
  760. _nova.novaclient(request).aggregates.add_host(aggregate_id, host)
  761. @profiler.trace
  762. def remove_host_from_aggregate(request, aggregate_id, host):
  763. _nova.novaclient(request).aggregates.remove_host(aggregate_id, host)
  764. @profiler.trace
  765. def interface_attach(request,
  766. server, port_id=None, net_id=None, fixed_ip=None):
  767. return _nova.novaclient(request).servers.interface_attach(
  768. server, port_id, net_id, fixed_ip)
  769. @profiler.trace
  770. def interface_detach(request, server, port_id):
  771. return _nova.novaclient(request).servers.interface_detach(server, port_id)
  772. @profiler.trace
  773. @memoized.memoized
  774. def list_extensions(request):
  775. """List all nova extensions, except the ones in the blacklist."""
  776. blacklist = set(getattr(settings,
  777. 'OPENSTACK_NOVA_EXTENSIONS_BLACKLIST', []))
  778. nova_api = _nova.novaclient(request)
  779. return tuple(
  780. extension for extension in
  781. nova_list_extensions.ListExtManager(nova_api).show_all()
  782. if extension.name not in blacklist
  783. )
  784. @profiler.trace
  785. @memoized.memoized
  786. def extension_supported(extension_name, request):
  787. """Determine if nova supports a given extension name.
  788. Example values for the extension_name include AdminActions, ConsoleOutput,
  789. etc.
  790. """
  791. for ext in list_extensions(request):
  792. if ext.name == extension_name:
  793. return True
  794. return False
  795. @profiler.trace
  796. def can_set_server_password():
  797. features = getattr(settings, 'OPENSTACK_HYPERVISOR_FEATURES', {})
  798. return features.get('can_set_password', False)
  799. @profiler.trace
  800. def instance_action_list(request, instance_id):
  801. return nova_instance_action.InstanceActionManager(
  802. _nova.novaclient(request)).list(instance_id)
  803. @profiler.trace
  804. def can_set_mount_point():
  805. """Return the Hypervisor's capability of setting mount points."""
  806. hypervisor_features = getattr(
  807. settings, "OPENSTACK_HYPERVISOR_FEATURES", {})
  808. return hypervisor_features.get("can_set_mount_point", False)
  809. @profiler.trace
  810. def requires_keypair():
  811. features = getattr(settings, 'OPENSTACK_HYPERVISOR_FEATURES', {})
  812. return features.get('requires_keypair', False)
  813. def can_set_quotas():
  814. features = getattr(settings, 'OPENSTACK_HYPERVISOR_FEATURES', {})
  815. return features.get('enable_quotas', True)