Shared filesystem management project for OpenStack.
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.

3872 lines
142KB

  1. # Copyright (c) 2014 Alex Meade. All rights reserved.
  2. # Copyright (c) 2015 Clinton Knight. All rights reserved.
  3. # Copyright (c) 2015 Tom Barron. All rights reserved.
  4. # Copyright (c) 2018 Jose Porrua. All rights reserved.
  5. #
  6. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  7. # not use this file except in compliance with the License. You may obtain
  8. # a copy of the License at
  9. #
  10. # http://www.apache.org/licenses/LICENSE-2.0
  11. #
  12. # Unless required by applicable law or agreed to in writing, software
  13. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  14. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  15. # License for the specific language governing permissions and limitations
  16. # under the License.
  17. import copy
  18. import hashlib
  19. import re
  20. import time
  21. from oslo_log import log
  22. from oslo_utils import strutils
  23. from oslo_utils import units
  24. import six
  25. from manila import exception
  26. from manila.i18n import _
  27. from manila.share.drivers.netapp.dataontap.client import api as netapp_api
  28. from manila.share.drivers.netapp.dataontap.client import client_base
  29. from manila.share.drivers.netapp import utils as na_utils
  30. LOG = log.getLogger(__name__)
  31. DELETED_PREFIX = 'deleted_manila_'
  32. DEFAULT_IPSPACE = 'Default'
  33. DEFAULT_MAX_PAGE_LENGTH = 50
  34. CUTOVER_ACTION_MAP = {
  35. 'defer': 'defer_on_failure',
  36. 'abort': 'abort_on_failure',
  37. 'force': 'force',
  38. 'wait': 'wait',
  39. }
  40. class NetAppCmodeClient(client_base.NetAppBaseClient):
  41. def __init__(self, **kwargs):
  42. super(NetAppCmodeClient, self).__init__(**kwargs)
  43. self.vserver = kwargs.get('vserver')
  44. self.connection.set_vserver(self.vserver)
  45. # Default values to run first api.
  46. self.connection.set_api_version(1, 15)
  47. (major, minor) = self.get_ontapi_version(cached=False)
  48. self.connection.set_api_version(major, minor)
  49. system_version = self.get_system_version(cached=False)
  50. self.connection.set_system_version(system_version)
  51. self._init_features()
  52. def _init_features(self):
  53. """Initialize cDOT feature support map."""
  54. super(NetAppCmodeClient, self)._init_features()
  55. ontapi_version = self.get_ontapi_version(cached=True)
  56. ontapi_1_20 = ontapi_version >= (1, 20)
  57. ontapi_1_2x = (1, 20) <= ontapi_version < (1, 30)
  58. ontapi_1_30 = ontapi_version >= (1, 30)
  59. ontapi_1_110 = ontapi_version >= (1, 110)
  60. self.features.add_feature('SNAPMIRROR_V2', supported=ontapi_1_20)
  61. self.features.add_feature('SYSTEM_METRICS', supported=ontapi_1_2x)
  62. self.features.add_feature('SYSTEM_CONSTITUENT_METRICS',
  63. supported=ontapi_1_30)
  64. self.features.add_feature('BROADCAST_DOMAINS', supported=ontapi_1_30)
  65. self.features.add_feature('IPSPACES', supported=ontapi_1_30)
  66. self.features.add_feature('SUBNETS', supported=ontapi_1_30)
  67. self.features.add_feature('CLUSTER_PEER_POLICY', supported=ontapi_1_30)
  68. self.features.add_feature('ADVANCED_DISK_PARTITIONING',
  69. supported=ontapi_1_30)
  70. self.features.add_feature('FLEXVOL_ENCRYPTION', supported=ontapi_1_110)
  71. def _invoke_vserver_api(self, na_element, vserver):
  72. server = copy.copy(self.connection)
  73. server.set_vserver(vserver)
  74. result = server.invoke_successfully(na_element, True)
  75. return result
  76. def _has_records(self, api_result_element):
  77. if (not api_result_element.get_child_content('num-records') or
  78. api_result_element.get_child_content('num-records') == '0'):
  79. return False
  80. else:
  81. return True
  82. def _get_record_count(self, api_result_element):
  83. try:
  84. return int(api_result_element.get_child_content('num-records'))
  85. except TypeError:
  86. msg = _('Missing record count for NetApp iterator API invocation.')
  87. raise exception.NetAppException(msg)
  88. def set_vserver(self, vserver):
  89. self.vserver = vserver
  90. self.connection.set_vserver(vserver)
  91. def send_iter_request(self, api_name, api_args=None,
  92. max_page_length=DEFAULT_MAX_PAGE_LENGTH):
  93. """Invoke an iterator-style getter API."""
  94. if not api_args:
  95. api_args = {}
  96. api_args['max-records'] = max_page_length
  97. # Get first page
  98. result = self.send_request(api_name, api_args)
  99. # Most commonly, we can just return here if there is no more data
  100. next_tag = result.get_child_content('next-tag')
  101. if not next_tag:
  102. return result
  103. # Ensure pagination data is valid and prepare to store remaining pages
  104. num_records = self._get_record_count(result)
  105. attributes_list = result.get_child_by_name('attributes-list')
  106. if not attributes_list:
  107. msg = _('Missing attributes list for API %s.') % api_name
  108. raise exception.NetAppException(msg)
  109. # Get remaining pages, saving data into first page
  110. while next_tag is not None:
  111. next_api_args = copy.deepcopy(api_args)
  112. next_api_args['tag'] = next_tag
  113. next_result = self.send_request(api_name, next_api_args)
  114. next_attributes_list = next_result.get_child_by_name(
  115. 'attributes-list') or netapp_api.NaElement('none')
  116. for record in next_attributes_list.get_children():
  117. attributes_list.add_child_elem(record)
  118. num_records += self._get_record_count(next_result)
  119. next_tag = next_result.get_child_content('next-tag')
  120. result.get_child_by_name('num-records').set_content(
  121. six.text_type(num_records))
  122. result.get_child_by_name('next-tag').set_content('')
  123. return result
  124. @na_utils.trace
  125. def create_vserver(self, vserver_name, root_volume_aggregate_name,
  126. root_volume_name, aggregate_names, ipspace_name):
  127. """Creates new vserver and assigns aggregates."""
  128. create_args = {
  129. 'vserver-name': vserver_name,
  130. 'root-volume-security-style': 'unix',
  131. 'root-volume-aggregate': root_volume_aggregate_name,
  132. 'root-volume': root_volume_name,
  133. 'name-server-switch': {
  134. 'nsswitch': 'file',
  135. },
  136. }
  137. if ipspace_name:
  138. if not self.features.IPSPACES:
  139. msg = 'IPSpaces are not supported on this backend.'
  140. raise exception.NetAppException(msg)
  141. else:
  142. create_args['ipspace'] = ipspace_name
  143. self.send_request('vserver-create', create_args)
  144. aggr_list = [{'aggr-name': aggr_name} for aggr_name in aggregate_names]
  145. modify_args = {
  146. 'aggr-list': aggr_list,
  147. 'vserver-name': vserver_name,
  148. }
  149. self.send_request('vserver-modify', modify_args)
  150. @na_utils.trace
  151. def vserver_exists(self, vserver_name):
  152. """Checks if Vserver exists."""
  153. LOG.debug('Checking if Vserver %s exists', vserver_name)
  154. api_args = {
  155. 'query': {
  156. 'vserver-info': {
  157. 'vserver-name': vserver_name,
  158. },
  159. },
  160. 'desired-attributes': {
  161. 'vserver-info': {
  162. 'vserver-name': None,
  163. },
  164. },
  165. }
  166. result = self.send_iter_request('vserver-get-iter', api_args)
  167. return self._has_records(result)
  168. @na_utils.trace
  169. def get_vserver_root_volume_name(self, vserver_name):
  170. """Get the root volume name of the vserver."""
  171. api_args = {
  172. 'query': {
  173. 'vserver-info': {
  174. 'vserver-name': vserver_name,
  175. },
  176. },
  177. 'desired-attributes': {
  178. 'vserver-info': {
  179. 'root-volume': None,
  180. },
  181. },
  182. }
  183. vserver_info = self.send_iter_request('vserver-get-iter', api_args)
  184. try:
  185. root_volume_name = vserver_info.get_child_by_name(
  186. 'attributes-list').get_child_by_name(
  187. 'vserver-info').get_child_content('root-volume')
  188. except AttributeError:
  189. msg = _('Could not determine root volume name '
  190. 'for Vserver %s.') % vserver_name
  191. raise exception.NetAppException(msg)
  192. return root_volume_name
  193. @na_utils.trace
  194. def get_vserver_ipspace(self, vserver_name):
  195. """Get the IPspace of the vserver, or None if not supported."""
  196. if not self.features.IPSPACES:
  197. return None
  198. api_args = {
  199. 'query': {
  200. 'vserver-info': {
  201. 'vserver-name': vserver_name,
  202. },
  203. },
  204. 'desired-attributes': {
  205. 'vserver-info': {
  206. 'ipspace': None,
  207. },
  208. },
  209. }
  210. vserver_info = self.send_iter_request('vserver-get-iter', api_args)
  211. try:
  212. ipspace = vserver_info.get_child_by_name(
  213. 'attributes-list').get_child_by_name(
  214. 'vserver-info').get_child_content('ipspace')
  215. except AttributeError:
  216. msg = _('Could not determine IPspace for Vserver %s.')
  217. raise exception.NetAppException(msg % vserver_name)
  218. return ipspace
  219. @na_utils.trace
  220. def ipspace_has_data_vservers(self, ipspace_name):
  221. """Check whether an IPspace has any data Vservers assigned to it."""
  222. if not self.features.IPSPACES:
  223. return False
  224. api_args = {
  225. 'query': {
  226. 'vserver-info': {
  227. 'ipspace': ipspace_name,
  228. 'vserver-type': 'data'
  229. },
  230. },
  231. 'desired-attributes': {
  232. 'vserver-info': {
  233. 'vserver-name': None,
  234. },
  235. },
  236. }
  237. result = self.send_iter_request('vserver-get-iter', api_args)
  238. return self._has_records(result)
  239. @na_utils.trace
  240. def list_vservers(self, vserver_type='data'):
  241. """Get the names of vservers present, optionally filtered by type."""
  242. query = {
  243. 'vserver-info': {
  244. 'vserver-type': vserver_type,
  245. }
  246. } if vserver_type else None
  247. api_args = {
  248. 'desired-attributes': {
  249. 'vserver-info': {
  250. 'vserver-name': None,
  251. },
  252. },
  253. }
  254. if query:
  255. api_args['query'] = query
  256. result = self.send_iter_request('vserver-get-iter', api_args)
  257. vserver_info_list = result.get_child_by_name(
  258. 'attributes-list') or netapp_api.NaElement('none')
  259. return [vserver_info.get_child_content('vserver-name')
  260. for vserver_info in vserver_info_list.get_children()]
  261. @na_utils.trace
  262. def get_vserver_volume_count(self):
  263. """Get the number of volumes present on a cluster or vserver.
  264. Call this on a vserver client to see how many volumes exist
  265. on that vserver.
  266. """
  267. api_args = {
  268. 'desired-attributes': {
  269. 'volume-attributes': {
  270. 'volume-id-attributes': {
  271. 'name': None,
  272. },
  273. },
  274. },
  275. }
  276. volumes_data = self.send_iter_request('volume-get-iter', api_args)
  277. return self._get_record_count(volumes_data)
  278. @na_utils.trace
  279. def delete_vserver(self, vserver_name, vserver_client,
  280. security_services=None):
  281. """Delete Vserver.
  282. Checks if Vserver exists and does not have active shares.
  283. Offlines and destroys root volumes. Deletes Vserver.
  284. """
  285. if not self.vserver_exists(vserver_name):
  286. LOG.error("Vserver %s does not exist.", vserver_name)
  287. return
  288. root_volume_name = self.get_vserver_root_volume_name(vserver_name)
  289. volumes_count = vserver_client.get_vserver_volume_count()
  290. if volumes_count == 1:
  291. try:
  292. vserver_client.offline_volume(root_volume_name)
  293. except netapp_api.NaApiError as e:
  294. if e.code == netapp_api.EVOLUMEOFFLINE:
  295. LOG.error("Volume %s is already offline.",
  296. root_volume_name)
  297. else:
  298. raise
  299. vserver_client.delete_volume(root_volume_name)
  300. elif volumes_count > 1:
  301. msg = _("Cannot delete Vserver. Vserver %s has shares.")
  302. raise exception.NetAppException(msg % vserver_name)
  303. if security_services:
  304. self._terminate_vserver_services(vserver_name, vserver_client,
  305. security_services)
  306. self.send_request('vserver-destroy', {'vserver-name': vserver_name})
  307. @na_utils.trace
  308. def _terminate_vserver_services(self, vserver_name, vserver_client,
  309. security_services):
  310. for service in security_services:
  311. if service['type'] == 'active_directory':
  312. api_args = {
  313. 'admin-password': service['password'],
  314. 'admin-username': service['user'],
  315. }
  316. try:
  317. vserver_client.send_request('cifs-server-delete', api_args)
  318. except netapp_api.NaApiError as e:
  319. if e.code == netapp_api.EOBJECTNOTFOUND:
  320. LOG.error('CIFS server does not exist for '
  321. 'Vserver %s.', vserver_name)
  322. else:
  323. vserver_client.send_request('cifs-server-delete')
  324. @na_utils.trace
  325. def is_nve_supported(self):
  326. """Determine whether NVE is supported on this platform and version."""
  327. nodes = self.list_cluster_nodes()
  328. system_version = self.get_system_version()
  329. version = system_version.get('version')
  330. version_tuple = system_version.get('version-tuple')
  331. # NVE requires an ONTAP version >= 9.1. Also, not all platforms
  332. # support this feature. NVE is not supported if the version
  333. # includes the substring '<1no-DARE>' (no Data At Rest Encryption).
  334. if version_tuple >= (9, 1, 0) and "<1no-DARE>" not in version:
  335. if nodes is not None:
  336. return self.get_security_key_manager_nve_support(nodes[0])
  337. else:
  338. LOG.debug('Cluster credentials are required in order to '
  339. 'determine whether NetApp Volume Encryption is '
  340. 'supported or not on this platform.')
  341. return False
  342. else:
  343. LOG.debug('NetApp Volume Encryption is not supported on this '
  344. 'ONTAP version: %(version)s, %(version_tuple)s. ',
  345. {'version': version, 'version_tuple': version_tuple})
  346. return False
  347. @na_utils.trace
  348. def list_cluster_nodes(self):
  349. """Get all available cluster nodes."""
  350. api_args = {
  351. 'desired-attributes': {
  352. 'node-details-info': {
  353. 'node': None,
  354. },
  355. },
  356. }
  357. result = self.send_iter_request('system-node-get-iter', api_args)
  358. nodes_info_list = result.get_child_by_name(
  359. 'attributes-list') or netapp_api.NaElement('none')
  360. return [node_info.get_child_content('node') for node_info
  361. in nodes_info_list.get_children()]
  362. @na_utils.trace
  363. def get_security_key_manager_nve_support(self, node):
  364. """Determine whether the cluster platform supports Volume Encryption"""
  365. api_args = {'node': node}
  366. try:
  367. result = self.send_request(
  368. 'security-key-manager-volume-encryption-supported', api_args)
  369. vol_encryption_supported = result.get_child_content(
  370. 'vol-encryption-supported') or 'false'
  371. except netapp_api.NaApiError as e:
  372. LOG.debug("NVE disabled due to error code: %s - %s",
  373. e.code, e.message)
  374. return False
  375. return strutils.bool_from_string(vol_encryption_supported)
  376. @na_utils.trace
  377. def list_node_data_ports(self, node):
  378. ports = self.get_node_data_ports(node)
  379. return [port.get('port') for port in ports]
  380. @na_utils.trace
  381. def get_node_data_ports(self, node):
  382. """Get applicable data ports on the node."""
  383. api_args = {
  384. 'query': {
  385. 'net-port-info': {
  386. 'node': node,
  387. 'link-status': 'up',
  388. 'port-type': 'physical|if_group',
  389. 'role': 'data',
  390. },
  391. },
  392. 'desired-attributes': {
  393. 'net-port-info': {
  394. 'port': None,
  395. 'node': None,
  396. 'operational-speed': None,
  397. 'ifgrp-port': None,
  398. },
  399. },
  400. }
  401. result = self.send_iter_request('net-port-get-iter', api_args)
  402. net_port_info_list = result.get_child_by_name(
  403. 'attributes-list') or netapp_api.NaElement('none')
  404. ports = []
  405. for port_info in net_port_info_list.get_children():
  406. # Skip physical ports that are part of interface groups.
  407. if port_info.get_child_content('ifgrp-port'):
  408. continue
  409. port = {
  410. 'node': port_info.get_child_content('node'),
  411. 'port': port_info.get_child_content('port'),
  412. 'speed': port_info.get_child_content('operational-speed'),
  413. }
  414. ports.append(port)
  415. return self._sort_data_ports_by_speed(ports)
  416. @na_utils.trace
  417. def _sort_data_ports_by_speed(self, ports):
  418. def sort_key(port):
  419. value = port.get('speed')
  420. if not (value and isinstance(value, six.string_types)):
  421. return 0
  422. elif value.isdigit():
  423. return int(value)
  424. elif value == 'auto':
  425. return 3
  426. elif value == 'undef':
  427. return 2
  428. else:
  429. return 1
  430. return sorted(ports, key=sort_key, reverse=True)
  431. @na_utils.trace
  432. def list_root_aggregates(self):
  433. """Get names of all aggregates that contain node root volumes."""
  434. desired_attributes = {
  435. 'aggr-attributes': {
  436. 'aggregate-name': None,
  437. 'aggr-raid-attributes': {
  438. 'has-local-root': None,
  439. 'has-partner-root': None,
  440. },
  441. },
  442. }
  443. aggrs = self._get_aggregates(desired_attributes=desired_attributes)
  444. root_aggregates = []
  445. for aggr in aggrs:
  446. aggr_name = aggr.get_child_content('aggregate-name')
  447. aggr_raid_attrs = aggr.get_child_by_name('aggr-raid-attributes')
  448. local_root = strutils.bool_from_string(
  449. aggr_raid_attrs.get_child_content('has-local-root'))
  450. partner_root = strutils.bool_from_string(
  451. aggr_raid_attrs.get_child_content('has-partner-root'))
  452. if local_root or partner_root:
  453. root_aggregates.append(aggr_name)
  454. return root_aggregates
  455. @na_utils.trace
  456. def list_non_root_aggregates(self):
  457. """Get names of all aggregates that don't contain node root volumes."""
  458. query = {
  459. 'aggr-attributes': {
  460. 'aggr-raid-attributes': {
  461. 'has-local-root': 'false',
  462. 'has-partner-root': 'false',
  463. }
  464. },
  465. }
  466. return self._list_aggregates(query=query)
  467. @na_utils.trace
  468. def _list_aggregates(self, query=None):
  469. """Get names of all aggregates."""
  470. try:
  471. api_args = {
  472. 'desired-attributes': {
  473. 'aggr-attributes': {
  474. 'aggregate-name': None,
  475. },
  476. },
  477. }
  478. if query:
  479. api_args['query'] = query
  480. result = self.send_iter_request('aggr-get-iter', api_args)
  481. aggr_list = result.get_child_by_name(
  482. 'attributes-list').get_children()
  483. except AttributeError:
  484. msg = _("Could not list aggregates.")
  485. raise exception.NetAppException(msg)
  486. return [aggr.get_child_content('aggregate-name') for aggr
  487. in aggr_list]
  488. @na_utils.trace
  489. def list_vserver_aggregates(self):
  490. """Returns a list of aggregates available to a vserver.
  491. This must be called against a Vserver LIF.
  492. """
  493. return list(self.get_vserver_aggregate_capacities().keys())
  494. @na_utils.trace
  495. def create_network_interface(self, ip, netmask, vlan, node, port,
  496. vserver_name, lif_name, ipspace_name, mtu):
  497. """Creates LIF on VLAN port."""
  498. home_port_name = port
  499. if vlan:
  500. self._create_vlan(node, port, vlan)
  501. home_port_name = '%(port)s-%(tag)s' % {'port': port, 'tag': vlan}
  502. if self.features.BROADCAST_DOMAINS:
  503. self._ensure_broadcast_domain_for_port(
  504. node, home_port_name, mtu, ipspace=ipspace_name)
  505. LOG.debug('Creating LIF %(lif)s for Vserver %(vserver)s ',
  506. {'lif': lif_name, 'vserver': vserver_name})
  507. api_args = {
  508. 'address': ip,
  509. 'administrative-status': 'up',
  510. 'data-protocols': [
  511. {'data-protocol': 'nfs'},
  512. {'data-protocol': 'cifs'},
  513. ],
  514. 'home-node': node,
  515. 'home-port': home_port_name,
  516. 'netmask': netmask,
  517. 'interface-name': lif_name,
  518. 'role': 'data',
  519. 'vserver': vserver_name,
  520. }
  521. self.send_request('net-interface-create', api_args)
  522. @na_utils.trace
  523. def _create_vlan(self, node, port, vlan):
  524. try:
  525. api_args = {
  526. 'vlan-info': {
  527. 'parent-interface': port,
  528. 'node': node,
  529. 'vlanid': vlan,
  530. },
  531. }
  532. self.send_request('net-vlan-create', api_args)
  533. except netapp_api.NaApiError as e:
  534. if e.code == netapp_api.EDUPLICATEENTRY:
  535. LOG.debug('VLAN %(vlan)s already exists on port %(port)s',
  536. {'vlan': vlan, 'port': port})
  537. else:
  538. msg = _('Failed to create VLAN %(vlan)s on '
  539. 'port %(port)s. %(err_msg)s')
  540. msg_args = {'vlan': vlan, 'port': port, 'err_msg': e.message}
  541. raise exception.NetAppException(msg % msg_args)
  542. @na_utils.trace
  543. def delete_vlan(self, node, port, vlan):
  544. try:
  545. api_args = {
  546. 'vlan-info': {
  547. 'parent-interface': port,
  548. 'node': node,
  549. 'vlanid': vlan,
  550. },
  551. }
  552. self.send_request('net-vlan-delete', api_args)
  553. except netapp_api.NaApiError as e:
  554. p = re.compile('port already has a lif bound.*', re.IGNORECASE)
  555. if (e.code == netapp_api.EAPIERROR and re.match(p, e.message)):
  556. LOG.debug('VLAN %(vlan)s on port %(port)s node %(node)s '
  557. 'still used by LIF and cannot be deleted.',
  558. {'vlan': vlan, 'port': port, 'node': node})
  559. else:
  560. msg = _('Failed to delete VLAN %(vlan)s on '
  561. 'port %(port)s node %(node)s: %(err_msg)s')
  562. msg_args = {
  563. 'vlan': vlan,
  564. 'port': port,
  565. 'node': node,
  566. 'err_msg': e.message
  567. }
  568. raise exception.NetAppException(msg % msg_args)
  569. @na_utils.trace
  570. def create_route(self, gateway, destination=None):
  571. if not gateway:
  572. return
  573. if not destination:
  574. if ':' in gateway:
  575. destination = '::/0'
  576. else:
  577. destination = '0.0.0.0/0'
  578. try:
  579. api_args = {
  580. 'destination': destination,
  581. 'gateway': gateway,
  582. 'return-record': 'true',
  583. }
  584. self.send_request('net-routes-create', api_args)
  585. except netapp_api.NaApiError as e:
  586. p = re.compile('.*Duplicate route exists.*', re.IGNORECASE)
  587. if (e.code == netapp_api.EAPIERROR and re.match(p, e.message)):
  588. LOG.debug('Route to %(destination)s via gateway %(gateway)s '
  589. 'exists.',
  590. {'destination': destination, 'gateway': gateway})
  591. else:
  592. msg = _('Failed to create a route to %(destination)s via '
  593. 'gateway %(gateway)s: %(err_msg)s')
  594. msg_args = {
  595. 'destination': destination,
  596. 'gateway': gateway,
  597. 'err_msg': e.message,
  598. }
  599. raise exception.NetAppException(msg % msg_args)
  600. @na_utils.trace
  601. def _ensure_broadcast_domain_for_port(self, node, port, mtu,
  602. ipspace=DEFAULT_IPSPACE):
  603. """Ensure a port is in a broadcast domain. Create one if necessary.
  604. If the IPspace:domain pair match for the given port, which commonly
  605. happens in multi-node clusters, then there isn't anything to do.
  606. Otherwise, we can assume the IPspace is correct and extant by this
  607. point, so the remaining task is to remove the port from any domain it
  608. is already in, create the domain for the IPspace if it doesn't exist,
  609. and add the port to this domain.
  610. """
  611. # Derive the broadcast domain name from the IPspace name since they
  612. # need to be 1-1 and the default for both is the same name, 'Default'.
  613. domain = re.sub(r'ipspace', 'domain', ipspace)
  614. port_info = self._get_broadcast_domain_for_port(node, port)
  615. # Port already in desired ipspace and broadcast domain.
  616. if (port_info['ipspace'] == ipspace
  617. and port_info['broadcast-domain'] == domain):
  618. self._modify_broadcast_domain(domain, ipspace, mtu)
  619. return
  620. # If in another broadcast domain, remove port from it.
  621. if port_info['broadcast-domain']:
  622. self._remove_port_from_broadcast_domain(
  623. node, port, port_info['broadcast-domain'],
  624. port_info['ipspace'])
  625. # If desired broadcast domain doesn't exist, create it.
  626. if not self._broadcast_domain_exists(domain, ipspace):
  627. self._create_broadcast_domain(domain, ipspace, mtu)
  628. else:
  629. self._modify_broadcast_domain(domain, ipspace, mtu)
  630. # Move the port into the broadcast domain where it is needed.
  631. self._add_port_to_broadcast_domain(node, port, domain, ipspace)
  632. @na_utils.trace
  633. def _get_broadcast_domain_for_port(self, node, port):
  634. """Get broadcast domain for a specific port."""
  635. api_args = {
  636. 'query': {
  637. 'net-port-info': {
  638. 'node': node,
  639. 'port': port,
  640. },
  641. },
  642. 'desired-attributes': {
  643. 'net-port-info': {
  644. 'broadcast-domain': None,
  645. 'ipspace': None,
  646. },
  647. },
  648. }
  649. result = self.send_iter_request('net-port-get-iter', api_args)
  650. net_port_info_list = result.get_child_by_name(
  651. 'attributes-list') or netapp_api.NaElement('none')
  652. port_info = net_port_info_list.get_children()
  653. if not port_info:
  654. msg = _('Could not find port %(port)s on node %(node)s.')
  655. msg_args = {'port': port, 'node': node}
  656. raise exception.NetAppException(msg % msg_args)
  657. port = {
  658. 'broadcast-domain':
  659. port_info[0].get_child_content('broadcast-domain'),
  660. 'ipspace': port_info[0].get_child_content('ipspace')
  661. }
  662. return port
  663. @na_utils.trace
  664. def _broadcast_domain_exists(self, domain, ipspace):
  665. """Check if a broadcast domain exists."""
  666. api_args = {
  667. 'query': {
  668. 'net-port-broadcast-domain-info': {
  669. 'ipspace': ipspace,
  670. 'broadcast-domain': domain,
  671. },
  672. },
  673. 'desired-attributes': {
  674. 'net-port-broadcast-domain-info': None,
  675. },
  676. }
  677. result = self.send_iter_request('net-port-broadcast-domain-get-iter',
  678. api_args)
  679. return self._has_records(result)
  680. @na_utils.trace
  681. def _create_broadcast_domain(self, domain, ipspace, mtu):
  682. """Create a broadcast domain."""
  683. api_args = {
  684. 'ipspace': ipspace,
  685. 'broadcast-domain': domain,
  686. 'mtu': mtu,
  687. }
  688. self.send_request('net-port-broadcast-domain-create', api_args)
  689. @na_utils.trace
  690. def _modify_broadcast_domain(self, domain, ipspace, mtu):
  691. """Modify a broadcast domain."""
  692. api_args = {
  693. 'ipspace': ipspace,
  694. 'broadcast-domain': domain,
  695. 'mtu': mtu,
  696. }
  697. self.send_request('net-port-broadcast-domain-modify', api_args)
  698. @na_utils.trace
  699. def _delete_broadcast_domain(self, domain, ipspace):
  700. """Delete a broadcast domain."""
  701. api_args = {
  702. 'ipspace': ipspace,
  703. 'broadcast-domain': domain,
  704. }
  705. self.send_request('net-port-broadcast-domain-destroy', api_args)
  706. @na_utils.trace
  707. def _delete_broadcast_domains_for_ipspace(self, ipspace_name):
  708. """Deletes all broadcast domains in an IPspace."""
  709. ipspaces = self.get_ipspaces(ipspace_name=ipspace_name)
  710. if not ipspaces:
  711. return
  712. ipspace = ipspaces[0]
  713. for broadcast_domain_name in ipspace['broadcast-domains']:
  714. self._delete_broadcast_domain(broadcast_domain_name, ipspace_name)
  715. @na_utils.trace
  716. def _add_port_to_broadcast_domain(self, node, port, domain, ipspace):
  717. qualified_port_name = ':'.join([node, port])
  718. try:
  719. api_args = {
  720. 'ipspace': ipspace,
  721. 'broadcast-domain': domain,
  722. 'ports': {
  723. 'net-qualified-port-name': qualified_port_name,
  724. }
  725. }
  726. self.send_request('net-port-broadcast-domain-add-ports', api_args)
  727. except netapp_api.NaApiError as e:
  728. if e.code == (netapp_api.
  729. E_VIFMGR_PORT_ALREADY_ASSIGNED_TO_BROADCAST_DOMAIN):
  730. LOG.debug('Port %(port)s already exists in broadcast domain '
  731. '%(domain)s', {'port': port, 'domain': domain})
  732. else:
  733. msg = _('Failed to add port %(port)s to broadcast domain '
  734. '%(domain)s. %(err_msg)s')
  735. msg_args = {
  736. 'port': qualified_port_name,
  737. 'domain': domain,
  738. 'err_msg': e.message,
  739. }
  740. raise exception.NetAppException(msg % msg_args)
  741. @na_utils.trace
  742. def _remove_port_from_broadcast_domain(self, node, port, domain, ipspace):
  743. qualified_port_name = ':'.join([node, port])
  744. api_args = {
  745. 'ipspace': ipspace,
  746. 'broadcast-domain': domain,
  747. 'ports': {
  748. 'net-qualified-port-name': qualified_port_name,
  749. }
  750. }
  751. self.send_request('net-port-broadcast-domain-remove-ports', api_args)
  752. @na_utils.trace
  753. def network_interface_exists(self, vserver_name, node, port, ip, netmask,
  754. vlan):
  755. """Checks if LIF exists."""
  756. home_port_name = (port if not vlan else
  757. '%(port)s-%(tag)s' % {'port': port, 'tag': vlan})
  758. api_args = {
  759. 'query': {
  760. 'net-interface-info': {
  761. 'address': ip,
  762. 'home-node': node,
  763. 'home-port': home_port_name,
  764. 'netmask': netmask,
  765. 'vserver': vserver_name,
  766. },
  767. },
  768. 'desired-attributes': {
  769. 'net-interface-info': {
  770. 'interface-name': None,
  771. },
  772. },
  773. }
  774. result = self.send_iter_request('net-interface-get-iter', api_args)
  775. return self._has_records(result)
  776. @na_utils.trace
  777. def list_network_interfaces(self):
  778. """Get the names of available LIFs."""
  779. api_args = {
  780. 'desired-attributes': {
  781. 'net-interface-info': {
  782. 'interface-name': None,
  783. },
  784. },
  785. }
  786. result = self.send_iter_request('net-interface-get-iter', api_args)
  787. lif_info_list = result.get_child_by_name(
  788. 'attributes-list') or netapp_api.NaElement('none')
  789. return [lif_info.get_child_content('interface-name') for lif_info
  790. in lif_info_list.get_children()]
  791. @na_utils.trace
  792. def get_network_interfaces(self, protocols=None):
  793. """Get available LIFs."""
  794. protocols = na_utils.convert_to_list(protocols)
  795. protocols = [protocol.lower() for protocol in protocols]
  796. api_args = {
  797. 'query': {
  798. 'net-interface-info': {
  799. 'data-protocols': {
  800. 'data-protocol': '|'.join(protocols),
  801. }
  802. }
  803. }
  804. } if protocols else None
  805. result = self.send_iter_request('net-interface-get-iter', api_args)
  806. lif_info_list = result.get_child_by_name(
  807. 'attributes-list') or netapp_api.NaElement('none')
  808. interfaces = []
  809. for lif_info in lif_info_list.get_children():
  810. lif = {
  811. 'address': lif_info.get_child_content('address'),
  812. 'home-node': lif_info.get_child_content('home-node'),
  813. 'home-port': lif_info.get_child_content('home-port'),
  814. 'interface-name': lif_info.get_child_content('interface-name'),
  815. 'netmask': lif_info.get_child_content('netmask'),
  816. 'role': lif_info.get_child_content('role'),
  817. 'vserver': lif_info.get_child_content('vserver'),
  818. }
  819. interfaces.append(lif)
  820. return interfaces
  821. @na_utils.trace
  822. def get_ipspaces(self, ipspace_name=None):
  823. """Gets one or more IPSpaces."""
  824. if not self.features.IPSPACES:
  825. return []
  826. api_args = {}
  827. if ipspace_name:
  828. api_args['query'] = {
  829. 'net-ipspaces-info': {
  830. 'ipspace': ipspace_name,
  831. }
  832. }
  833. result = self.send_iter_request('net-ipspaces-get-iter', api_args)
  834. if not self._has_records(result):
  835. return []
  836. ipspaces = []
  837. for net_ipspaces_info in result.get_child_by_name(
  838. 'attributes-list').get_children():
  839. ipspace = {
  840. 'ports': [],
  841. 'vservers': [],
  842. 'broadcast-domains': [],
  843. }
  844. ports = net_ipspaces_info.get_child_by_name(
  845. 'ports') or netapp_api.NaElement('none')
  846. for port in ports.get_children():
  847. ipspace['ports'].append(port.get_content())
  848. vservers = net_ipspaces_info.get_child_by_name(
  849. 'vservers') or netapp_api.NaElement('none')
  850. for vserver in vservers.get_children():
  851. ipspace['vservers'].append(vserver.get_content())
  852. broadcast_domains = net_ipspaces_info.get_child_by_name(
  853. 'broadcast-domains') or netapp_api.NaElement('none')
  854. for broadcast_domain in broadcast_domains.get_children():
  855. ipspace['broadcast-domains'].append(
  856. broadcast_domain.get_content())
  857. ipspace['ipspace'] = net_ipspaces_info.get_child_content('ipspace')
  858. ipspace['id'] = net_ipspaces_info.get_child_content('id')
  859. ipspace['uuid'] = net_ipspaces_info.get_child_content('uuid')
  860. ipspaces.append(ipspace)
  861. return ipspaces
  862. @na_utils.trace
  863. def ipspace_exists(self, ipspace_name):
  864. """Checks if IPspace exists."""
  865. if not self.features.IPSPACES:
  866. return False
  867. api_args = {
  868. 'query': {
  869. 'net-ipspaces-info': {
  870. 'ipspace': ipspace_name,
  871. },
  872. },
  873. 'desired-attributes': {
  874. 'net-ipspaces-info': {
  875. 'ipspace': None,
  876. },
  877. },
  878. }
  879. result = self.send_iter_request('net-ipspaces-get-iter', api_args)
  880. return self._has_records(result)
  881. @na_utils.trace
  882. def create_ipspace(self, ipspace_name):
  883. """Creates an IPspace."""
  884. api_args = {'ipspace': ipspace_name}
  885. self.send_request('net-ipspaces-create', api_args)
  886. @na_utils.trace
  887. def delete_ipspace(self, ipspace_name):
  888. """Deletes an IPspace."""
  889. self._delete_broadcast_domains_for_ipspace(ipspace_name)
  890. api_args = {'ipspace': ipspace_name}
  891. self.send_request('net-ipspaces-destroy', api_args)
  892. @na_utils.trace
  893. def add_vserver_to_ipspace(self, ipspace_name, vserver_name):
  894. """Assigns a vserver to an IPspace."""
  895. api_args = {'ipspace': ipspace_name, 'vserver': vserver_name}
  896. self.send_request('net-ipspaces-assign-vserver', api_args)
  897. @na_utils.trace
  898. def get_node_for_aggregate(self, aggregate_name):
  899. """Get home node for the specified aggregate.
  900. This API could return None, most notably if it was sent
  901. to a Vserver LIF, so the caller must be able to handle that case.
  902. """
  903. if not aggregate_name:
  904. return None
  905. desired_attributes = {
  906. 'aggr-attributes': {
  907. 'aggregate-name': None,
  908. 'aggr-ownership-attributes': {
  909. 'home-name': None,
  910. },
  911. },
  912. }
  913. try:
  914. aggrs = self._get_aggregates(aggregate_names=[aggregate_name],
  915. desired_attributes=desired_attributes)
  916. except netapp_api.NaApiError as e:
  917. if e.code == netapp_api.EAPINOTFOUND:
  918. return None
  919. else:
  920. raise
  921. if len(aggrs) < 1:
  922. return None
  923. aggr_ownership_attrs = aggrs[0].get_child_by_name(
  924. 'aggr-ownership-attributes') or netapp_api.NaElement('none')
  925. return aggr_ownership_attrs.get_child_content('home-name')
  926. @na_utils.trace
  927. def get_cluster_aggregate_capacities(self, aggregate_names):
  928. """Calculates capacity of one or more aggregates.
  929. Returns dictionary of aggregate capacity metrics.
  930. 'size-used' is the actual space consumed on the aggregate.
  931. 'size-available' is the actual space remaining.
  932. 'size-total' is the defined total aggregate size, such that
  933. used + available = total.
  934. """
  935. if aggregate_names is not None and len(aggregate_names) == 0:
  936. return {}
  937. desired_attributes = {
  938. 'aggr-attributes': {
  939. 'aggregate-name': None,
  940. 'aggr-space-attributes': {
  941. 'size-available': None,
  942. 'size-total': None,
  943. 'size-used': None,
  944. },
  945. },
  946. }
  947. aggrs = self._get_aggregates(aggregate_names=aggregate_names,
  948. desired_attributes=desired_attributes)
  949. aggr_space_dict = dict()
  950. for aggr in aggrs:
  951. aggr_name = aggr.get_child_content('aggregate-name')
  952. aggr_space_attrs = aggr.get_child_by_name('aggr-space-attributes')
  953. aggr_space_dict[aggr_name] = {
  954. 'available':
  955. int(aggr_space_attrs.get_child_content('size-available')),
  956. 'total':
  957. int(aggr_space_attrs.get_child_content('size-total')),
  958. 'used':
  959. int(aggr_space_attrs.get_child_content('size-used')),
  960. }
  961. return aggr_space_dict
  962. @na_utils.trace
  963. def get_vserver_aggregate_capacities(self, aggregate_names=None):
  964. """Calculates capacity of one or more aggregates for a vserver.
  965. Returns dictionary of aggregate capacity metrics. This must
  966. be called against a Vserver LIF.
  967. """
  968. if aggregate_names is not None and len(aggregate_names) == 0:
  969. return {}
  970. api_args = {
  971. 'desired-attributes': {
  972. 'vserver-info': {
  973. 'vserver-name': None,
  974. 'vserver-aggr-info-list': {
  975. 'vserver-aggr-info': {
  976. 'aggr-name': None,
  977. 'aggr-availsize': None,
  978. },
  979. },
  980. },
  981. },
  982. }
  983. result = self.send_request('vserver-get', api_args)
  984. attributes = result.get_child_by_name('attributes')
  985. if not attributes:
  986. raise exception.NetAppException('Failed to read Vserver info')
  987. vserver_info = attributes.get_child_by_name('vserver-info')
  988. vserver_name = vserver_info.get_child_content('vserver-name')
  989. vserver_aggr_info_element = vserver_info.get_child_by_name(
  990. 'vserver-aggr-info-list') or netapp_api.NaElement('none')
  991. vserver_aggr_info_list = vserver_aggr_info_element.get_children()
  992. if not vserver_aggr_info_list:
  993. LOG.warning('No aggregates assigned to Vserver %s.',
  994. vserver_name)
  995. # Return dict of key-value pair of aggr_name:aggr_size_available.
  996. aggr_space_dict = {}
  997. for aggr_info in vserver_aggr_info_list:
  998. aggr_name = aggr_info.get_child_content('aggr-name')
  999. if aggregate_names is None or aggr_name in aggregate_names:
  1000. aggr_size = int(aggr_info.get_child_content('aggr-availsize'))
  1001. aggr_space_dict[aggr_name] = {'available': aggr_size}
  1002. LOG.debug('Found available Vserver aggregates: %s', aggr_space_dict)
  1003. return aggr_space_dict
  1004. @na_utils.trace
  1005. def _get_aggregates(self, aggregate_names=None, desired_attributes=None):
  1006. query = {
  1007. 'aggr-attributes': {
  1008. 'aggregate-name': '|'.join(aggregate_names),
  1009. }
  1010. } if aggregate_names else None
  1011. api_args = {}
  1012. if query:
  1013. api_args['query'] = query
  1014. if desired_attributes:
  1015. api_args['desired-attributes'] = desired_attributes
  1016. result = self.send_iter_request('aggr-get-iter', api_args)
  1017. if not self._has_records(result):
  1018. return []
  1019. else:
  1020. return result.get_child_by_name('attributes-list').get_children()
  1021. def get_performance_instance_uuids(self, object_name, node_name):
  1022. """Get UUIDs of performance instances for a cluster node."""
  1023. api_args = {
  1024. 'objectname': object_name,
  1025. 'query': {
  1026. 'instance-info': {
  1027. 'uuid': node_name + ':*',
  1028. }
  1029. }
  1030. }
  1031. result = self.send_request('perf-object-instance-list-info-iter',
  1032. api_args)
  1033. uuids = []
  1034. instances = result.get_child_by_name(
  1035. 'attributes-list') or netapp_api.NaElement('None')
  1036. for instance_info in instances.get_children():
  1037. uuids.append(instance_info.get_child_content('uuid'))
  1038. return uuids
  1039. def get_performance_counter_info(self, object_name, counter_name):
  1040. """Gets info about one or more Data ONTAP performance counters."""
  1041. api_args = {'objectname': object_name}
  1042. result = self.send_request('perf-object-counter-list-info', api_args)
  1043. counters = result.get_child_by_name(
  1044. 'counters') or netapp_api.NaElement('None')
  1045. for counter in counters.get_children():
  1046. if counter.get_child_content('name') == counter_name:
  1047. labels = []
  1048. label_list = counter.get_child_by_name(
  1049. 'labels') or netapp_api.NaElement('None')
  1050. for label in label_list.get_children():
  1051. labels.extend(label.get_content().split(','))
  1052. base_counter = counter.get_child_content('base-counter')
  1053. return {
  1054. 'name': counter_name,
  1055. 'labels': labels,
  1056. 'base-counter': base_counter,
  1057. }
  1058. else:
  1059. raise exception.NotFound(_('Counter %s not found') % counter_name)
  1060. def get_performance_counters(self, object_name, instance_uuids,
  1061. counter_names):
  1062. """Gets one or more cDOT performance counters."""
  1063. api_args = {
  1064. 'objectname': object_name,
  1065. 'instance-uuids': [
  1066. {'instance-uuid': instance_uuid}
  1067. for instance_uuid in instance_uuids
  1068. ],
  1069. 'counters': [
  1070. {'counter': counter} for counter in counter_names
  1071. ],
  1072. }
  1073. result = self.send_request('perf-object-get-instances', api_args)
  1074. counter_data = []
  1075. timestamp = result.get_child_content('timestamp')
  1076. instances = result.get_child_by_name(
  1077. 'instances') or netapp_api.NaElement('None')
  1078. for instance in instances.get_children():
  1079. instance_name = instance.get_child_content('name')
  1080. instance_uuid = instance.get_child_content('uuid')
  1081. node_name = instance_uuid.split(':')[0]
  1082. counters = instance.get_child_by_name(
  1083. 'counters') or netapp_api.NaElement('None')
  1084. for counter in counters.get_children():
  1085. counter_name = counter.get_child_content('name')
  1086. counter_value = counter.get_child_content('value')
  1087. counter_data.append({
  1088. 'instance-name': instance_name,
  1089. 'instance-uuid': instance_uuid,
  1090. 'node-name': node_name,
  1091. 'timestamp': timestamp,
  1092. counter_name: counter_value,
  1093. })
  1094. return counter_data
  1095. @na_utils.trace
  1096. def setup_security_services(self, security_services, vserver_client,
  1097. vserver_name):
  1098. api_args = {
  1099. 'name-mapping-switch': [
  1100. {'nmswitch': 'ldap'},
  1101. {'nmswitch': 'file'}
  1102. ],
  1103. 'name-server-switch': [
  1104. {'nsswitch': 'ldap'},
  1105. {'nsswitch': 'file'}
  1106. ],
  1107. 'vserver-name': vserver_name,
  1108. }
  1109. self.send_request('vserver-modify', api_args)
  1110. for security_service in security_services:
  1111. if security_service['type'].lower() == 'ldap':
  1112. vserver_client.configure_ldap(security_service)
  1113. elif security_service['type'].lower() == 'active_directory':
  1114. vserver_client.configure_active_directory(security_service,
  1115. vserver_name)
  1116. elif security_service['type'].lower() == 'kerberos':
  1117. self.create_kerberos_realm(security_service)
  1118. vserver_client.configure_kerberos(security_service,
  1119. vserver_name)
  1120. else:
  1121. msg = _('Unsupported security service type %s for '
  1122. 'Data ONTAP driver')
  1123. raise exception.NetAppException(msg % security_service['type'])
  1124. @na_utils.trace
  1125. def enable_nfs(self, versions):
  1126. """Enables NFS on Vserver."""
  1127. self.send_request('nfs-enable')
  1128. self._enable_nfs_protocols(versions)
  1129. self._create_default_nfs_export_rules()
  1130. @na_utils.trace
  1131. def _enable_nfs_protocols(self, versions):
  1132. """Set the enabled NFS protocol versions."""
  1133. nfs3 = 'true' if 'nfs3' in versions else 'false'
  1134. nfs40 = 'true' if 'nfs4.0' in versions else 'false'
  1135. nfs41 = 'true' if 'nfs4.1' in versions else 'false'
  1136. nfs_service_modify_args = {
  1137. 'is-nfsv3-enabled': nfs3,
  1138. 'is-nfsv40-enabled': nfs40,
  1139. 'is-nfsv41-enabled': nfs41,
  1140. }
  1141. self.send_request('nfs-service-modify', nfs_service_modify_args)
  1142. @na_utils.trace
  1143. def _create_default_nfs_export_rules(self):
  1144. """Create the default export rule for the NFS service."""
  1145. export_rule_create_args = {
  1146. 'client-match': '0.0.0.0/0',
  1147. 'policy-name': 'default',
  1148. 'ro-rule': {
  1149. 'security-flavor': 'any',
  1150. },
  1151. 'rw-rule': {
  1152. 'security-flavor': 'never',
  1153. },
  1154. }
  1155. self.send_request('export-rule-create', export_rule_create_args)
  1156. export_rule_create_args['client-match'] = '::/0'
  1157. self.send_request('export-rule-create', export_rule_create_args)
  1158. @na_utils.trace
  1159. def configure_ldap(self, security_service):
  1160. """Configures LDAP on Vserver."""
  1161. config_name = hashlib.md5(six.b(security_service['id'])).hexdigest()
  1162. api_args = {
  1163. 'ldap-client-config': config_name,
  1164. 'servers': {
  1165. 'ip-address': security_service['server'],
  1166. },
  1167. 'tcp-port': '389',
  1168. 'schema': 'RFC-2307',
  1169. 'bind-password': security_service['password'],
  1170. }
  1171. self.send_request('ldap-client-create', api_args)
  1172. api_args = {'client-config': config_name, 'client-enabled': 'true'}
  1173. self.send_request('ldap-config-create', api_args)
  1174. @na_utils.trace
  1175. def configure_active_directory(self, security_service, vserver_name):
  1176. """Configures AD on Vserver."""
  1177. self.configure_dns(security_service)
  1178. # 'cifs-server' is CIFS Server NetBIOS Name, max length is 15.
  1179. # Should be unique within each domain (data['domain']).
  1180. cifs_server = (vserver_name[0:7] + '..' + vserver_name[-6:]).upper()
  1181. api_args = {
  1182. 'admin-username': security_service['user'],
  1183. 'admin-password': security_service['password'],
  1184. 'force-account-overwrite': 'true',
  1185. 'cifs-server': cifs_server,
  1186. 'domain': security_service['domain'],
  1187. }
  1188. if security_service['ou'] is not None:
  1189. api_args['organizational-unit'] = security_service['ou']
  1190. try:
  1191. LOG.debug("Trying to setup CIFS server with data: %s", api_args)
  1192. self.send_request('cifs-server-create', api_args)
  1193. except netapp_api.NaApiError as e:
  1194. msg = _("Failed to create CIFS server entry. %s")
  1195. raise exception.NetAppException(msg % e.message)
  1196. @na_utils.trace
  1197. def create_kerberos_realm(self, security_service):
  1198. """Creates Kerberos realm on cluster."""
  1199. api_args = {
  1200. 'admin-server-ip': security_service['server'],
  1201. 'admin-server-port': '749',
  1202. 'clock-skew': '5',
  1203. 'comment': '',
  1204. 'config-name': security_service['id'],
  1205. 'kdc-ip': security_service['server'],
  1206. 'kdc-port': '88',
  1207. 'kdc-vendor': 'other',
  1208. 'password-server-ip': security_service['server'],
  1209. 'password-server-port': '464',
  1210. 'realm': security_service['domain'].upper(),
  1211. }
  1212. try:
  1213. self.send_request('kerberos-realm-create', api_args)
  1214. except netapp_api.NaApiError as e:
  1215. if e.code == netapp_api.EDUPLICATEENTRY:
  1216. LOG.debug('Kerberos realm config already exists.')
  1217. else:
  1218. msg = _('Failed to create Kerberos realm. %s')
  1219. raise exception.NetAppException(msg % e.message)
  1220. @na_utils.trace
  1221. def configure_kerberos(self, security_service, vserver_name):
  1222. """Configures Kerberos for NFS on Vserver."""
  1223. self.configure_dns(security_service)
  1224. spn = self._get_kerberos_service_principal_name(
  1225. security_service, vserver_name)
  1226. lifs = self.list_network_interfaces()
  1227. if not lifs:
  1228. msg = _("Cannot set up Kerberos. There are no LIFs configured.")
  1229. raise exception.NetAppException(msg)
  1230. for lif_name in lifs:
  1231. api_args = {
  1232. 'admin-password': security_service['password'],
  1233. 'admin-user-name': security_service['user'],
  1234. 'interface-name': lif_name,
  1235. 'is-kerberos-enabled': 'true',
  1236. 'service-principal-name': spn,
  1237. }
  1238. self.send_request('kerberos-config-modify', api_args)
  1239. @na_utils.trace
  1240. def _get_kerberos_service_principal_name(self, security_service,
  1241. vserver_name):
  1242. return ('nfs/' + vserver_name.replace('_', '-') + '.' +
  1243. security_service['domain'] + '@' +
  1244. security_service['domain'].upper())
  1245. @na_utils.trace
  1246. def configure_dns(self, security_service):
  1247. api_args = {
  1248. 'domains': {
  1249. 'string': security_service['domain'],
  1250. },
  1251. 'name-servers': {
  1252. 'ip-address': security_service['dns_ip'],
  1253. },
  1254. 'dns-state': 'enabled',
  1255. }
  1256. try:
  1257. self.send_request('net-dns-create', api_args)
  1258. except netapp_api.NaApiError as e:
  1259. if e.code == netapp_api.EDUPLICATEENTRY:
  1260. LOG.error("DNS exists for Vserver.")
  1261. else:
  1262. msg = _("Failed to configure DNS. %s")
  1263. raise exception.NetAppException(msg % e.message)
  1264. @na_utils.trace
  1265. def create_volume(self, aggregate_name, volume_name, size_gb,
  1266. thin_provisioned=False, snapshot_policy=None,
  1267. language=None, dedup_enabled=False,
  1268. compression_enabled=False, max_files=None,
  1269. snapshot_reserve=None, volume_type='rw',
  1270. qos_policy_group=None,
  1271. encrypt=False, **options):
  1272. """Creates a volume."""
  1273. api_args = {
  1274. 'containing-aggr-name': aggregate_name,
  1275. 'size': six.text_type(size_gb) + 'g',
  1276. 'volume': volume_name,
  1277. 'volume-type': volume_type,
  1278. }
  1279. if volume_type != 'dp':
  1280. api_args['junction-path'] = '/%s' % volume_name
  1281. if thin_provisioned:
  1282. api_args['space-reserve'] = 'none'
  1283. if snapshot_policy is not None:
  1284. api_args['snapshot-policy'] = snapshot_policy
  1285. if language is not None:
  1286. api_args['language-code'] = language
  1287. if snapshot_reserve is not None:
  1288. api_args['percentage-snapshot-reserve'] = six.text_type(
  1289. snapshot_reserve)
  1290. if qos_policy_group is not None:
  1291. api_args['qos-policy-group-name'] = qos_policy_group
  1292. if encrypt is True:
  1293. if not self.features.FLEXVOL_ENCRYPTION:
  1294. msg = 'Flexvol encryption is not supported on this backend.'
  1295. raise exception.NetAppException(msg)
  1296. else:
  1297. api_args['encrypt'] = 'true'
  1298. self.send_request('volume-create', api_args)
  1299. self.update_volume_efficiency_attributes(volume_name,
  1300. dedup_enabled,
  1301. compression_enabled)
  1302. if max_files is not None:
  1303. self.set_volume_max_files(volume_name, max_files)
  1304. @na_utils.trace
  1305. def enable_dedup(self, volume_name):
  1306. """Enable deduplication on volume."""
  1307. api_args = {'path': '/vol/%s' % volume_name}
  1308. self.send_request('sis-enable', api_args)
  1309. @na_utils.trace
  1310. def disable_dedup(self, volume_name):
  1311. """Disable deduplication on volume."""
  1312. api_args = {'path': '/vol/%s' % volume_name}
  1313. self.send_request('sis-disable', api_args)
  1314. @na_utils.trace
  1315. def enable_compression(self, volume_name):
  1316. """Enable compression on volume."""
  1317. api_args = {
  1318. 'path': '/vol/%s' % volume_name,
  1319. 'enable-compression': 'true'
  1320. }
  1321. self.send_request('sis-set-config', api_args)
  1322. @na_utils.trace
  1323. def disable_compression(self, volume_name):
  1324. """Disable compression on volume."""
  1325. api_args = {
  1326. 'path': '/vol/%s' % volume_name,
  1327. 'enable-compression': 'false'
  1328. }
  1329. self.send_request('sis-set-config', api_args)
  1330. @na_utils.trace
  1331. def get_volume_efficiency_status(self, volume_name):
  1332. """Get dedupe & compression status for a volume."""
  1333. api_args = {
  1334. 'query': {
  1335. 'sis-status-info': {
  1336. 'path': '/vol/%s' % volume_name,
  1337. },
  1338. },
  1339. 'desired-attributes': {
  1340. 'sis-status-info': {
  1341. 'state': None,
  1342. 'is-compression-enabled': None,
  1343. },
  1344. },
  1345. }
  1346. result = self.send_iter_request('sis-get-iter', api_args)
  1347. attributes_list = result.get_child_by_name(
  1348. 'attributes-list') or netapp_api.NaElement('none')
  1349. sis_status_info = attributes_list.get_child_by_name(
  1350. 'sis-status-info') or netapp_api.NaElement('none')
  1351. return {
  1352. 'dedupe': True if 'enabled' == sis_status_info.get_child_content(
  1353. 'state') else False,
  1354. 'compression': True if 'true' == sis_status_info.get_child_content(
  1355. 'is-compression-enabled') else False,
  1356. }
  1357. @na_utils.trace
  1358. def set_volume_max_files(self, volume_name, max_files):
  1359. """Set flexvol file limit."""
  1360. api_args = {
  1361. 'query': {
  1362. 'volume-attributes': {
  1363. 'volume-id-attributes': {
  1364. 'name': volume_name,
  1365. },
  1366. },
  1367. },
  1368. 'attributes': {
  1369. 'volume-attributes': {
  1370. 'volume-inode-attributes': {
  1371. 'files-total': max_files,
  1372. },
  1373. },
  1374. },
  1375. }
  1376. self.send_request('volume-modify-iter', api_args)
  1377. @na_utils.trace
  1378. def set_volume_size(self, volume_name, size_gb):
  1379. """Set volume size."""
  1380. api_args = {
  1381. 'query': {
  1382. 'volume-attributes': {
  1383. 'volume-id-attributes': {
  1384. 'name': volume_name,
  1385. },
  1386. },
  1387. },
  1388. 'attributes': {
  1389. 'volume-attributes': {
  1390. 'volume-space-attributes': {
  1391. 'size': int(size_gb) * units.Gi,
  1392. },
  1393. },
  1394. },
  1395. }
  1396. result = self.send_request('volume-modify-iter', api_args)
  1397. failures = result.get_child_content('num-failed')
  1398. if failures and int(failures) > 0:
  1399. failure_list = result.get_child_by_name(
  1400. 'failure-list') or netapp_api.NaElement('none')
  1401. errors = failure_list.get_children()
  1402. if errors:
  1403. raise netapp_api.NaApiError(
  1404. errors[0].get_child_content('error-code'),
  1405. errors[0].get_child_content('error-message'))
  1406. @na_utils.trace
  1407. def set_volume_snapdir_access(self, volume_name, hide_snapdir):
  1408. """Set volume snapshot directory visibility."""
  1409. api_args = {
  1410. 'query': {
  1411. 'volume-attributes': {
  1412. 'volume-id-attributes': {
  1413. 'name': volume_name,
  1414. },
  1415. },
  1416. },
  1417. 'attributes': {
  1418. 'volume-attributes': {
  1419. 'volume-snapshot-attributes': {
  1420. 'snapdir-access-enabled': six.text_type(
  1421. not hide_snapdir).lower(),
  1422. },
  1423. },
  1424. },
  1425. }
  1426. result = self.send_request('volume-modify-iter', api_args)
  1427. failures = result.get_child_content('num-failed')
  1428. if failures and int(failures) > 0:
  1429. failure_list = result.get_child_by_name(
  1430. 'failure-list') or netapp_api.NaElement('none')
  1431. errors = failure_list.get_children()
  1432. if errors:
  1433. raise netapp_api.NaApiError(
  1434. errors[0].get_child_content('error-code'),
  1435. errors[0].get_child_content('error-message'))
  1436. @na_utils.trace
  1437. def set_volume_filesys_size_fixed(self,
  1438. volume_name, filesys_size_fixed=False):
  1439. """Set volume file system size fixed to true/false."""
  1440. api_args = {
  1441. 'query': {
  1442. 'volume-attributes': {
  1443. 'volume-id-attributes': {
  1444. 'name': volume_name,
  1445. },
  1446. },
  1447. },
  1448. 'attributes': {
  1449. 'volume-attributes': {
  1450. 'volume-space-attributes': {
  1451. 'is-filesys-size-fixed': six.text_type(
  1452. filesys_size_fixed).lower(),
  1453. },
  1454. },
  1455. },
  1456. }
  1457. result = self.send_request('volume-modify-iter', api_args)
  1458. failures = result.get_child_content('num-failed')
  1459. if failures and int(failures) > 0:
  1460. failure_list = result.get_child_by_name(
  1461. 'failure-list') or netapp_api.NaElement('none')
  1462. errors = failure_list.get_children()
  1463. if errors:
  1464. raise netapp_api.NaApiError(
  1465. errors[0].get_child_content('error-code'),
  1466. errors[0].get_child_content('error-message'))
  1467. @na_utils.trace
  1468. def set_volume_security_style(self, volume_name, security_style='unix'):
  1469. """Set volume security style"""
  1470. api_args = {
  1471. 'query': {
  1472. 'volume-attributes': {
  1473. 'volume-id-attributes': {
  1474. 'name': volume_name,
  1475. },
  1476. },
  1477. },
  1478. 'attributes': {
  1479. 'volume-attributes': {
  1480. 'volume-security-attributes': {
  1481. 'style': security_style,
  1482. },
  1483. },
  1484. },
  1485. }
  1486. result = self.send_request('volume-modify-iter', api_args)
  1487. failures = result.get_child_content('num-failed')
  1488. if failures and int(failures) > 0:
  1489. failure_list = result.get_child_by_name(
  1490. 'failure-list') or netapp_api.NaElement('none')
  1491. errors = failure_list.get_children()
  1492. if errors:
  1493. raise netapp_api.NaApiError(
  1494. errors[0].get_child_content('error-code'),
  1495. errors[0].get_child_content('error-message'))
  1496. @na_utils.trace
  1497. def set_volume_name(self, volume_name, new_volume_name):
  1498. """Set flexvol name."""
  1499. api_args = {
  1500. 'volume': volume_name,
  1501. 'new-volume-name': new_volume_name,
  1502. }
  1503. self.send_request('volume-rename', api_args)
  1504. @na_utils.trace
  1505. def modify_volume(self, aggregate_name, volume_name,
  1506. thin_provisioned=False, snapshot_policy=None,
  1507. language=None, dedup_enabled=False,
  1508. compression_enabled=False, max_files=None,
  1509. qos_policy_group=None, hide_snapdir=None,
  1510. **options):
  1511. """Update backend volume for a share as necessary."""
  1512. api_args = {
  1513. 'query': {
  1514. 'volume-attributes': {
  1515. 'volume-id-attributes': {
  1516. 'containing-aggregate-name': aggregate_name,
  1517. 'name': volume_name,
  1518. },
  1519. },
  1520. },
  1521. 'attributes': {
  1522. 'volume-attributes': {
  1523. 'volume-inode-attributes': {},
  1524. 'volume-language-attributes': {},
  1525. 'volume-snapshot-attributes': {},
  1526. 'volume-space-attributes': {
  1527. 'space-guarantee': ('none' if thin_provisioned else
  1528. 'volume'),
  1529. },
  1530. },
  1531. },
  1532. }
  1533. if language:
  1534. api_args['attributes']['volume-attributes'][
  1535. 'volume-language-attributes']['language'] = language
  1536. if max_files:
  1537. api_args['attributes']['volume-attributes'][
  1538. 'volume-inode-attributes']['files-total'] = max_files
  1539. if snapshot_policy:
  1540. api_args['attributes']['volume-attributes'][
  1541. 'volume-snapshot-attributes'][
  1542. 'snapshot-policy'] = snapshot_policy
  1543. if qos_policy_group:
  1544. api_args['attributes']['volume-attributes'][
  1545. 'volume-qos-attributes'] = {
  1546. 'policy-group-name': qos_policy_group,
  1547. }
  1548. if hide_snapdir in (True, False):
  1549. # Value of hide_snapdir needs to be inverted for ZAPI parameter
  1550. api_args['attributes']['volume-attributes'][
  1551. 'volume-snapshot-attributes'][
  1552. 'snapdir-access-enabled'] = six.text_type(
  1553. not hide_snapdir).lower()
  1554. self.send_request('volume-modify-iter', api_args)
  1555. # Efficiency options must be handled separately
  1556. self.update_volume_efficiency_attributes(volume_name,
  1557. dedup_enabled,
  1558. compression_enabled)
  1559. @na_utils.trace
  1560. def update_volume_efficiency_attributes(self, volume_name, dedup_enabled,
  1561. compression_enabled):
  1562. """Update dedupe & compression attributes to match desired values."""
  1563. efficiency_status = self.get_volume_efficiency_status(volume_name)
  1564. # cDOT compression requires dedup to be enabled
  1565. dedup_enabled = dedup_enabled or compression_enabled
  1566. # enable/disable dedup if needed
  1567. if dedup_enabled and not efficiency_status['dedupe']:
  1568. self.enable_dedup(volume_name)
  1569. elif not dedup_enabled and efficiency_status['dedupe']:
  1570. self.disable_dedup(volume_name)
  1571. # enable/disable compression if needed
  1572. if compression_enabled and not efficiency_status['compression']:
  1573. self.enable_compression(volume_name)
  1574. elif not compression_enabled and efficiency_status['compression']:
  1575. self.disable_compression(volume_name)
  1576. @na_utils.trace
  1577. def volume_exists(self, volume_name):
  1578. """Checks if volume exists."""
  1579. LOG.debug('Checking if volume %s exists', volume_name)
  1580. api_args = {
  1581. 'query': {
  1582. 'volume-attributes': {
  1583. 'volume-id-attributes': {
  1584. 'name': volume_name,
  1585. },
  1586. },
  1587. },
  1588. 'desired-attributes': {
  1589. 'volume-attributes': {
  1590. 'volume-id-attributes': {
  1591. 'name': None,
  1592. },
  1593. },
  1594. },
  1595. }
  1596. result = self.send_iter_request('volume-get-iter', api_args)
  1597. return self._has_records(result)
  1598. @na_utils.trace
  1599. def is_flexvol_encrypted(self, volume_name, vserver_name):
  1600. """Checks whether the volume is encrypted or not."""
  1601. if not self.features.FLEXVOL_ENCRYPTION:
  1602. return False
  1603. api_args = {
  1604. 'query': {
  1605. 'volume-attributes': {
  1606. 'encrypt': 'true',
  1607. 'volume-id-attributes': {
  1608. 'name': volume_name,
  1609. 'owning-vserver-name': vserver_name,
  1610. },
  1611. },
  1612. },
  1613. 'desired-attributes': {
  1614. 'volume-attributes': {
  1615. 'encrypt': None,
  1616. },
  1617. },
  1618. }
  1619. result = self.send_iter_request('volume-get-iter', api_args)
  1620. if self._has_records(result):
  1621. attributes_list = result.get_child_by_name(
  1622. 'attributes-list') or netapp_api.NaElement('none')
  1623. volume_attributes = attributes_list.get_child_by_name(
  1624. 'volume-attributes') or netapp_api.NaElement('none')
  1625. encrypt = volume_attributes.get_child_content('encrypt')
  1626. if encrypt:
  1627. return True
  1628. return False
  1629. @na_utils.trace
  1630. def get_aggregate_for_volume(self, volume_name):
  1631. """Get the name of the aggregate containing a volume."""
  1632. api_args = {
  1633. 'query': {
  1634. 'volume-attributes': {
  1635. 'volume-id-attributes': {
  1636. 'name': volume_name,
  1637. },
  1638. },
  1639. },
  1640. 'desired-attributes': {
  1641. 'volume-attributes': {
  1642. 'volume-id-attributes': {
  1643. 'containing-aggregate-name': None,
  1644. 'name': None,
  1645. },
  1646. },
  1647. },
  1648. }
  1649. result = self.send_iter_request('volume-get-iter', api_args)
  1650. attributes_list = result.get_child_by_name(
  1651. 'attributes-list') or netapp_api.NaElement('none')
  1652. volume_attributes = attributes_list.get_child_by_name(
  1653. 'volume-attributes') or netapp_api.NaElement('none')
  1654. volume_id_attributes = volume_attributes.get_child_by_name(
  1655. 'volume-id-attributes') or netapp_api.NaElement('none')
  1656. aggregate = volume_id_attributes.get_child_content(
  1657. 'containing-aggregate-name')
  1658. if not aggregate:
  1659. msg = _('Could not find aggregate for volume %s.')
  1660. raise exception.NetAppException(msg % volume_name)
  1661. return aggregate
  1662. @na_utils.trace
  1663. def volume_has_luns(self, volume_name):
  1664. """Checks if volume has LUNs."""
  1665. LOG.debug('Checking if volume %s has LUNs', volume_name)
  1666. api_args = {
  1667. 'query': {
  1668. 'lun-info': {
  1669. 'volume': volume_name,
  1670. },
  1671. },
  1672. 'desired-attributes': {
  1673. 'lun-info': {
  1674. 'path': None,
  1675. },
  1676. },
  1677. }
  1678. result = self.send_iter_request('lun-get-iter', api_args)
  1679. return self._has_records(result)
  1680. @na_utils.trace
  1681. def volume_has_junctioned_volumes(self, volume_name):
  1682. """Checks if volume has volumes mounted beneath its junction path."""
  1683. junction_path = self.get_volume_junction_path(volume_name)
  1684. if not junction_path:
  1685. return False
  1686. api_args = {
  1687. 'query': {
  1688. 'volume-attributes': {
  1689. 'volume-id-attributes': {
  1690. 'junction-path': junction_path + '/*',
  1691. },
  1692. },
  1693. },
  1694. 'desired-attributes': {
  1695. 'volume-attributes': {
  1696. 'volume-id-attributes': {
  1697. 'name': None,
  1698. },
  1699. },
  1700. },
  1701. }
  1702. result = self.send_iter_request('volume-get-iter', api_args)
  1703. return self._has_records(result)
  1704. @na_utils.trace
  1705. def get_volume(self, volume_name):
  1706. """Returns the volume with the specified name, if present."""
  1707. api_args = {
  1708. 'query': {
  1709. 'volume-attributes': {
  1710. 'volume-id-attributes': {
  1711. 'name': volume_name,
  1712. },
  1713. },
  1714. },
  1715. 'desired-attributes': {
  1716. 'volume-attributes': {
  1717. 'volume-id-attributes': {
  1718. 'containing-aggregate-name': None,
  1719. 'junction-path': None,
  1720. 'name': None,
  1721. 'owning-vserver-name': None,
  1722. 'type': None,
  1723. 'style': None,
  1724. },
  1725. 'volume-qos-attributes': {
  1726. 'policy-group-name': None,
  1727. },
  1728. 'volume-space-attributes': {
  1729. 'size': None,
  1730. },
  1731. },
  1732. },
  1733. }
  1734. result = self.send_request('volume-get-iter', api_args)
  1735. attributes_list = result.get_child_by_name(
  1736. 'attributes-list') or netapp_api.NaElement('none')
  1737. volume_attributes_list = attributes_list.get_children()
  1738. if not self._has_records(result):
  1739. raise exception.StorageResourceNotFound(name=volume_name)
  1740. elif len(volume_attributes_list) > 1:
  1741. msg = _('Could not find unique volume %(vol)s.')
  1742. msg_args = {'vol': volume_name}
  1743. raise exception.NetAppException(msg % msg_args)
  1744. volume_attributes = volume_attributes_list[0]
  1745. volume_id_attributes = volume_attributes.get_child_by_name(
  1746. 'volume-id-attributes') or netapp_api.NaElement('none')
  1747. volume_qos_attributes = volume_attributes.get_child_by_name(
  1748. 'volume-qos-attributes') or netapp_api.NaElement('none')
  1749. volume_space_attributes = volume_attributes.get_child_by_name(
  1750. 'volume-space-attributes') or netapp_api.NaElement('none')
  1751. volume = {
  1752. 'aggregate': volume_id_attributes.get_child_content(
  1753. 'containing-aggregate-name'),
  1754. 'junction-path': volume_id_attributes.get_child_content(
  1755. 'junction-path'),
  1756. 'name': volume_id_attributes.get_child_content('name'),
  1757. 'owning-vserver-name': volume_id_attributes.get_child_content(
  1758. 'owning-vserver-name'),
  1759. 'type': volume_id_attributes.get_child_content('type'),
  1760. 'style': volume_id_attributes.get_child_content('style'),
  1761. 'size': volume_space_attributes.get_child_content('size'),
  1762. 'qos-policy-group-name': volume_qos_attributes.get_child_content(
  1763. 'policy-group-name')
  1764. }
  1765. return volume
  1766. @na_utils.trace
  1767. def get_volume_at_junction_path(self, junction_path):
  1768. """Returns the volume with the specified junction path, if present."""
  1769. if not junction_path:
  1770. return None
  1771. api_args = {
  1772. 'query': {
  1773. 'volume-attributes': {
  1774. 'volume-id-attributes': {
  1775. 'junction-path': junction_path,
  1776. },
  1777. },
  1778. },
  1779. 'desired-attributes': {
  1780. 'volume-attributes': {
  1781. 'volume-id-attributes': {
  1782. 'containing-aggregate-name': None,
  1783. 'junction-path': None,
  1784. 'name': None,
  1785. 'type': None,
  1786. 'style': None,
  1787. },
  1788. 'volume-space-attributes': {
  1789. 'size': None,
  1790. }
  1791. },
  1792. },
  1793. }
  1794. result = self.send_iter_request('volume-get-iter', api_args)
  1795. if not self._has_records(result):
  1796. return None
  1797. attributes_list = result.get_child_by_name(
  1798. 'attributes-list') or netapp_api.NaElement('none')
  1799. volume_attributes = attributes_list.get_child_by_name(
  1800. 'volume-attributes') or netapp_api.NaElement('none')
  1801. volume_id_attributes = volume_attributes.get_child_by_name(
  1802. 'volume-id-attributes') or netapp_api.NaElement('none')
  1803. volume_space_attributes = volume_attributes.get_child_by_name(
  1804. 'volume-space-attributes') or netapp_api.NaElement('none')
  1805. volume = {
  1806. 'aggregate': volume_id_attributes.get_child_content(
  1807. 'containing-aggregate-name'),
  1808. 'junction-path': volume_id_attributes.get_child_content(
  1809. 'junction-path'),
  1810. 'name': volume_id_attributes.get_child_content('name'),
  1811. 'type': volume_id_attributes.get_child_content('type'),
  1812. 'style': volume_id_attributes.get_child_content('style'),
  1813. 'size': volume_space_attributes.get_child_content('size'),
  1814. }
  1815. return volume
  1816. @na_utils.trace
  1817. def get_volume_to_manage(self, aggregate_name, volume_name):
  1818. """Get flexvol to be managed by Manila."""
  1819. api_args = {
  1820. 'query': {
  1821. 'volume-attributes': {
  1822. 'volume-id-attributes': {
  1823. 'containing-aggregate-name': aggregate_name,
  1824. 'name': volume_name,
  1825. },
  1826. },
  1827. },
  1828. 'desired-attributes': {
  1829. 'volume-attributes': {
  1830. 'volume-id-attributes': {
  1831. 'containing-aggregate-name': None,
  1832. 'junction-path': None,
  1833. 'name': None,
  1834. 'type': None,
  1835. 'style': None,
  1836. 'owning-vserver-name': None,
  1837. },
  1838. 'volume-qos-attributes': {
  1839. 'policy-group-name': None,
  1840. },
  1841. 'volume-space-attributes': {
  1842. 'size': None,
  1843. },
  1844. },
  1845. },
  1846. }
  1847. result = self.send_iter_request('volume-get-iter', api_args)
  1848. if not self._has_records(result):
  1849. return None
  1850. attributes_list = result.get_child_by_name(
  1851. 'attributes-list') or netapp_api.NaElement('none')
  1852. volume_attributes = attributes_list.get_child_by_name(
  1853. 'volume-attributes') or netapp_api.NaElement('none')
  1854. volume_id_attributes = volume_attributes.get_child_by_name(
  1855. 'volume-id-attributes') or netapp_api.NaElement('none')
  1856. volume_qos_attributes = volume_attributes.get_child_by_name(
  1857. 'volume-qos-attributes') or netapp_api.NaElement('none')
  1858. volume_space_attributes = volume_attributes.get_child_by_name(
  1859. 'volume-space-attributes') or netapp_api.NaElement('none')
  1860. volume = {
  1861. 'aggregate': volume_id_attributes.get_child_content(
  1862. 'containing-aggregate-name'),
  1863. 'junction-path': volume_id_attributes.get_child_content(
  1864. 'junction-path'),
  1865. 'name': volume_id_attributes.get_child_content('name'),
  1866. 'type': volume_id_attributes.get_child_content('type'),
  1867. 'style': volume_id_attributes.get_child_content('style'),
  1868. 'owning-vserver-name': volume_id_attributes.get_child_content(
  1869. 'owning-vserver-name'),
  1870. 'size': volume_space_attributes.get_child_content('size'),
  1871. 'qos-policy-group-name': volume_qos_attributes.get_child_content(
  1872. 'policy-group-name')
  1873. }
  1874. return volume
  1875. @na_utils.trace
  1876. def create_volume_clone(self, volume_name, parent_volume_name,
  1877. parent_snapshot_name=None, split=False,
  1878. qos_policy_group=None, **options):
  1879. """Clones a volume."""
  1880. api_args = {
  1881. 'volume': volume_name,
  1882. 'parent-volume': parent_volume_name,
  1883. 'parent-snapshot': parent_snapshot_name,
  1884. 'junction-path': '/%s' % volume_name,
  1885. }
  1886. if qos_policy_group is not None:
  1887. api_args['qos-policy-group-name'] = qos_policy_group
  1888. self.send_request('volume-clone-create', api_args)
  1889. if split:
  1890. self.split_volume_clone(volume_name)
  1891. @na_utils.trace
  1892. def split_volume_clone(self, volume_name):
  1893. """Begins splitting a clone from its parent."""
  1894. try:
  1895. api_args = {'volume': volume_name}
  1896. self.send_request('volume-clone-split-start', api_args)
  1897. except netapp_api.NaApiError as e:
  1898. if e.code == netapp_api.EVOL_CLONE_BEING_SPLIT:
  1899. return
  1900. raise
  1901. @na_utils.trace
  1902. def get_clone_children_for_snapshot(self, volume_name, snapshot_name):
  1903. """Returns volumes that are keeping a snapshot locked."""
  1904. api_args = {
  1905. 'query': {
  1906. 'volume-attributes': {
  1907. 'volume-clone-attributes': {
  1908. 'volume-clone-parent-attributes': {
  1909. 'name': volume_name,
  1910. 'snapshot-name': snapshot_name,
  1911. },
  1912. },
  1913. },
  1914. },
  1915. 'desired-attributes': {
  1916. 'volume-attributes': {
  1917. 'volume-id-attributes': {
  1918. 'name': None,
  1919. },
  1920. },
  1921. },
  1922. }
  1923. result = self.send_iter_request('volume-get-iter', api_args)
  1924. if not self._has_records(result):
  1925. return []
  1926. volume_list = []
  1927. attributes_list = result.get_child_by_name(
  1928. 'attributes-list') or netapp_api.NaElement('none')
  1929. for volume_attributes in attributes_list.get_children():
  1930. volume_id_attributes = volume_attributes.get_child_by_name(
  1931. 'volume-id-attributes') or netapp_api.NaElement('none')
  1932. volume_list.append({
  1933. 'name': volume_id_attributes.get_child_content('name'),
  1934. })
  1935. return volume_list
  1936. @na_utils.trace
  1937. def get_volume_junction_path(self, volume_name, is_style_cifs=False):
  1938. """Gets a volume junction path."""
  1939. api_args = {
  1940. 'volume': volume_name,
  1941. 'is-style-cifs': six.text_type(is_style_cifs).lower(),
  1942. }
  1943. result = self.send_request('volume-get-volume-path', api_args)
  1944. return result.get_child_content('junction')
  1945. @na_utils.trace
  1946. def mount_volume(self, volume_name, junction_path=None):
  1947. """Mounts a volume on a junction path."""
  1948. api_args = {
  1949. 'volume-name': volume_name,
  1950. 'junction-path': (junction_path if junction_path
  1951. else '/%s' % volume_name)
  1952. }
  1953. self.send_request('volume-mount', api_args)
  1954. @na_utils.trace
  1955. def offline_volume(self, volume_name):
  1956. """Offlines a volume."""
  1957. try:
  1958. self.send_request('volume-offline', {'name': volume_name})
  1959. except netapp_api.NaApiError as e:
  1960. if e.code == netapp_api.EVOLUMEOFFLINE:
  1961. return
  1962. raise
  1963. @na_utils.trace
  1964. def _unmount_volume(self, volume_name, force=False):
  1965. """Unmounts a volume."""
  1966. api_args = {
  1967. 'volume-name': volume_name,
  1968. 'force': six.text_type(force).lower(),
  1969. }
  1970. try:
  1971. self.send_request('volume-unmount', api_args)
  1972. except netapp_api.NaApiError as e:
  1973. if e.code == netapp_api.EVOL_NOT_MOUNTED:
  1974. return
  1975. raise
  1976. @na_utils.trace
  1977. def unmount_volume(self, volume_name, force=False, wait_seconds=30):
  1978. """Unmounts a volume, retrying if a clone split is ongoing.
  1979. NOTE(cknight): While unlikely to happen in normal operation, any client
  1980. that tries to delete volumes immediately after creating volume clones
  1981. is likely to experience failures if cDOT isn't quite ready for the
  1982. delete. The volume unmount is the first operation in the delete
  1983. path that fails in this case, and there is no proactive check we can
  1984. use to reliably predict the failure. And there isn't a specific error
  1985. code from volume-unmount, so we have to check for a generic error code
  1986. plus certain language in the error code. It's ugly, but it works, and
  1987. it's better than hard-coding a fixed delay.
  1988. """
  1989. # Do the unmount, handling split-related errors with retries.
  1990. retry_interval = 3 # seconds
  1991. for retry in range(int(wait_seconds / retry_interval)):
  1992. try:
  1993. self._unmount_volume(volume_name, force=force)
  1994. LOG.debug('Volume %s unmounted.', volume_name)
  1995. return
  1996. except netapp_api.NaApiError as e:
  1997. if e.code == netapp_api.EAPIERROR and 'job ID' in e.message:
  1998. msg = ('Could not unmount volume %(volume)s due to '
  1999. 'ongoing volume operation: %(exception)s')
  2000. msg_args = {'volume': volume_name, 'exception': e}
  2001. LOG.warning(msg, msg_args)
  2002. time.sleep(retry_interval)
  2003. continue
  2004. raise
  2005. msg = _('Failed to unmount volume %(volume)s after '
  2006. 'waiting for %(wait_seconds)s seconds.')
  2007. msg_args = {'volume': volume_name, 'wait_seconds': wait_seconds}
  2008. LOG.error(msg, msg_args)
  2009. raise exception.NetAppException(msg % msg_args)
  2010. @na_utils.trace
  2011. def delete_volume(self, volume_name):
  2012. """Deletes a volume."""
  2013. self.send_request('volume-destroy', {'name': volume_name})
  2014. @na_utils.trace
  2015. def create_snapshot(self, volume_name, snapshot_name):
  2016. """Creates a volume snapshot."""
  2017. api_args = {'volume': volume_name, 'snapshot': snapshot_name}
  2018. self.send_request('snapshot-create', api_args)
  2019. @na_utils.trace
  2020. def snapshot_exists(self, snapshot_name, volume_name):
  2021. """Checks if Snapshot exists for a specified volume."""
  2022. LOG.debug('Checking if snapshot %(snapshot)s exists for '
  2023. 'volume %(volume)s',
  2024. {'snapshot': snapshot_name, 'volume': volume_name})
  2025. """Gets a single snapshot."""
  2026. api_args = {
  2027. 'query': {
  2028. 'snapshot-info': {
  2029. 'name': snapshot_name,
  2030. 'volume': volume_name,
  2031. },
  2032. },
  2033. 'desired-attributes': {
  2034. 'snapshot-info': {
  2035. 'name': None,
  2036. 'volume': None,
  2037. 'busy': None,
  2038. 'snapshot-owners-list': {
  2039. 'snapshot-owner': None,
  2040. }
  2041. },
  2042. },
  2043. }
  2044. result = self.send_request('snapshot-get-iter', api_args)
  2045. error_record_list = result.get_child_by_name(
  2046. 'volume-errors') or netapp_api.NaElement('none')
  2047. errors = error_record_list.get_children()
  2048. if errors:
  2049. error = errors[0]
  2050. error_code = error.get_child_content('errno')
  2051. error_reason = error.get_child_content('reason')
  2052. msg = _('Could not read information for snapshot %(name)s. '
  2053. 'Code: %(code)s. Reason: %(reason)s')
  2054. msg_args = {
  2055. 'name': snapshot_name,
  2056. 'code': error_code,
  2057. 'reason': error_reason
  2058. }
  2059. if error_code == netapp_api.ESNAPSHOTNOTALLOWED:
  2060. raise exception.SnapshotUnavailable(msg % msg_args)
  2061. else:
  2062. raise exception.NetAppException(msg % msg_args)
  2063. return self._has_records(result)
  2064. @na_utils.trace
  2065. def get_snapshot(self, volume_name, snapshot_name):
  2066. """Gets a single snapshot."""
  2067. api_args = {
  2068. 'query': {
  2069. 'snapshot-info': {
  2070. 'name': snapshot_name,
  2071. 'volume': volume_name,
  2072. },
  2073. },
  2074. 'desired-attributes': {
  2075. 'snapshot-info': {
  2076. 'access-time': None,
  2077. 'name': None,
  2078. 'volume': None,
  2079. 'busy': None,
  2080. 'snapshot-owners-list': {
  2081. 'snapshot-owner': None,
  2082. }
  2083. },
  2084. },
  2085. }
  2086. result = self.send_request('snapshot-get-iter', api_args)
  2087. error_record_list = result.get_child_by_name(
  2088. 'volume-errors') or netapp_api.NaElement('none')
  2089. errors = error_record_list.get_children()
  2090. if errors:
  2091. error = errors[0]
  2092. error_code = error.get_child_content('errno')
  2093. error_reason = error.get_child_content('reason')
  2094. msg = _('Could not read information for snapshot %(name)s. '
  2095. 'Code: %(code)s. Reason: %(reason)s')
  2096. msg_args = {
  2097. 'name': snapshot_name,
  2098. 'code': error_code,
  2099. 'reason': error_reason
  2100. }
  2101. if error_code == netapp_api.ESNAPSHOTNOTALLOWED:
  2102. raise exception.SnapshotUnavailable(msg % msg_args)
  2103. else:
  2104. raise exception.NetAppException(msg % msg_args)
  2105. attributes_list = result.get_child_by_name(
  2106. 'attributes-list') or netapp_api.NaElement('none')
  2107. snapshot_info_list = attributes_list.get_children()
  2108. if not self._has_records(result):
  2109. raise exception.SnapshotResourceNotFound(name=snapshot_name)
  2110. elif len(snapshot_info_list) > 1:
  2111. msg = _('Could not find unique snapshot %(snap)s on '
  2112. 'volume %(vol)s.')
  2113. msg_args = {'snap': snapshot_name, 'vol': volume_name}
  2114. raise exception.NetAppException(msg % msg_args)
  2115. snapshot_info = snapshot_info_list[0]
  2116. snapshot = {
  2117. 'access-time': snapshot_info.get_child_content('access-time'),
  2118. 'name': snapshot_info.get_child_content('name'),
  2119. 'volume': snapshot_info.get_child_content('volume'),
  2120. 'busy': strutils.bool_from_string(
  2121. snapshot_info.get_child_content('busy')),
  2122. }
  2123. snapshot_owners_list = snapshot_info.get_child_by_name(
  2124. 'snapshot-owners-list') or netapp_api.NaElement('none')
  2125. snapshot_owners = set([
  2126. snapshot_owner.get_child_content('owner')
  2127. for snapshot_owner in snapshot_owners_list.get_children()])
  2128. snapshot['owners'] = snapshot_owners
  2129. return snapshot
  2130. @na_utils.trace
  2131. def rename_snapshot(self, volume_name, snapshot_name, new_snapshot_name):
  2132. api_args = {
  2133. 'volume': volume_name,
  2134. 'current-name': snapshot_name,
  2135. 'new-name': new_snapshot_name
  2136. }
  2137. self.send_request('snapshot-rename', api_args)
  2138. @na_utils.trace
  2139. def restore_snapshot(self, volume_name, snapshot_name):
  2140. """Reverts a volume to the specified snapshot."""
  2141. api_args = {
  2142. 'volume': volume_name,
  2143. 'snapshot': snapshot_name,
  2144. }
  2145. self.send_request('snapshot-restore-volume', api_args)
  2146. @na_utils.trace
  2147. def delete_snapshot(self, volume_name, snapshot_name, ignore_owners=False):
  2148. """Deletes a volume snapshot."""
  2149. ignore_owners = ('true' if strutils.bool_from_string(ignore_owners)
  2150. else 'false')
  2151. api_args = {
  2152. 'volume': volume_name,
  2153. 'snapshot': snapshot_name,
  2154. 'ignore-owners': ignore_owners,
  2155. }
  2156. self.send_request('snapshot-delete', api_args)
  2157. @na_utils.trace
  2158. def soft_delete_snapshot(self, volume_name, snapshot_name):
  2159. """Deletes a volume snapshot, or renames it if delete fails."""
  2160. try:
  2161. self.delete_snapshot(volume_name, snapshot_name)
  2162. except netapp_api.NaApiError:
  2163. self.rename_snapshot(volume_name,
  2164. snapshot_name,
  2165. DELETED_PREFIX + snapshot_name)
  2166. msg = _('Soft-deleted snapshot %(snapshot)s on volume %(volume)s.')
  2167. msg_args = {'snapshot': snapshot_name, 'volume': volume_name}
  2168. LOG.info(msg, msg_args)
  2169. @na_utils.trace
  2170. def prune_deleted_snapshots(self):
  2171. """Deletes non-busy snapshots that were previously soft-deleted."""
  2172. deleted_snapshots_map = self._get_deleted_snapshots()
  2173. for vserver in deleted_snapshots_map:
  2174. client = copy.deepcopy(self)
  2175. client.set_vserver(vserver)
  2176. for snapshot in deleted_snapshots_map[vserver]:
  2177. try:
  2178. client.delete_snapshot(snapshot['volume'],
  2179. snapshot['name'])
  2180. except netapp_api.NaApiError:
  2181. msg = _('Could not delete snapshot %(snap)s on '
  2182. 'volume %(volume)s.')
  2183. msg_args = {
  2184. 'snap': snapshot['name'],
  2185. 'volume': snapshot['volume'],
  2186. }
  2187. LOG.exception(msg, msg_args)
  2188. @na_utils.trace
  2189. def _get_deleted_snapshots(self):
  2190. """Returns non-busy, soft-deleted snapshots suitable for reaping."""
  2191. api_args = {
  2192. 'query': {
  2193. 'snapshot-info': {
  2194. 'name': DELETED_PREFIX + '*',
  2195. 'busy': 'false',
  2196. },
  2197. },
  2198. 'desired-attributes': {
  2199. 'snapshot-info': {
  2200. 'name': None,
  2201. 'vserver': None,
  2202. 'volume': None,
  2203. },
  2204. },
  2205. }
  2206. result = self.send_iter_request('snapshot-get-iter', api_args)
  2207. attributes_list = result.get_child_by_name(
  2208. 'attributes-list') or netapp_api.NaElement('none')
  2209. # Build a map of snapshots, one list of snapshots per vserver
  2210. snapshot_map = {}
  2211. for snapshot_info in attributes_list.get_children():
  2212. vserver = snapshot_info.get_child_content('vserver')
  2213. snapshot_list = snapshot_map.get(vserver, [])
  2214. snapshot_list.append({
  2215. 'name': snapshot_info.get_child_content('name'),
  2216. 'volume': snapshot_info.get_child_content('volume'),
  2217. 'vserver': vserver,
  2218. })
  2219. snapshot_map[vserver] = snapshot_list
  2220. return snapshot_map
  2221. @na_utils.trace
  2222. def create_cg_snapshot(self, volume_names, snapshot_name):
  2223. """Creates a consistency group snapshot of one or more flexvols."""
  2224. cg_id = self._start_cg_snapshot(volume_names, snapshot_name)
  2225. if not cg_id:
  2226. msg = _('Could not start consistency group snapshot %s.')
  2227. raise exception.NetAppException(msg % snapshot_name)
  2228. self._commit_cg_snapshot(cg_id)
  2229. @na_utils.trace
  2230. def _start_cg_snapshot(self, volume_names, snapshot_name):
  2231. api_args = {
  2232. 'snapshot': snapshot_name,
  2233. 'timeout': 'relaxed',
  2234. 'volumes': [
  2235. {'volume-name': volume_name} for volume_name in volume_names
  2236. ],
  2237. }
  2238. result = self.send_request('cg-start', api_args)
  2239. return result.get_child_content('cg-id')
  2240. @na_utils.trace
  2241. def _commit_cg_snapshot(self, cg_id):
  2242. api_args = {'cg-id': cg_id}
  2243. self.send_request('cg-commit', api_args)
  2244. @na_utils.trace
  2245. def create_cifs_share(self, share_name):
  2246. share_path = '/%s' % share_name
  2247. api_args = {'path': share_path, 'share-name': share_name}
  2248. self.send_request('cifs-share-create', api_args)
  2249. @na_utils.trace
  2250. def get_cifs_share_access(self, share_name):
  2251. api_args = {
  2252. 'query': {
  2253. 'cifs-share-access-control': {
  2254. 'share': share_name,
  2255. },
  2256. },
  2257. 'desired-attributes': {
  2258. 'cifs-share-access-control': {
  2259. 'user-or-group': None,
  2260. 'permission': None,
  2261. },
  2262. },
  2263. }
  2264. result = self.send_iter_request('cifs-share-access-control-get-iter',
  2265. api_args)
  2266. attributes_list = result.get_child_by_name(
  2267. 'attributes-list') or netapp_api.NaElement('none')
  2268. rules = {}
  2269. for rule in attributes_list.get_children():
  2270. user_or_group = rule.get_child_content('user-or-group')
  2271. permission = rule.get_child_content('permission')
  2272. rules[user_or_group] = permission
  2273. return rules
  2274. @na_utils.trace
  2275. def add_cifs_share_access(self, share_name, user_name, readonly):
  2276. api_args = {
  2277. 'permission': 'read' if readonly else 'full_control',
  2278. 'share': share_name,
  2279. 'user-or-group': user_name,
  2280. }
  2281. self.send_request('cifs-share-access-control-create', api_args)
  2282. @na_utils.trace
  2283. def modify_cifs_share_access(self, share_name, user_name, readonly):
  2284. api_args = {
  2285. 'permission': 'read' if readonly else 'full_control',
  2286. 'share': share_name,
  2287. 'user-or-group': user_name,
  2288. }
  2289. self.send_request('cifs-share-access-control-modify', api_args)
  2290. @na_utils.trace
  2291. def remove_cifs_share_access(self, share_name, user_name):
  2292. api_args = {'user-or-group': user_name, 'share': share_name}
  2293. self.send_request('cifs-share-access-control-delete', api_args)
  2294. @na_utils.trace
  2295. def remove_cifs_share(self, share_name):
  2296. self.send_request('cifs-share-delete', {'share-name': share_name})
  2297. @na_utils.trace
  2298. def add_nfs_export_rule(self, policy_name, client_match, readonly):
  2299. rule_indices = self._get_nfs_export_rule_indices(policy_name,
  2300. client_match)
  2301. if not rule_indices:
  2302. self._add_nfs_export_rule(policy_name, client_match, readonly)
  2303. else:
  2304. # Update first rule and delete the rest
  2305. self._update_nfs_export_rule(
  2306. policy_name, client_match, readonly, rule_indices.pop(0))
  2307. self._remove_nfs_export_rules(policy_name, rule_indices)
  2308. @na_utils.trace
  2309. def _add_nfs_export_rule(self, policy_name, client_match, readonly):
  2310. api_args = {
  2311. 'policy-name': policy_name,
  2312. 'client-match': client_match,
  2313. 'ro-rule': {
  2314. 'security-flavor': 'sys',
  2315. },
  2316. 'rw-rule': {
  2317. 'security-flavor': 'sys' if not readonly else 'never',
  2318. },
  2319. 'super-user-security': {
  2320. 'security-flavor': 'sys',
  2321. },
  2322. }
  2323. self.send_request('export-rule-create', api_args)
  2324. @na_utils.trace
  2325. def _update_nfs_export_rule(self, policy_name, client_match, readonly,
  2326. rule_index):
  2327. api_args = {
  2328. 'policy-name': policy_name,
  2329. 'rule-index': rule_index,
  2330. 'client-match': client_match,
  2331. 'ro-rule': {
  2332. 'security-flavor': 'sys'
  2333. },
  2334. 'rw-rule': {
  2335. 'security-flavor': 'sys' if not readonly else 'never'
  2336. },
  2337. 'super-user-security': {
  2338. 'security-flavor': 'sys'
  2339. },
  2340. }
  2341. self.send_request('export-rule-modify', api_args)
  2342. @na_utils.trace
  2343. def _get_nfs_export_rule_indices(self, policy_name, client_match):
  2344. api_args = {
  2345. 'query': {
  2346. 'export-rule-info': {
  2347. 'policy-name': policy_name,
  2348. 'client-match': client_match,
  2349. },
  2350. },
  2351. 'desired-attributes': {
  2352. 'export-rule-info': {
  2353. 'vserver-name': None,
  2354. 'policy-name': None,
  2355. 'client-match': None,
  2356. 'rule-index': None,
  2357. },
  2358. },
  2359. }
  2360. result = self.send_iter_request('export-rule-get-iter', api_args)
  2361. attributes_list = result.get_child_by_name(
  2362. 'attributes-list') or netapp_api.NaElement('none')
  2363. export_rule_info_list = attributes_list.get_children()
  2364. rule_indices = [int(export_rule_info.get_child_content('rule-index'))
  2365. for export_rule_info in export_rule_info_list]
  2366. rule_indices.sort()
  2367. return [six.text_type(rule_index) for rule_index in rule_indices]
  2368. @na_utils.trace
  2369. def remove_nfs_export_rule(self, policy_name, client_match):
  2370. rule_indices = self._get_nfs_export_rule_indices(policy_name,
  2371. client_match)
  2372. self._remove_nfs_export_rules(policy_name, rule_indices)
  2373. @na_utils.trace
  2374. def _remove_nfs_export_rules(self, policy_name, rule_indices):
  2375. for rule_index in rule_indices:
  2376. api_args = {
  2377. 'policy-name': policy_name,
  2378. 'rule-index': rule_index
  2379. }
  2380. try:
  2381. self.send_request('export-rule-destroy', api_args)
  2382. except netapp_api.NaApiError as e:
  2383. if e.code != netapp_api.EOBJECTNOTFOUND:
  2384. raise
  2385. @na_utils.trace
  2386. def clear_nfs_export_policy_for_volume(self, volume_name):
  2387. self.set_nfs_export_policy_for_volume(volume_name, 'default')
  2388. @na_utils.trace
  2389. def set_nfs_export_policy_for_volume(self, volume_name, policy_name):
  2390. api_args = {
  2391. 'query': {
  2392. 'volume-attributes': {
  2393. 'volume-id-attributes': {
  2394. 'name': volume_name,
  2395. },
  2396. },
  2397. },
  2398. 'attributes': {
  2399. 'volume-attributes': {
  2400. 'volume-export-attributes': {
  2401. 'policy': policy_name,
  2402. },
  2403. },
  2404. },
  2405. }
  2406. self.send_request('volume-modify-iter', api_args)
  2407. @na_utils.trace
  2408. def set_qos_policy_group_for_volume(self, volume_name,
  2409. qos_policy_group_name):
  2410. api_args = {
  2411. 'query': {
  2412. 'volume-attributes': {
  2413. 'volume-id-attributes': {
  2414. 'name': volume_name,
  2415. },
  2416. },
  2417. },
  2418. 'attributes': {
  2419. 'volume-attributes': {
  2420. 'volume-qos-attributes': {
  2421. 'policy-group-name': qos_policy_group_name,
  2422. },
  2423. },
  2424. },
  2425. }
  2426. self.send_request('volume-modify-iter', api_args)
  2427. @na_utils.trace
  2428. def get_nfs_export_policy_for_volume(self, volume_name):
  2429. """Get the name of the export policy for a volume."""
  2430. api_args = {
  2431. 'query': {
  2432. 'volume-attributes': {
  2433. 'volume-id-attributes': {
  2434. 'name': volume_name,
  2435. },
  2436. },
  2437. },
  2438. 'desired-attributes': {
  2439. 'volume-attributes': {
  2440. 'volume-export-attributes': {
  2441. 'policy': None,
  2442. },
  2443. },
  2444. },
  2445. }
  2446. result = self.send_iter_request('volume-get-iter', api_args)
  2447. attributes_list = result.get_child_by_name(
  2448. 'attributes-list') or netapp_api.NaElement('none')
  2449. volume_attributes = attributes_list.get_child_by_name(
  2450. 'volume-attributes') or netapp_api.NaElement('none')
  2451. volume_export_attributes = volume_attributes.get_child_by_name(
  2452. 'volume-export-attributes') or netapp_api.NaElement('none')
  2453. export_policy = volume_export_attributes.get_child_content('policy')
  2454. if not export_policy:
  2455. msg = _('Could not find export policy for volume %s.')
  2456. raise exception.NetAppException(msg % volume_name)
  2457. return export_policy
  2458. @na_utils.trace
  2459. def create_nfs_export_policy(self, policy_name):
  2460. api_args = {'policy-name': policy_name}
  2461. try:
  2462. self.send_request('export-policy-create', api_args)
  2463. except netapp_api.NaApiError as e:
  2464. if e.code != netapp_api.EDUPLICATEENTRY:
  2465. raise
  2466. @na_utils.trace
  2467. def soft_delete_nfs_export_policy(self, policy_name):
  2468. try:
  2469. self.delete_nfs_export_policy(policy_name)
  2470. except netapp_api.NaApiError:
  2471. # NOTE(cknight): Policy deletion can fail if called too soon after
  2472. # removing from a flexvol. So rename for later harvesting.
  2473. self.rename_nfs_export_policy(policy_name,
  2474. DELETED_PREFIX + policy_name)
  2475. @na_utils.trace
  2476. def delete_nfs_export_policy(self, policy_name):
  2477. api_args = {'policy-name': policy_name}
  2478. try:
  2479. self.send_request('export-policy-destroy', api_args)
  2480. except netapp_api.NaApiError as e:
  2481. if e.code == netapp_api.EOBJECTNOTFOUND:
  2482. return
  2483. raise
  2484. @na_utils.trace
  2485. def rename_nfs_export_policy(self, policy_name, new_policy_name):
  2486. api_args = {
  2487. 'policy-name': policy_name,
  2488. 'new-policy-name': new_policy_name
  2489. }
  2490. self.send_request('export-policy-rename', api_args)
  2491. @na_utils.trace
  2492. def prune_deleted_nfs_export_policies(self):
  2493. deleted_policy_map = self._get_deleted_nfs_export_policies()
  2494. for vserver in deleted_policy_map:
  2495. client = copy.deepcopy(self)
  2496. client.set_vserver(vserver)
  2497. for policy in deleted_policy_map[vserver]:
  2498. try:
  2499. client.delete_nfs_export_policy(policy)
  2500. except netapp_api.NaApiError:
  2501. LOG.debug('Could not delete export policy %s.', policy)
  2502. @na_utils.trace
  2503. def _get_deleted_nfs_export_policies(self):
  2504. api_args = {
  2505. 'query': {
  2506. 'export-policy-info': {
  2507. 'policy-name': DELETED_PREFIX + '*',
  2508. },
  2509. },
  2510. 'desired-attributes': {
  2511. 'export-policy-info': {
  2512. 'policy-name': None,
  2513. 'vserver': None,
  2514. },
  2515. },
  2516. }
  2517. result = self.send_iter_request('export-policy-get-iter', api_args)
  2518. attributes_list = result.get_child_by_name(
  2519. 'attributes-list') or netapp_api.NaElement('none')
  2520. policy_map = {}
  2521. for export_info in attributes_list.get_children():
  2522. vserver = export_info.get_child_content('vserver')
  2523. policies = policy_map.get(vserver, [])
  2524. policies.append(export_info.get_child_content('policy-name'))
  2525. policy_map[vserver] = policies
  2526. return policy_map
  2527. @na_utils.trace
  2528. def _get_ems_log_destination_vserver(self):
  2529. """Returns the best vserver destination for EMS messages."""
  2530. major, minor = self.get_ontapi_version(cached=True)
  2531. if (major > 1) or (major == 1 and minor > 15):
  2532. # Prefer admin Vserver (requires cluster credentials).
  2533. admin_vservers = self.list_vservers(vserver_type='admin')
  2534. if admin_vservers:
  2535. return admin_vservers[0]
  2536. # Fall back to data Vserver.
  2537. data_vservers = self.list_vservers(vserver_type='data')
  2538. if data_vservers:
  2539. return data_vservers[0]
  2540. # If older API version, or no other Vservers found, use node Vserver.
  2541. node_vservers = self.list_vservers(vserver_type='node')
  2542. if node_vservers:
  2543. return node_vservers[0]
  2544. raise exception.NotFound("No Vserver found to receive EMS messages.")
  2545. @na_utils.trace
  2546. def send_ems_log_message(self, message_dict):
  2547. """Sends a message to the Data ONTAP EMS log."""
  2548. # NOTE(cknight): Cannot use deepcopy on the connection context
  2549. node_client = copy.copy(self)
  2550. node_client.connection = copy.copy(self.connection)
  2551. node_client.connection.set_timeout(25)
  2552. try:
  2553. node_client.set_vserver(self._get_ems_log_destination_vserver())
  2554. node_client.send_request('ems-autosupport-log', message_dict)
  2555. LOG.debug('EMS executed successfully.')
  2556. except netapp_api.NaApiError as e:
  2557. LOG.warning('Failed to invoke EMS. %s', e)
  2558. @na_utils.trace
  2559. def get_aggregate(self, aggregate_name):
  2560. """Get aggregate attributes needed for the storage service catalog."""
  2561. if not aggregate_name:
  2562. return {}
  2563. desired_attributes = {
  2564. 'aggr-attributes': {
  2565. 'aggregate-name': None,
  2566. 'aggr-raid-attributes': {
  2567. 'raid-type': None,
  2568. 'is-hybrid': None,
  2569. },
  2570. },
  2571. }
  2572. try:
  2573. aggrs = self._get_aggregates(aggregate_names=[aggregate_name],
  2574. desired_attributes=desired_attributes)
  2575. except netapp_api.NaApiError:
  2576. msg = _('Failed to get info for aggregate %s.')
  2577. LOG.exception(msg, aggregate_name)
  2578. return {}
  2579. if len(aggrs) < 1:
  2580. return {}
  2581. aggr_attributes = aggrs[0]
  2582. aggr_raid_attrs = aggr_attributes.get_child_by_name(
  2583. 'aggr-raid-attributes') or netapp_api.NaElement('none')
  2584. aggregate = {
  2585. 'name': aggr_attributes.get_child_content('aggregate-name'),
  2586. 'raid-type': aggr_raid_attrs.get_child_content('raid-type'),
  2587. 'is-hybrid': strutils.bool_from_string(
  2588. aggr_raid_attrs.get_child_content('is-hybrid')),
  2589. }
  2590. return aggregate
  2591. @na_utils.trace
  2592. def get_aggregate_disk_types(self, aggregate_name):
  2593. """Get the disk type(s) of an aggregate."""
  2594. disk_types = set()
  2595. disk_types.update(self._get_aggregate_disk_types(aggregate_name))
  2596. if self.features.ADVANCED_DISK_PARTITIONING:
  2597. disk_types.update(self._get_aggregate_disk_types(aggregate_name,
  2598. shared=True))
  2599. return list(disk_types) if disk_types else None
  2600. @na_utils.trace
  2601. def _get_aggregate_disk_types(self, aggregate_name, shared=False):
  2602. """Get the disk type(s) of an aggregate."""
  2603. disk_types = set()
  2604. if shared:
  2605. disk_raid_info = {
  2606. 'disk-shared-info': {
  2607. 'aggregate-list': {
  2608. 'shared-aggregate-info': {
  2609. 'aggregate-name': aggregate_name,
  2610. },
  2611. },
  2612. },
  2613. }
  2614. else:
  2615. disk_raid_info = {
  2616. 'disk-aggregate-info': {
  2617. 'aggregate-name': aggregate_name,
  2618. },
  2619. }
  2620. api_args = {
  2621. 'query': {
  2622. 'storage-disk-info': {
  2623. 'disk-raid-info': disk_raid_info,
  2624. },
  2625. },
  2626. 'desired-attributes': {
  2627. 'storage-disk-info': {
  2628. 'disk-raid-info': {
  2629. 'effective-disk-type': None,
  2630. },
  2631. },
  2632. },
  2633. }
  2634. try:
  2635. result = self.send_iter_request('storage-disk-get-iter', api_args)
  2636. except netapp_api.NaApiError:
  2637. msg = _('Failed to get disk info for aggregate %s.')
  2638. LOG.exception(msg, aggregate_name)
  2639. return disk_types
  2640. attributes_list = result.get_child_by_name(
  2641. 'attributes-list') or netapp_api.NaElement('none')
  2642. for storage_disk_info in attributes_list.get_children():
  2643. disk_raid_info = storage_disk_info.get_child_by_name(
  2644. 'disk-raid-info') or netapp_api.NaElement('none')
  2645. disk_type = disk_raid_info.get_child_content(
  2646. 'effective-disk-type')
  2647. if disk_type:
  2648. disk_types.add(disk_type)
  2649. return disk_types
  2650. @na_utils.trace
  2651. def check_for_cluster_credentials(self):
  2652. try:
  2653. self.list_cluster_nodes()
  2654. # API succeeded, so definitely a cluster management LIF
  2655. return True
  2656. except netapp_api.NaApiError as e:
  2657. if e.code == netapp_api.EAPINOTFOUND:
  2658. LOG.debug('Not connected to cluster management LIF.')
  2659. return False
  2660. else:
  2661. raise
  2662. @na_utils.trace
  2663. def create_cluster_peer(self, addresses, username=None, password=None,
  2664. passphrase=None):
  2665. """Creates a cluster peer relationship."""
  2666. api_args = {
  2667. 'peer-addresses': [
  2668. {'remote-inet-address': address} for address in addresses
  2669. ],
  2670. }
  2671. if username:
  2672. api_args['user-name'] = username
  2673. if password:
  2674. api_args['password'] = password
  2675. if passphrase:
  2676. api_args['passphrase'] = passphrase
  2677. self.send_request('cluster-peer-create', api_args)
  2678. @na_utils.trace
  2679. def get_cluster_peers(self, remote_cluster_name=None):
  2680. """Gets one or more cluster peer relationships."""
  2681. api_args = {}
  2682. if remote_cluster_name:
  2683. api_args['query'] = {
  2684. 'cluster-peer-info': {
  2685. 'remote-cluster-name': remote_cluster_name,
  2686. }
  2687. }
  2688. result = self.send_iter_request('cluster-peer-get-iter', api_args)
  2689. if not self._has_records(result):
  2690. return []
  2691. cluster_peers = []
  2692. for cluster_peer_info in result.get_child_by_name(
  2693. 'attributes-list').get_children():
  2694. cluster_peer = {
  2695. 'active-addresses': [],
  2696. 'peer-addresses': []
  2697. }
  2698. active_addresses = cluster_peer_info.get_child_by_name(
  2699. 'active-addresses') or netapp_api.NaElement('none')
  2700. for address in active_addresses.get_children():
  2701. cluster_peer['active-addresses'].append(address.get_content())
  2702. peer_addresses = cluster_peer_info.get_child_by_name(
  2703. 'peer-addresses') or netapp_api.NaElement('none')
  2704. for address in peer_addresses.get_children():
  2705. cluster_peer['peer-addresses'].append(address.get_content())
  2706. cluster_peer['availability'] = cluster_peer_info.get_child_content(
  2707. 'availability')
  2708. cluster_peer['cluster-name'] = cluster_peer_info.get_child_content(
  2709. 'cluster-name')
  2710. cluster_peer['cluster-uuid'] = cluster_peer_info.get_child_content(
  2711. 'cluster-uuid')
  2712. cluster_peer['remote-cluster-name'] = (
  2713. cluster_peer_info.get_child_content('remote-cluster-name'))
  2714. cluster_peer['serial-number'] = (
  2715. cluster_peer_info.get_child_content('serial-number'))
  2716. cluster_peer['timeout'] = cluster_peer_info.get_child_content(
  2717. 'timeout')
  2718. cluster_peers.append(cluster_peer)
  2719. return cluster_peers
  2720. @na_utils.trace
  2721. def delete_cluster_peer(self, cluster_name):
  2722. """Deletes a cluster peer relationship."""
  2723. api_args = {'cluster-name': cluster_name}
  2724. self.send_request('cluster-peer-delete', api_args)
  2725. @na_utils.trace
  2726. def get_cluster_peer_policy(self):
  2727. """Gets the cluster peering policy configuration."""
  2728. if not self.features.CLUSTER_PEER_POLICY:
  2729. return {}
  2730. result = self.send_request('cluster-peer-policy-get')
  2731. attributes = result.get_child_by_name(
  2732. 'attributes') or netapp_api.NaElement('none')
  2733. cluster_peer_policy = attributes.get_child_by_name(
  2734. 'cluster-peer-policy') or netapp_api.NaElement('none')
  2735. policy = {
  2736. 'is-unauthenticated-access-permitted':
  2737. cluster_peer_policy.get_child_content(
  2738. 'is-unauthenticated-access-permitted'),
  2739. 'passphrase-minimum-length':
  2740. cluster_peer_policy.get_child_content(
  2741. 'passphrase-minimum-length'),
  2742. }
  2743. if policy['is-unauthenticated-access-permitted'] is not None:
  2744. policy['is-unauthenticated-access-permitted'] = (
  2745. strutils.bool_from_string(
  2746. policy['is-unauthenticated-access-permitted']))
  2747. if policy['passphrase-minimum-length'] is not None:
  2748. policy['passphrase-minimum-length'] = int(
  2749. policy['passphrase-minimum-length'])
  2750. return policy
  2751. @na_utils.trace
  2752. def set_cluster_peer_policy(self, is_unauthenticated_access_permitted=None,
  2753. passphrase_minimum_length=None):
  2754. """Modifies the cluster peering policy configuration."""
  2755. if not self.features.CLUSTER_PEER_POLICY:
  2756. return
  2757. if (is_unauthenticated_access_permitted is None and
  2758. passphrase_minimum_length is None):
  2759. return
  2760. api_args = {}
  2761. if is_unauthenticated_access_permitted is not None:
  2762. api_args['is-unauthenticated-access-permitted'] = (
  2763. 'true' if strutils.bool_from_string(
  2764. is_unauthenticated_access_permitted) else 'false')
  2765. if passphrase_minimum_length is not None:
  2766. api_args['passphrase-minlength'] = six.text_type(
  2767. passphrase_minimum_length)
  2768. self.send_request('cluster-peer-policy-modify', api_args)
  2769. @na_utils.trace
  2770. def create_vserver_peer(self, vserver_name, peer_vserver_name):
  2771. """Creates a Vserver peer relationship for SnapMirrors."""
  2772. api_args = {
  2773. 'vserver': vserver_name,
  2774. 'peer-vserver': peer_vserver_name,
  2775. 'applications': [
  2776. {'vserver-peer-application': 'snapmirror'},
  2777. ],
  2778. }
  2779. self.send_request('vserver-peer-create', api_args)
  2780. @na_utils.trace
  2781. def delete_vserver_peer(self, vserver_name, peer_vserver_name):
  2782. """Deletes a Vserver peer relationship."""
  2783. api_args = {'vserver': vserver_name, 'peer-vserver': peer_vserver_name}
  2784. self.send_request('vserver-peer-delete', api_args)
  2785. @na_utils.trace
  2786. def accept_vserver_peer(self, vserver_name, peer_vserver_name):
  2787. """Accepts a pending Vserver peer relationship."""
  2788. api_args = {'vserver': vserver_name, 'peer-vserver': peer_vserver_name}
  2789. self.send_request('vserver-peer-accept', api_args)
  2790. @na_utils.trace
  2791. def get_vserver_peers(self, vserver_name=None, peer_vserver_name=None):
  2792. """Gets one or more Vserver peer relationships."""
  2793. api_args = None
  2794. if vserver_name or peer_vserver_name:
  2795. api_args = {'query': {'vserver-peer-info': {}}}
  2796. if vserver_name:
  2797. api_args['query']['vserver-peer-info']['vserver'] = (
  2798. vserver_name)
  2799. if peer_vserver_name:
  2800. api_args['query']['vserver-peer-info']['peer-vserver'] = (
  2801. peer_vserver_name)
  2802. result = self.send_iter_request('vserver-peer-get-iter', api_args)
  2803. if not self._has_records(result):
  2804. return []
  2805. vserver_peers = []
  2806. for vserver_peer_info in result.get_child_by_name(
  2807. 'attributes-list').get_children():
  2808. vserver_peer = {
  2809. 'vserver': vserver_peer_info.get_child_content('vserver'),
  2810. 'peer-vserver':
  2811. vserver_peer_info.get_child_content('peer-vserver'),
  2812. 'peer-state':
  2813. vserver_peer_info.get_child_content('peer-state'),
  2814. 'peer-cluster':
  2815. vserver_peer_info.get_child_content('peer-cluster'),
  2816. }
  2817. vserver_peers.append(vserver_peer)
  2818. return vserver_peers
  2819. def _ensure_snapmirror_v2(self):
  2820. """Verify support for SnapMirror control plane v2."""
  2821. if not self.features.SNAPMIRROR_V2:
  2822. msg = _('SnapMirror features require Data ONTAP 8.2 or later.')
  2823. raise exception.NetAppException(msg)
  2824. @na_utils.trace
  2825. def create_snapmirror(self, source_vserver, source_volume,
  2826. destination_vserver, destination_volume,
  2827. schedule=None, policy=None,
  2828. relationship_type='data_protection'):
  2829. """Creates a SnapMirror relationship (cDOT 8.2 or later only)."""
  2830. self._ensure_snapmirror_v2()
  2831. api_args = {
  2832. 'source-volume': source_volume,
  2833. 'source-vserver': source_vserver,
  2834. 'destination-volume': destination_volume,
  2835. 'destination-vserver': destination_vserver,
  2836. 'relationship-type': relationship_type,
  2837. }
  2838. if schedule:
  2839. api_args['schedule'] = schedule
  2840. if policy:
  2841. api_args['policy'] = policy
  2842. try:
  2843. self.send_request('snapmirror-create', api_args)
  2844. except netapp_api.NaApiError as e:
  2845. if e.code != netapp_api.ERELATION_EXISTS:
  2846. raise
  2847. @na_utils.trace
  2848. def initialize_snapmirror(self, source_vserver, source_volume,
  2849. destination_vserver, destination_volume,
  2850. source_snapshot=None, transfer_priority=None):
  2851. """Initializes a SnapMirror relationship (cDOT 8.2 or later only)."""
  2852. self._ensure_snapmirror_v2()
  2853. api_args = {
  2854. 'source-volume': source_volume,
  2855. 'source-vserver': source_vserver,
  2856. 'destination-volume': destination_volume,
  2857. 'destination-vserver': destination_vserver,
  2858. }
  2859. if source_snapshot:
  2860. api_args['source-snapshot'] = source_snapshot
  2861. if transfer_priority:
  2862. api_args['transfer-priority'] = transfer_priority
  2863. result = self.send_request('snapmirror-initialize', api_args)
  2864. result_info = {}
  2865. result_info['operation-id'] = result.get_child_content(
  2866. 'result-operation-id')
  2867. result_info['status'] = result.get_child_content('result-status')
  2868. result_info['jobid'] = result.get_child_content('result-jobid')
  2869. result_info['error-code'] = result.get_child_content(
  2870. 'result-error-code')
  2871. result_info['error-message'] = result.get_child_content(
  2872. 'result-error-message')
  2873. return result_info
  2874. @na_utils.trace
  2875. def release_snapmirror(self, source_vserver, source_volume,
  2876. destination_vserver, destination_volume,
  2877. relationship_info_only=False):
  2878. """Removes a SnapMirror relationship on the source endpoint."""
  2879. self._ensure_snapmirror_v2()
  2880. api_args = {
  2881. 'query': {
  2882. 'snapmirror-destination-info': {
  2883. 'source-volume': source_volume,
  2884. 'source-vserver': source_vserver,
  2885. 'destination-volume': destination_volume,
  2886. 'destination-vserver': destination_vserver,
  2887. 'relationship-info-only': ('true' if relationship_info_only
  2888. else 'false'),
  2889. }
  2890. }
  2891. }
  2892. self.send_request('snapmirror-release-iter', api_args)
  2893. @na_utils.trace
  2894. def quiesce_snapmirror(self, source_vserver, source_volume,
  2895. destination_vserver, destination_volume):
  2896. """Disables future transfers to a SnapMirror destination."""
  2897. self._ensure_snapmirror_v2()
  2898. api_args = {
  2899. 'source-volume': source_volume,
  2900. 'source-vserver': source_vserver,
  2901. 'destination-volume': destination_volume,
  2902. 'destination-vserver': destination_vserver,
  2903. }
  2904. self.send_request('snapmirror-quiesce', api_args)
  2905. @na_utils.trace
  2906. def abort_snapmirror(self, source_vserver, source_volume,
  2907. destination_vserver, destination_volume,
  2908. clear_checkpoint=False):
  2909. """Stops ongoing transfers for a SnapMirror relationship."""
  2910. self._ensure_snapmirror_v2()
  2911. api_args = {
  2912. 'source-volume': source_volume,
  2913. 'source-vserver': source_vserver,
  2914. 'destination-volume': destination_volume,
  2915. 'destination-vserver': destination_vserver,
  2916. 'clear-checkpoint': 'true' if clear_checkpoint else 'false',
  2917. }
  2918. try:
  2919. self.send_request('snapmirror-abort', api_args)
  2920. except netapp_api.NaApiError as e:
  2921. if e.code != netapp_api.ENOTRANSFER_IN_PROGRESS:
  2922. raise
  2923. @na_utils.trace
  2924. def break_snapmirror(self, source_vserver, source_volume,
  2925. destination_vserver, destination_volume):
  2926. """Breaks a data protection SnapMirror relationship."""
  2927. self._ensure_snapmirror_v2()
  2928. api_args = {
  2929. 'source-volume': source_volume,
  2930. 'source-vserver': source_vserver,
  2931. 'destination-volume': destination_volume,
  2932. 'destination-vserver': destination_vserver,
  2933. }
  2934. self.send_request('snapmirror-break', api_args)
  2935. @na_utils.trace
  2936. def modify_snapmirror(self, source_vserver, source_volume,
  2937. destination_vserver, destination_volume,
  2938. schedule=None, policy=None, tries=None,
  2939. max_transfer_rate=None):
  2940. """Modifies a SnapMirror relationship."""
  2941. self._ensure_snapmirror_v2()
  2942. api_args = {
  2943. 'source-volume': source_volume,
  2944. 'source-vserver': source_vserver,
  2945. 'destination-volume': destination_volume,
  2946. 'destination-vserver': destination_vserver,
  2947. }
  2948. if schedule:
  2949. api_args['schedule'] = schedule
  2950. if policy:
  2951. api_args['policy'] = policy
  2952. if tries is not None:
  2953. api_args['tries'] = tries
  2954. if max_transfer_rate is not None:
  2955. api_args['max-transfer-rate'] = max_transfer_rate
  2956. self.send_request('snapmirror-modify', api_args)
  2957. @na_utils.trace
  2958. def delete_snapmirror(self, source_vserver, source_volume,
  2959. destination_vserver, destination_volume):
  2960. """Destroys a SnapMirror relationship."""
  2961. self._ensure_snapmirror_v2()
  2962. api_args = {
  2963. 'query': {
  2964. 'snapmirror-info': {
  2965. 'source-volume': source_volume,
  2966. 'source-vserver': source_vserver,
  2967. 'destination-volume': destination_volume,
  2968. 'destination-vserver': destination_vserver,
  2969. }
  2970. }
  2971. }
  2972. self.send_request('snapmirror-destroy-iter', api_args)
  2973. @na_utils.trace
  2974. def update_snapmirror(self, source_vserver, source_volume,
  2975. destination_vserver, destination_volume):
  2976. """Schedules a snapmirror update."""
  2977. self._ensure_snapmirror_v2()
  2978. api_args = {
  2979. 'source-volume': source_volume,
  2980. 'source-vserver': source_vserver,
  2981. 'destination-volume': destination_volume,
  2982. 'destination-vserver': destination_vserver,
  2983. }
  2984. try:
  2985. self.send_request('snapmirror-update', api_args)
  2986. except netapp_api.NaApiError as e:
  2987. if (e.code != netapp_api.ETRANSFER_IN_PROGRESS and
  2988. e.code != netapp_api.EANOTHER_OP_ACTIVE):
  2989. raise
  2990. @na_utils.trace
  2991. def resume_snapmirror(self, source_vserver, source_volume,
  2992. destination_vserver, destination_volume):
  2993. """Resume a SnapMirror relationship if it is quiesced."""
  2994. self._ensure_snapmirror_v2()
  2995. api_args = {
  2996. 'source-volume': source_volume,
  2997. 'source-vserver': source_vserver,
  2998. 'destination-volume': destination_volume,
  2999. 'destination-vserver': destination_vserver,
  3000. }
  3001. try:
  3002. self.send_request('snapmirror-resume', api_args)
  3003. except netapp_api.NaApiError as e:
  3004. if e.code != netapp_api.ERELATION_NOT_QUIESCED:
  3005. raise
  3006. @na_utils.trace
  3007. def resync_snapmirror(self, source_vserver, source_volume,
  3008. destination_vserver, destination_volume):
  3009. """Resync a SnapMirror relationship."""
  3010. self._ensure_snapmirror_v2()
  3011. api_args = {
  3012. 'source-volume': source_volume,
  3013. 'source-vserver': source_vserver,
  3014. 'destination-volume': destination_volume,
  3015. 'destination-vserver': destination_vserver,
  3016. }
  3017. self.send_request('snapmirror-resync', api_args)
  3018. @na_utils.trace
  3019. def _get_snapmirrors(self, source_vserver=None, source_volume=None,
  3020. destination_vserver=None, destination_volume=None,
  3021. desired_attributes=None):
  3022. query = None
  3023. if (source_vserver or source_volume or destination_vserver or
  3024. destination_volume):
  3025. query = {'snapmirror-info': {}}
  3026. if source_volume:
  3027. query['snapmirror-info']['source-volume'] = source_volume
  3028. if destination_volume:
  3029. query['snapmirror-info']['destination-volume'] = (
  3030. destination_volume)
  3031. if source_vserver:
  3032. query['snapmirror-info']['source-vserver'] = source_vserver
  3033. if destination_vserver:
  3034. query['snapmirror-info']['destination-vserver'] = (
  3035. destination_vserver)
  3036. api_args = {}
  3037. if query:
  3038. api_args['query'] = query
  3039. if desired_attributes:
  3040. api_args['desired-attributes'] = desired_attributes
  3041. result = self.send_iter_request('snapmirror-get-iter', api_args)
  3042. if not self._has_records(result):
  3043. return []
  3044. else:
  3045. return result.get_child_by_name('attributes-list').get_children()
  3046. @na_utils.trace
  3047. def get_snapmirrors(self, source_vserver, source_volume,
  3048. destination_vserver, destination_volume,
  3049. desired_attributes=None):
  3050. """Gets one or more SnapMirror relationships.
  3051. Either the source or destination info may be omitted.
  3052. Desired attributes should be a flat list of attribute names.
  3053. """
  3054. self._ensure_snapmirror_v2()
  3055. if desired_attributes is not None:
  3056. desired_attributes = {
  3057. 'snapmirror-info': {attr: None for attr in desired_attributes},
  3058. }
  3059. result = self._get_snapmirrors(
  3060. source_vserver=source_vserver,
  3061. source_volume=source_volume,
  3062. destination_vserver=destination_vserver,
  3063. destination_volume=destination_volume,
  3064. desired_attributes=desired_attributes)
  3065. snapmirrors = []
  3066. for snapmirror_info in result:
  3067. snapmirror = {}
  3068. for child in snapmirror_info.get_children():
  3069. name = self._strip_xml_namespace(child.get_name())
  3070. snapmirror[name] = child.get_content()
  3071. snapmirrors.append(snapmirror)
  3072. return snapmirrors
  3073. def volume_has_snapmirror_relationships(self, volume):
  3074. """Return True if snapmirror relationships exist for a given volume.
  3075. If we have snapmirror control plane license, we can verify whether
  3076. the given volume is part of any snapmirror relationships.
  3077. """
  3078. try:
  3079. # Check if volume is a source snapmirror volume
  3080. snapmirrors = self.get_snapmirrors(
  3081. volume['owning-vserver-name'], volume['name'], None, None)
  3082. # Check if volume is a destination snapmirror volume
  3083. if not snapmirrors:
  3084. snapmirrors = self.get_snapmirrors(
  3085. None, None, volume['owning-vserver-name'], volume['name'])
  3086. has_snapmirrors = len(snapmirrors) > 0
  3087. except netapp_api.NaApiError:
  3088. msg = ("Could not determine if volume %s is part of "
  3089. "existing snapmirror relationships.")
  3090. LOG.exception(msg, volume['name'])
  3091. has_snapmirrors = False
  3092. return has_snapmirrors
  3093. def list_snapmirror_snapshots(self, volume_name, newer_than=None):
  3094. """Gets SnapMirror snapshots on a volume."""
  3095. api_args = {
  3096. 'query': {
  3097. 'snapshot-info': {
  3098. 'dependency': 'snapmirror',
  3099. 'volume': volume_name,
  3100. },
  3101. },
  3102. }
  3103. if newer_than:
  3104. api_args['query']['snapshot-info'][
  3105. 'access-time'] = '>' + newer_than
  3106. result = self.send_iter_request('snapshot-get-iter', api_args)
  3107. attributes_list = result.get_child_by_name(
  3108. 'attributes-list') or netapp_api.NaElement('none')
  3109. return [snapshot_info.get_child_content('name')
  3110. for snapshot_info in attributes_list.get_children()]
  3111. @na_utils.trace
  3112. def start_volume_move(self, volume_name, vserver, destination_aggregate,
  3113. cutover_action='wait', encrypt_destination=None):
  3114. """Moves a FlexVol across Vserver aggregates.
  3115. Requires cluster-scoped credentials.
  3116. """
  3117. self._send_volume_move_request(
  3118. volume_name, vserver,
  3119. destination_aggregate,
  3120. cutover_action=cutover_action,
  3121. encrypt_destination=encrypt_destination)
  3122. @na_utils.trace
  3123. def check_volume_move(self, volume_name, vserver, destination_aggregate,
  3124. encrypt_destination=None):
  3125. """Moves a FlexVol across Vserver aggregates.
  3126. Requires cluster-scoped credentials.
  3127. """
  3128. self._send_volume_move_request(
  3129. volume_name,
  3130. vserver,
  3131. destination_aggregate,
  3132. validation_only=True,
  3133. encrypt_destination=encrypt_destination)
  3134. @na_utils.trace
  3135. def _send_volume_move_request(self, volume_name, vserver,
  3136. destination_aggregate,
  3137. cutover_action='wait',
  3138. validation_only=False,
  3139. encrypt_destination=None):
  3140. """Send request to check if vol move is possible, or start it.
  3141. :param volume_name: Name of the FlexVol to be moved.
  3142. :param destination_aggregate: Name of the destination aggregate
  3143. :param cutover_action: can have one of ['force', 'defer', 'abort',
  3144. 'wait']. 'force' will force a cutover despite errors (causing
  3145. possible client disruptions), 'wait' will wait for cutover to be
  3146. triggered manually. 'abort' will rollback move on errors on
  3147. cutover, 'defer' will attempt a cutover, but wait for manual
  3148. intervention in case of errors.
  3149. :param validation_only: If set to True, only validates if the volume
  3150. move is possible, does not trigger data copy.
  3151. :param encrypt_destination: If set to True, it encrypts the Flexvol
  3152. after the volume move is complete.
  3153. """
  3154. api_args = {
  3155. 'source-volume': volume_name,
  3156. 'vserver': vserver,
  3157. 'dest-aggr': destination_aggregate,
  3158. 'cutover-action': CUTOVER_ACTION_MAP[cutover_action],
  3159. }
  3160. if self.features.FLEXVOL_ENCRYPTION and encrypt_destination:
  3161. api_args['encrypt-destination'] = 'true'
  3162. elif encrypt_destination:
  3163. msg = 'Flexvol encryption is not supported on this backend.'
  3164. raise exception.NetAppException(msg)
  3165. else:
  3166. api_args['encrypt-destination'] = 'false'
  3167. if validation_only:
  3168. api_args['perform-validation-only'] = 'true'
  3169. self.send_request('volume-move-start', api_args)
  3170. @na_utils.trace
  3171. def abort_volume_move(self, volume_name, vserver):
  3172. """Aborts an existing volume move operation."""
  3173. api_args = {
  3174. 'source-volume': volume_name,
  3175. 'vserver': vserver,
  3176. }
  3177. self.send_request('volume-move-trigger-abort', api_args)
  3178. @na_utils.trace
  3179. def trigger_volume_move_cutover(self, volume_name, vserver, force=True):
  3180. """Triggers the cut-over for a volume in data motion."""
  3181. api_args = {
  3182. 'source-volume': volume_name,
  3183. 'vserver': vserver,
  3184. 'force': 'true' if force else 'false',
  3185. }
  3186. self.send_request('volume-move-trigger-cutover', api_args)
  3187. @na_utils.trace
  3188. def get_volume_move_status(self, volume_name, vserver):
  3189. """Gets the current state of a volume move operation."""
  3190. api_args = {
  3191. 'query': {
  3192. 'volume-move-info': {
  3193. 'volume': volume_name,
  3194. 'vserver': vserver,
  3195. },
  3196. },
  3197. 'desired-attributes': {
  3198. 'volume-move-info': {
  3199. 'percent-complete': None,
  3200. 'estimated-completion-time': None,
  3201. 'state': None,
  3202. 'details': None,
  3203. 'cutover-action': None,
  3204. 'phase': None,
  3205. },
  3206. },
  3207. }
  3208. result = self.send_iter_request('volume-move-get-iter', api_args)
  3209. if not self._has_records(result):
  3210. msg = _("Volume %(vol)s in Vserver %(server)s is not part of any "
  3211. "data motion operations.")
  3212. msg_args = {'vol': volume_name, 'server': vserver}
  3213. raise exception.NetAppException(msg % msg_args)
  3214. attributes_list = result.get_child_by_name(
  3215. 'attributes-list') or netapp_api.NaElement('none')
  3216. volume_move_info = attributes_list.get_child_by_name(
  3217. 'volume-move-info') or netapp_api.NaElement('none')
  3218. status_info = {
  3219. 'percent-complete': volume_move_info.get_child_content(
  3220. 'percent-complete'),
  3221. 'estimated-completion-time': volume_move_info.get_child_content(
  3222. 'estimated-completion-time'),
  3223. 'state': volume_move_info.get_child_content('state'),
  3224. 'details': volume_move_info.get_child_content('details'),
  3225. 'cutover-action': volume_move_info.get_child_content(
  3226. 'cutover-action'),
  3227. 'phase': volume_move_info.get_child_content('phase'),
  3228. }
  3229. return status_info
  3230. @na_utils.trace
  3231. def qos_policy_group_exists(self, qos_policy_group_name):
  3232. """Checks if a QoS policy group exists."""
  3233. try:
  3234. self.qos_policy_group_get(qos_policy_group_name)
  3235. except exception.NetAppException:
  3236. return False
  3237. return True
  3238. @na_utils.trace
  3239. def qos_policy_group_get(self, qos_policy_group_name):
  3240. """Checks if a QoS policy group exists."""
  3241. api_args = {
  3242. 'query': {
  3243. 'qos-policy-group-info': {
  3244. 'policy-group': qos_policy_group_name,
  3245. },
  3246. },
  3247. 'desired-attributes': {
  3248. 'qos-policy-group-info': {
  3249. 'policy-group': None,
  3250. 'vserver': None,
  3251. 'max-throughput': None,
  3252. 'num-workloads': None
  3253. },
  3254. },
  3255. }
  3256. try:
  3257. result = self.send_request('qos-policy-group-get-iter',
  3258. api_args,
  3259. False)
  3260. except netapp_api.NaApiError as e:
  3261. if e.code == netapp_api.EAPINOTFOUND:
  3262. msg = _("Configured ONTAP login user cannot retrieve "
  3263. "QoS policies.")
  3264. LOG.error(msg)
  3265. raise exception.NetAppException(msg)
  3266. else:
  3267. raise
  3268. if not self._has_records(result):
  3269. msg = _("No QoS policy group found with name %s.")
  3270. raise exception.NetAppException(msg % qos_policy_group_name)
  3271. attributes_list = result.get_child_by_name(
  3272. 'attributes-list') or netapp_api.NaElement('none')
  3273. qos_policy_group_info = attributes_list.get_child_by_name(
  3274. 'qos-policy-group-info') or netapp_api.NaElement('none')
  3275. policy_info = {
  3276. 'policy-group': qos_policy_group_info.get_child_content(
  3277. 'policy-group'),
  3278. 'vserver': qos_policy_group_info.get_child_content('vserver'),
  3279. 'max-throughput': qos_policy_group_info.get_child_content(
  3280. 'max-throughput'),
  3281. 'num-workloads': int(qos_policy_group_info.get_child_content(
  3282. 'num-workloads')),
  3283. }
  3284. return policy_info
  3285. @na_utils.trace
  3286. def qos_policy_group_create(self, qos_policy_group_name, vserver,
  3287. max_throughput=None):
  3288. """Creates a QoS policy group."""
  3289. api_args = {
  3290. 'policy-group': qos_policy_group_name,
  3291. 'vserver': vserver,
  3292. }
  3293. if max_throughput:
  3294. api_args['max-throughput'] = max_throughput
  3295. return self.send_request('qos-policy-group-create', api_args, False)
  3296. @na_utils.trace
  3297. def qos_policy_group_modify(self, qos_policy_group_name, max_throughput):
  3298. """Modifies a QoS policy group."""
  3299. api_args = {
  3300. 'policy-group': qos_policy_group_name,
  3301. 'max-throughput': max_throughput,
  3302. }
  3303. return self.send_request('qos-policy-group-modify', api_args, False)
  3304. @na_utils.trace
  3305. def qos_policy_group_delete(self, qos_policy_group_name):
  3306. """Attempts to delete a QoS policy group."""
  3307. api_args = {'policy-group': qos_policy_group_name}
  3308. return self.send_request('qos-policy-group-delete', api_args, False)
  3309. @na_utils.trace
  3310. def qos_policy_group_rename(self, qos_policy_group_name, new_name):
  3311. """Renames a QoS policy group."""
  3312. if qos_policy_group_name == new_name:
  3313. return
  3314. api_args = {
  3315. 'policy-group-name': qos_policy_group_name,
  3316. 'new-name': new_name,
  3317. }
  3318. return self.send_request('qos-policy-group-rename', api_args, False)
  3319. @na_utils.trace
  3320. def mark_qos_policy_group_for_deletion(self, qos_policy_group_name):
  3321. """Soft delete backing QoS policy group for a manila share."""
  3322. # NOTE(gouthamr): ONTAP deletes storage objects asynchronously. As
  3323. # long as garbage collection hasn't occurred, assigned QoS policy may
  3324. # still be tagged "in use". So, we rename the QoS policy group using a
  3325. # specific pattern and later attempt on a best effort basis to
  3326. # delete any QoS policy groups matching that pattern.
  3327. if self.qos_policy_group_exists(qos_policy_group_name):
  3328. new_name = DELETED_PREFIX + qos_policy_group_name
  3329. try:
  3330. self.qos_policy_group_rename(qos_policy_group_name, new_name)
  3331. except netapp_api.NaApiError as ex:
  3332. msg = ('Rename failure in cleanup of cDOT QoS policy '
  3333. 'group %(name)s: %(ex)s')
  3334. msg_args = {'name': qos_policy_group_name, 'ex': ex}
  3335. LOG.warning(msg, msg_args)
  3336. # Attempt to delete any QoS policies named "deleted_manila-*".
  3337. self.remove_unused_qos_policy_groups()
  3338. @na_utils.trace
  3339. def remove_unused_qos_policy_groups(self):
  3340. """Deletes all QoS policy groups that are marked for deletion."""
  3341. api_args = {
  3342. 'query': {
  3343. 'qos-policy-group-info': {
  3344. 'policy-group': '%s*' % DELETED_PREFIX,
  3345. }
  3346. },
  3347. 'max-records': 3500,
  3348. 'continue-on-failure': 'true',
  3349. 'return-success-list': 'false',
  3350. 'return-failure-list': 'false',
  3351. }
  3352. try:
  3353. self.send_request('qos-policy-group-delete-iter', api_args, False)
  3354. except netapp_api.NaApiError as ex:
  3355. msg = 'Could not delete QoS policy groups. Details: %(ex)s'
  3356. msg_args = {'ex': ex}
  3357. LOG.debug(msg, msg_args)
  3358. @na_utils.trace
  3359. def get_net_options(self):
  3360. result = self.send_request('net-options-get', None, False)
  3361. options = result.get_child_by_name('net-options')
  3362. ipv6_enabled = False
  3363. ipv6_info = options.get_child_by_name('ipv6-options-info')
  3364. if ipv6_info:
  3365. ipv6_enabled = ipv6_info.get_child_content('enabled') == 'true'
  3366. return {
  3367. 'ipv6-enabled': ipv6_enabled,
  3368. }