Fuel plugin that enables to configure multiple Cinder backend support for Kaminario K2 All-Flash Array
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.

kaminario_common.py 38KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893
  1. # Copyright (c) 2016 by Kaminario Technologies, Ltd.
  2. # All Rights Reserved.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  5. # not use this file except in compliance with the License. You may obtain
  6. # a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. # License for the specific language governing permissions and limitations
  14. # under the License.
  15. """Volume driver for Kaminario K2 all-flash arrays."""
  16. import math
  17. import re
  18. import threading
  19. import eventlet
  20. from oslo_config import cfg
  21. from oslo_log import log as logging
  22. from oslo_utils import importutils
  23. from oslo_utils import units
  24. from oslo_utils import versionutils
  25. import requests
  26. import six
  27. import cinder
  28. from cinder import exception
  29. from cinder.i18n import _, _LE, _LW, _LI
  30. from cinder.objects import fields
  31. from cinder import utils
  32. from cinder.volume.drivers.san import san
  33. from cinder.volume import utils as vol_utils
  34. krest = importutils.try_import("krest")
  35. K2_MIN_VERSION = '2.2.0'
  36. K2_LOCK_PREFIX = 'Kaminario'
  37. MAX_K2_RETRY = 5
  38. LOG = logging.getLogger(__name__)
  39. kaminario1_opts = [
  40. cfg.StrOpt('kaminario_nodedup_substring',
  41. default='K2-nodedup',
  42. help="If volume-type name contains this substring "
  43. "nodedup volume will be created, otherwise "
  44. "dedup volume wil be created.",
  45. deprecated_for_removal=True,
  46. deprecated_reason="This option is deprecated in favour of "
  47. "'kaminario:thin_prov_type' in extra-specs "
  48. "and will be removed in the next release.")]
  49. kaminario2_opts = [
  50. cfg.BoolOpt('auto_calc_max_oversubscription_ratio',
  51. default=False,
  52. help="K2 driver will calculate max_oversubscription_ratio "
  53. "on setting this option as True.")]
  54. CONF = cfg.CONF
  55. CONF.register_opts(kaminario1_opts)
  56. K2HTTPError = requests.exceptions.HTTPError
  57. K2_RETRY_ERRORS = ("MC_ERR_BUSY", "MC_ERR_BUSY_SPECIFIC",
  58. "MC_ERR_INPROGRESS", "MC_ERR_START_TIMEOUT")
  59. if krest:
  60. class KrestWrap(krest.EndPoint):
  61. def __init__(self, *args, **kwargs):
  62. self.krestlock = threading.Lock()
  63. super(KrestWrap, self).__init__(*args, **kwargs)
  64. def _should_retry(self, err_code, err_msg):
  65. if err_code == 400:
  66. for er in K2_RETRY_ERRORS:
  67. if er in err_msg:
  68. LOG.debug("Retry ERROR: %d with status %s",
  69. err_code, err_msg)
  70. return True
  71. return False
  72. @utils.retry(exception.KaminarioRetryableException,
  73. retries=MAX_K2_RETRY)
  74. def _request(self, method, *args, **kwargs):
  75. try:
  76. LOG.debug("running through the _request wrapper...")
  77. self.krestlock.acquire()
  78. return super(KrestWrap, self)._request(method,
  79. *args, **kwargs)
  80. except K2HTTPError as err:
  81. err_code = err.response.status_code
  82. err_msg = err.response.text
  83. if self._should_retry(err_code, err_msg):
  84. raise exception.KaminarioRetryableException(
  85. reason=six.text_type(err_msg))
  86. raise
  87. finally:
  88. self.krestlock.release()
  89. def kaminario_logger(func):
  90. """Return a function wrapper.
  91. The wrapper adds log for entry and exit to the function.
  92. """
  93. def func_wrapper(*args, **kwargs):
  94. LOG.debug('Entering %(function)s of %(class)s with arguments: '
  95. ' %(args)s, %(kwargs)s',
  96. {'class': args[0].__class__.__name__,
  97. 'function': func.__name__,
  98. 'args': args[1:],
  99. 'kwargs': kwargs})
  100. ret = func(*args, **kwargs)
  101. LOG.debug('Exiting %(function)s of %(class)s '
  102. 'having return value: %(ret)s',
  103. {'class': args[0].__class__.__name__,
  104. 'function': func.__name__,
  105. 'ret': ret})
  106. return ret
  107. return func_wrapper
  108. class Replication(object):
  109. def __init__(self, config, *args, **kwargs):
  110. self.backend_id = config.get('backend_id')
  111. self.login = config.get('login')
  112. self.password = config.get('password')
  113. self.rpo = config.get('rpo')
  114. class KaminarioCinderDriver(cinder.volume.driver.ISCSIDriver):
  115. VENDOR = "Kaminario"
  116. stats = {}
  117. def __init__(self, *args, **kwargs):
  118. super(KaminarioCinderDriver, self).__init__(*args, **kwargs)
  119. self.configuration.append_config_values(san.san_opts)
  120. self.configuration.append_config_values(kaminario2_opts)
  121. self.replica = None
  122. self._protocol = None
  123. k2_lock_sfx = self.configuration.safe_get('volume_backend_name') or ''
  124. self.k2_lock_name = "%s-%s" % (K2_LOCK_PREFIX, k2_lock_sfx)
  125. def check_for_setup_error(self):
  126. if krest is None:
  127. msg = _("Unable to import 'krest' python module.")
  128. LOG.error(msg)
  129. raise exception.KaminarioCinderDriverException(reason=msg)
  130. else:
  131. conf = self.configuration
  132. self.client = KrestWrap(conf.san_ip,
  133. conf.san_login,
  134. conf.san_password,
  135. ssl_validate=False)
  136. if self.replica:
  137. self.target = KrestWrap(self.replica.backend_id,
  138. self.replica.login,
  139. self.replica.password,
  140. ssl_validate=False)
  141. v_rs = self.client.search("system/state")
  142. if hasattr(v_rs, 'hits') and v_rs.total != 0:
  143. ver = v_rs.hits[0].rest_api_version
  144. ver_exist = versionutils.convert_version_to_int(ver)
  145. ver_min = versionutils.convert_version_to_int(K2_MIN_VERSION)
  146. if ver_exist < ver_min:
  147. msg = _("K2 rest api version should be "
  148. ">= %s.") % K2_MIN_VERSION
  149. LOG.error(msg)
  150. raise exception.KaminarioCinderDriverException(reason=msg)
  151. else:
  152. msg = _("K2 rest api version search failed.")
  153. LOG.error(msg)
  154. raise exception.KaminarioCinderDriverException(reason=msg)
  155. @kaminario_logger
  156. def _check_ops(self):
  157. """Ensure that the options we care about are set."""
  158. required_ops = ['san_ip', 'san_login', 'san_password']
  159. for attr in required_ops:
  160. if not getattr(self.configuration, attr, None):
  161. raise exception.InvalidInput(reason=_('%s is not set.') % attr)
  162. replica = self.configuration.safe_get('replication_device')
  163. if replica and isinstance(replica, list):
  164. replica_ops = ['backend_id', 'login', 'password', 'rpo']
  165. for attr in replica_ops:
  166. if attr not in replica[0]:
  167. msg = _('replication_device %s is not set.') % attr
  168. raise exception.InvalidInput(reason=msg)
  169. self.replica = Replication(replica[0])
  170. @kaminario_logger
  171. def do_setup(self, context):
  172. super(KaminarioCinderDriver, self).do_setup(context)
  173. self._check_ops()
  174. @kaminario_logger
  175. def create_volume(self, volume):
  176. """Volume creation in K2 needs a volume group.
  177. - create a volume group
  178. - create a volume in the volume group
  179. """
  180. vg_name = self.get_volume_group_name(volume.id)
  181. vol_name = self.get_volume_name(volume.id)
  182. prov_type = self._get_is_dedup(volume.get('volume_type'))
  183. try:
  184. LOG.debug("Creating volume group with name: %(name)s, "
  185. "quota: unlimited and dedup_support: %(dedup)s",
  186. {'name': vg_name, 'dedup': prov_type})
  187. vg = self.client.new("volume_groups", name=vg_name, quota=0,
  188. is_dedup=prov_type).save()
  189. LOG.debug("Creating volume with name: %(name)s, size: %(size)s "
  190. "GB, volume_group: %(vg)s",
  191. {'name': vol_name, 'size': volume.size, 'vg': vg_name})
  192. vol = self.client.new("volumes", name=vol_name,
  193. size=volume.size * units.Mi,
  194. volume_group=vg).save()
  195. except Exception as ex:
  196. vg_rs = self.client.search("volume_groups", name=vg_name)
  197. if vg_rs.total != 0:
  198. LOG.debug("Deleting vg: %s for failed volume in K2.", vg_name)
  199. vg_rs.hits[0].delete()
  200. LOG.exception(_LE("Creation of volume %s failed."), vol_name)
  201. raise exception.KaminarioCinderDriverException(
  202. reason=six.text_type(ex.message))
  203. if self._get_is_replica(volume.volume_type) and self.replica:
  204. self._create_volume_replica(volume, vg, vol, self.replica.rpo)
  205. @kaminario_logger
  206. def _create_volume_replica(self, volume, vg, vol, rpo):
  207. """Volume replica creation in K2 needs session and remote volume.
  208. - create a session
  209. - create a volume in the volume group
  210. """
  211. session_name = self.get_session_name(volume.id)
  212. rsession_name = self.get_rep_name(session_name)
  213. rvg_name = self.get_rep_name(vg.name)
  214. rvol_name = self.get_rep_name(vol.name)
  215. k2peer_rs = self.client.search("replication/peer_k2arrays",
  216. mgmt_host=self.replica.backend_id)
  217. if hasattr(k2peer_rs, 'hits') and k2peer_rs.total != 0:
  218. k2peer = k2peer_rs.hits[0]
  219. else:
  220. msg = _("Unable to find K2peer in source K2:")
  221. LOG.error(msg)
  222. raise exception.KaminarioCinderDriverException(reason=msg)
  223. try:
  224. LOG.debug("Creating source session with name: %(sname)s and "
  225. " target session name: %(tname)s",
  226. {'sname': session_name, 'tname': rsession_name})
  227. src_ssn = self.client.new("replication/sessions")
  228. src_ssn.replication_peer_k2array = k2peer
  229. src_ssn.auto_configure_peer_volumes = "False"
  230. src_ssn.local_volume_group = vg
  231. src_ssn.replication_peer_volume_group_name = rvg_name
  232. src_ssn.remote_replication_session_name = rsession_name
  233. src_ssn.name = session_name
  234. src_ssn.rpo = rpo
  235. src_ssn.save()
  236. LOG.debug("Creating remote volume with name: %s",
  237. rvol_name)
  238. self.client.new("replication/peer_volumes",
  239. local_volume=vol,
  240. name=rvol_name,
  241. replication_session=src_ssn).save()
  242. src_ssn.state = "in_sync"
  243. src_ssn.save()
  244. except Exception as ex:
  245. LOG.exception(_LE("Replication for the volume %s has "
  246. "failed."), vol.name)
  247. self._delete_by_ref(self.client, "replication/sessions",
  248. session_name, 'session')
  249. self._delete_by_ref(self.target, "replication/sessions",
  250. rsession_name, 'remote session')
  251. self._delete_by_ref(self.target, "volumes",
  252. rvol_name, 'remote volume')
  253. self._delete_by_ref(self.client, "volumes", vol.name, "volume")
  254. self._delete_by_ref(self.target, "volume_groups",
  255. rvg_name, "remote vg")
  256. self._delete_by_ref(self.client, "volume_groups", vg.name, "vg")
  257. raise exception.KaminarioCinderDriverException(
  258. reason=six.text_type(ex.message))
  259. def _delete_by_ref(self, device, url, name, msg):
  260. rs = device.search(url, name=name)
  261. for result in rs.hits:
  262. result.delete()
  263. LOG.debug("Deleting %(msg)s: %(name)s", {'msg': msg, 'name': name})
  264. @kaminario_logger
  265. def _failover_volume(self, volume):
  266. """Promoting a secondary volume to primary volume."""
  267. session_name = self.get_session_name(volume.id)
  268. rsession_name = self.get_rep_name(session_name)
  269. tgt_ssn = self.target.search("replication/sessions",
  270. name=rsession_name).hits[0]
  271. if tgt_ssn.state == 'in_sync':
  272. tgt_ssn.state = 'failed_over'
  273. tgt_ssn.save()
  274. LOG.debug("The target session: %s state is "
  275. "changed to failed_over ", rsession_name)
  276. @kaminario_logger
  277. def failover_host(self, context, volumes, secondary_id=None):
  278. """Failover to replication target."""
  279. volume_updates = []
  280. if secondary_id and secondary_id != self.replica.backend_id:
  281. LOG.error(_LE("Kaminario driver received failover_host "
  282. "request, But backend is non replicated device"))
  283. raise exception.UnableToFailOver(reason=_("Failover requested "
  284. "on non replicated "
  285. "backend."))
  286. for v in volumes:
  287. vol_name = self.get_volume_name(v['id'])
  288. rv = self.get_rep_name(vol_name)
  289. if self.target.search("volumes", name=rv).total:
  290. self._failover_volume(v)
  291. volume_updates.append(
  292. {'volume_id': v['id'],
  293. 'updates':
  294. {'replication_status':
  295. fields.ReplicationStatus.FAILED_OVER}})
  296. else:
  297. volume_updates.append({'volume_id': v['id'],
  298. 'updates': {'status': 'error', }})
  299. return self.replica.backend_id, volume_updates
  300. @kaminario_logger
  301. def create_volume_from_snapshot(self, volume, snapshot):
  302. """Create volume from snapshot.
  303. - search for snapshot and retention_policy
  304. - create a view from snapshot and attach view
  305. - create a volume and attach volume
  306. - copy data from attached view to attached volume
  307. - detach volume and view and finally delete view
  308. """
  309. snap_name = self.get_snap_name(snapshot.id)
  310. view_name = self.get_view_name(volume.id)
  311. vol_name = self.get_volume_name(volume.id)
  312. cview = src_attach_info = dest_attach_info = None
  313. rpolicy = self.get_policy()
  314. properties = utils.brick_get_connector_properties()
  315. LOG.debug("Searching for snapshot: %s in K2.", snap_name)
  316. snap_rs = self.client.search("snapshots", short_name=snap_name)
  317. if hasattr(snap_rs, 'hits') and snap_rs.total != 0:
  318. snap = snap_rs.hits[0]
  319. LOG.debug("Creating a view: %(view)s from snapshot: %(snap)s",
  320. {'view': view_name, 'snap': snap_name})
  321. try:
  322. cview = self.client.new("snapshots",
  323. short_name=view_name,
  324. source=snap, retention_policy=rpolicy,
  325. is_exposable=True).save()
  326. except Exception as ex:
  327. LOG.exception(_LE("Creating a view: %(view)s from snapshot: "
  328. "%(snap)s failed"), {"view": view_name,
  329. "snap": snap_name})
  330. raise exception.KaminarioCinderDriverException(
  331. reason=six.text_type(ex.message))
  332. else:
  333. msg = _("Snapshot: %s search failed in K2.") % snap_name
  334. LOG.error(msg)
  335. raise exception.KaminarioCinderDriverException(reason=msg)
  336. try:
  337. conn = self.initialize_connection(cview, properties)
  338. src_attach_info = self._connect_device(conn)
  339. self.create_volume(volume)
  340. conn = self.initialize_connection(volume, properties)
  341. dest_attach_info = self._connect_device(conn)
  342. vol_utils.copy_volume(src_attach_info['device']['path'],
  343. dest_attach_info['device']['path'],
  344. snapshot.volume.size * units.Ki,
  345. self.configuration.volume_dd_blocksize,
  346. sparse=True)
  347. self.terminate_connection(volume, properties)
  348. self.terminate_connection(cview, properties)
  349. except Exception as ex:
  350. self.terminate_connection(cview, properties)
  351. self.terminate_connection(volume, properties)
  352. cview.delete()
  353. self.delete_volume(volume)
  354. LOG.exception(_LE("Copy to volume: %(vol)s from view: %(view)s "
  355. "failed"), {"vol": vol_name, "view": view_name})
  356. raise exception.KaminarioCinderDriverException(
  357. reason=six.text_type(ex.message))
  358. @kaminario_logger
  359. def create_cloned_volume(self, volume, src_vref):
  360. """Create a clone from source volume.
  361. - attach source volume
  362. - create and attach new volume
  363. - copy data from attached source volume to attached new volume
  364. - detach both volumes
  365. """
  366. clone_name = self.get_volume_name(volume.id)
  367. src_name = self.get_volume_name(src_vref.id)
  368. src_vol = self.client.search("volumes", name=src_name)
  369. src_map = self.client.search("mappings", volume=src_vol)
  370. if src_map.total != 0:
  371. msg = _("K2 driver does not support clone of a attached volume. "
  372. "To get this done, create a snapshot from the attached "
  373. "volume and then create a volume from the snapshot.")
  374. LOG.error(msg)
  375. raise exception.KaminarioCinderDriverException(reason=msg)
  376. try:
  377. properties = utils.brick_get_connector_properties()
  378. conn = self.initialize_connection(src_vref, properties)
  379. src_attach_info = self._connect_device(conn)
  380. self.create_volume(volume)
  381. conn = self.initialize_connection(volume, properties)
  382. dest_attach_info = self._connect_device(conn)
  383. vol_utils.copy_volume(src_attach_info['device']['path'],
  384. dest_attach_info['device']['path'],
  385. src_vref.size * units.Ki,
  386. self.configuration.volume_dd_blocksize,
  387. sparse=True)
  388. self.terminate_connection(volume, properties)
  389. self.terminate_connection(src_vref, properties)
  390. except Exception as ex:
  391. self.terminate_connection(src_vref, properties)
  392. self.terminate_connection(volume, properties)
  393. self.delete_volume(volume)
  394. LOG.exception(_LE("Create a clone: %s failed."), clone_name)
  395. raise exception.KaminarioCinderDriverException(
  396. reason=six.text_type(ex.message))
  397. @kaminario_logger
  398. def delete_volume(self, volume):
  399. """Volume in K2 exists in a volume group.
  400. - delete the volume
  401. - delete the corresponding volume group
  402. """
  403. vg_name = self.get_volume_group_name(volume.id)
  404. vol_name = self.get_volume_name(volume.id)
  405. try:
  406. if self._get_is_replica(volume.volume_type) and self.replica:
  407. self._delete_volume_replica(volume, vg_name, vol_name)
  408. LOG.debug("Searching and deleting volume: %s in K2.", vol_name)
  409. vol_rs = self.client.search("volumes", name=vol_name)
  410. if vol_rs.total != 0:
  411. vol_rs.hits[0].delete()
  412. LOG.debug("Searching and deleting vg: %s in K2.", vg_name)
  413. vg_rs = self.client.search("volume_groups", name=vg_name)
  414. if vg_rs.total != 0:
  415. vg_rs.hits[0].delete()
  416. except Exception as ex:
  417. LOG.exception(_LE("Deletion of volume %s failed."), vol_name)
  418. raise exception.KaminarioCinderDriverException(
  419. reason=six.text_type(ex.message))
  420. @kaminario_logger
  421. def _delete_volume_replica(self, volume, vg_name, vol_name):
  422. rvg_name = self.get_rep_name(vg_name)
  423. rvol_name = self.get_rep_name(vol_name)
  424. session_name = self.get_session_name(volume.id)
  425. rsession_name = self.get_rep_name(session_name)
  426. src_ssn = self.client.search('replication/sessions',
  427. name=session_name).hits[0]
  428. tgt_ssn = self.target.search('replication/sessions',
  429. name=rsession_name).hits[0]
  430. src_ssn.state = 'suspended'
  431. src_ssn.save()
  432. self._check_for_status(tgt_ssn, 'suspended')
  433. src_ssn.state = 'idle'
  434. src_ssn.save()
  435. self._check_for_status(tgt_ssn, 'idle')
  436. tgt_ssn.delete()
  437. src_ssn.delete()
  438. LOG.debug("Searching and deleting snapshots for volume groups:"
  439. "%(vg1)s, %(vg2)s in K2.", {'vg1': vg_name, 'vg2': rvg_name})
  440. vg = self.client.search('volume_groups', name=vg_name).hits
  441. rvg = self.target.search('volume_groups', name=rvg_name).hits
  442. snaps = self.client.search('snapshots', volume_group=vg).hits
  443. for s in snaps:
  444. s.delete()
  445. rsnaps = self.target.search('snapshots', volume_group=rvg).hits
  446. for s in rsnaps:
  447. s.delete()
  448. self._delete_by_ref(self.target, "volumes", rvol_name, 'remote volume')
  449. self._delete_by_ref(self.target, "volume_groups",
  450. rvg_name, "remote vg")
  451. @kaminario_logger
  452. def _check_for_status(self, obj, status):
  453. while obj.state != status:
  454. obj.refresh()
  455. eventlet.sleep(1)
  456. @kaminario_logger
  457. def get_volume_stats(self, refresh=False):
  458. if refresh:
  459. self.update_volume_stats()
  460. stats = self.stats
  461. stats['storage_protocol'] = self._protocol
  462. stats['driver_version'] = self.VERSION
  463. stats['vendor_name'] = self.VENDOR
  464. backend_name = self.configuration.safe_get('volume_backend_name')
  465. stats['volume_backend_name'] = (backend_name or
  466. self.__class__.__name__)
  467. return stats
  468. def create_export(self, context, volume, connector):
  469. pass
  470. def ensure_export(self, context, volume):
  471. pass
  472. def remove_export(self, context, volume):
  473. pass
  474. @kaminario_logger
  475. def create_snapshot(self, snapshot):
  476. """Create a snapshot from a volume_group."""
  477. vg_name = self.get_volume_group_name(snapshot.volume_id)
  478. snap_name = self.get_snap_name(snapshot.id)
  479. rpolicy = self.get_policy()
  480. try:
  481. LOG.debug("Searching volume_group: %s in K2.", vg_name)
  482. vg = self.client.search("volume_groups", name=vg_name).hits[0]
  483. LOG.debug("Creating a snapshot: %(snap)s from vg: %(vg)s",
  484. {'snap': snap_name, 'vg': vg_name})
  485. self.client.new("snapshots", short_name=snap_name,
  486. source=vg, retention_policy=rpolicy,
  487. is_auto_deleteable=False).save()
  488. except Exception as ex:
  489. LOG.exception(_LE("Creation of snapshot: %s failed."), snap_name)
  490. raise exception.KaminarioCinderDriverException(
  491. reason=six.text_type(ex.message))
  492. @kaminario_logger
  493. def delete_snapshot(self, snapshot):
  494. """Delete a snapshot."""
  495. snap_name = self.get_snap_name(snapshot.id)
  496. try:
  497. LOG.debug("Searching and deleting snapshot: %s in K2.", snap_name)
  498. snap_rs = self.client.search("snapshots", short_name=snap_name)
  499. if snap_rs.total != 0:
  500. snap_rs.hits[0].delete()
  501. except Exception as ex:
  502. LOG.exception(_LE("Deletion of snapshot: %s failed."), snap_name)
  503. raise exception.KaminarioCinderDriverException(
  504. reason=six.text_type(ex.message))
  505. @kaminario_logger
  506. def extend_volume(self, volume, new_size):
  507. """Extend volume."""
  508. vol_name = self.get_volume_name(volume.id)
  509. try:
  510. LOG.debug("Searching volume: %s in K2.", vol_name)
  511. vol = self.client.search("volumes", name=vol_name).hits[0]
  512. vol.size = new_size * units.Mi
  513. LOG.debug("Extending volume: %s in K2.", vol_name)
  514. vol.save()
  515. except Exception as ex:
  516. LOG.exception(_LE("Extending volume: %s failed."), vol_name)
  517. raise exception.KaminarioCinderDriverException(
  518. reason=six.text_type(ex.message))
  519. @kaminario_logger
  520. def update_volume_stats(self):
  521. conf = self.configuration
  522. LOG.debug("Searching system capacity in K2.")
  523. cap = self.client.search("system/capacity").hits[0]
  524. LOG.debug("Searching total volumes in K2 for updating stats.")
  525. total_volumes = self.client.search("volumes").total - 1
  526. provisioned_vol = cap.provisioned_volumes
  527. if (conf.auto_calc_max_oversubscription_ratio and cap.provisioned
  528. and (cap.total - cap.free) != 0):
  529. ratio = provisioned_vol / float(cap.total - cap.free)
  530. else:
  531. ratio = conf.max_over_subscription_ratio
  532. self.stats = {'QoS_support': False,
  533. 'free_capacity_gb': cap.free / units.Mi,
  534. 'total_capacity_gb': cap.total / units.Mi,
  535. 'thin_provisioning_support': True,
  536. 'sparse_copy_volume': True,
  537. 'total_volumes': total_volumes,
  538. 'thick_provisioning_support': False,
  539. 'provisioned_capacity_gb': provisioned_vol / units.Mi,
  540. 'max_oversubscription_ratio': ratio,
  541. 'kaminario:thin_prov_type': 'dedup/nodedup',
  542. 'replication_enabled': True,
  543. 'kaminario:replication': True}
  544. @kaminario_logger
  545. def get_initiator_host_name(self, connector):
  546. """Return the initiator host name.
  547. Valid characters: 0-9, a-z, A-Z, '-', '_'
  548. All other characters are replaced with '_'.
  549. Total characters in initiator host name: 32
  550. """
  551. return re.sub('[^0-9a-zA-Z-_]', '_', connector.get('host', ''))[:32]
  552. @kaminario_logger
  553. def get_volume_group_name(self, vid):
  554. """Return the volume group name."""
  555. return "cvg-{0}".format(vid)
  556. @kaminario_logger
  557. def get_volume_name(self, vid):
  558. """Return the volume name."""
  559. return "cv-{0}".format(vid)
  560. @kaminario_logger
  561. def get_session_name(self, vid):
  562. """Return the volume name."""
  563. return "ssn-{0}".format(vid)
  564. @kaminario_logger
  565. def get_snap_name(self, sid):
  566. """Return the snapshot name."""
  567. return "cs-{0}".format(sid)
  568. @kaminario_logger
  569. def get_view_name(self, vid):
  570. """Return the view name."""
  571. return "cview-{0}".format(vid)
  572. @kaminario_logger
  573. def get_rep_name(self, name):
  574. """Return the corresponding replication names."""
  575. return "r{0}".format(name)
  576. @kaminario_logger
  577. def _delete_host_by_name(self, name):
  578. """Deleting host by name."""
  579. host_rs = self.client.search("hosts", name=name)
  580. if hasattr(host_rs, "hits") and host_rs.total != 0:
  581. host = host_rs.hits[0]
  582. host.delete()
  583. @kaminario_logger
  584. def get_policy(self):
  585. """Return the retention policy."""
  586. try:
  587. LOG.debug("Searching for retention_policy in K2.")
  588. return self.client.search("retention_policies",
  589. name="Best_Effort_Retention").hits[0]
  590. except Exception as ex:
  591. LOG.exception(_LE("Retention policy search failed in K2."))
  592. raise exception.KaminarioCinderDriverException(
  593. reason=six.text_type(ex.message))
  594. @kaminario_logger
  595. def _get_volume_object(self, volume):
  596. vol_name = self.get_volume_name(volume.id)
  597. if volume.replication_status == 'failed-over':
  598. vol_name = self.get_rep_name(vol_name)
  599. self.client = self.target
  600. LOG.debug("Searching volume : %s in K2.", vol_name)
  601. vol_rs = self.client.search("volumes", name=vol_name)
  602. if not hasattr(vol_rs, 'hits') or vol_rs.total == 0:
  603. msg = _("Unable to find volume: %s from K2.") % vol_name
  604. LOG.error(msg)
  605. raise exception.KaminarioCinderDriverException(reason=msg)
  606. return vol_rs.hits[0]
  607. @kaminario_logger
  608. def _get_lun_number(self, vol, host):
  609. volsnap = None
  610. LOG.debug("Searching volsnaps in K2.")
  611. volsnap_rs = self.client.search("volsnaps", snapshot=vol)
  612. if hasattr(volsnap_rs, 'hits') and volsnap_rs.total != 0:
  613. volsnap = volsnap_rs.hits[0]
  614. LOG.debug("Searching mapping of volsnap in K2.")
  615. map_rs = self.client.search("mappings", volume=volsnap, host=host)
  616. return map_rs.hits[0].lun
  617. def initialize_connection(self, volume, connector):
  618. pass
  619. @kaminario_logger
  620. def terminate_connection(self, volume, connector):
  621. """Terminate connection of volume from host."""
  622. # Get volume object
  623. if type(volume).__name__ != 'RestObject':
  624. vol_name = self.get_volume_name(volume.id)
  625. if volume.replication_status == 'failed-over':
  626. vol_name = self.get_rep_name(vol_name)
  627. self.client = self.target
  628. LOG.debug("Searching volume: %s in K2.", vol_name)
  629. volume_rs = self.client.search("volumes", name=vol_name)
  630. if hasattr(volume_rs, "hits") and volume_rs.total != 0:
  631. volume = volume_rs.hits[0]
  632. else:
  633. vol_name = volume.name
  634. # Get host object.
  635. host_name = self.get_initiator_host_name(connector)
  636. host_rs = self.client.search("hosts", name=host_name)
  637. if hasattr(host_rs, "hits") and host_rs.total != 0 and volume:
  638. host = host_rs.hits[0]
  639. LOG.debug("Searching and deleting mapping of volume: %(name)s to "
  640. "host: %(host)s", {'host': host_name, 'name': vol_name})
  641. map_rs = self.client.search("mappings", volume=volume, host=host)
  642. if hasattr(map_rs, "hits") and map_rs.total != 0:
  643. map_rs.hits[0].delete()
  644. if self.client.search("mappings", host=host).total == 0:
  645. LOG.debug("Deleting initiator hostname: %s in K2.", host_name)
  646. host.delete()
  647. else:
  648. LOG.warning(_LW("Host: %s not found on K2."), host_name)
  649. def k2_initialize_connection(self, volume, connector):
  650. # Get volume object.
  651. if type(volume).__name__ != 'RestObject':
  652. vol = self._get_volume_object(volume)
  653. else:
  654. vol = volume
  655. # Get host object.
  656. host, host_rs, host_name = self._get_host_object(connector)
  657. try:
  658. # Map volume object to host object.
  659. LOG.debug("Mapping volume: %(vol)s to host: %(host)s",
  660. {'host': host_name, 'vol': vol.name})
  661. mapping = self.client.new("mappings", volume=vol, host=host).save()
  662. except Exception as ex:
  663. if host_rs.total == 0:
  664. self._delete_host_by_name(host_name)
  665. LOG.exception(_LE("Unable to map volume: %(vol)s to host: "
  666. "%(host)s"), {'host': host_name,
  667. 'vol': vol.name})
  668. raise exception.KaminarioCinderDriverException(
  669. reason=six.text_type(ex.message))
  670. # Get lun number.
  671. if type(volume).__name__ == 'RestObject':
  672. return self._get_lun_number(vol, host)
  673. else:
  674. return mapping.lun
  675. def _get_host_object(self, connector):
  676. pass
  677. def _get_is_dedup(self, vol_type):
  678. if vol_type:
  679. specs_val = vol_type.get('extra_specs', {}).get(
  680. 'kaminario:thin_prov_type')
  681. if specs_val == 'nodedup':
  682. return False
  683. elif CONF.kaminario_nodedup_substring in vol_type.get('name'):
  684. LOG.info(_LI("'kaminario_nodedup_substring' option is "
  685. "deprecated in favour of 'kaminario:thin_prov_"
  686. "type' in extra-specs and will be removed in "
  687. "the 10.0.0 release."))
  688. return False
  689. else:
  690. return True
  691. else:
  692. return True
  693. def _get_is_replica(self, vol_type):
  694. replica = False
  695. if vol_type and vol_type.get('extra_specs'):
  696. specs = vol_type.get('extra_specs')
  697. if (specs.get('kaminario:replication') == 'enabled' and
  698. self.replica):
  699. replica = True
  700. return replica
  701. def _get_replica_status(self, vg_name):
  702. vg = self.client.search("volume_groups", name=vg_name).hits[0]
  703. if self.client.search("replication/sessions",
  704. local_volume_group=vg).total != 0:
  705. return True
  706. else:
  707. return False
  708. def manage_existing(self, volume, existing_ref):
  709. vol_name = existing_ref['source-name']
  710. new_name = self.get_volume_name(volume.id)
  711. vg_new_name = self.get_volume_group_name(volume.id)
  712. vg_name = None
  713. is_dedup = self._get_is_dedup(volume.get('volume_type'))
  714. try:
  715. LOG.debug("Searching volume: %s in K2.", vol_name)
  716. vol = self.client.search("volumes", name=vol_name).hits[0]
  717. vg = vol.volume_group
  718. vg_replica = self._get_replica_status(vg.name)
  719. vol_map = False
  720. if self.client.search("mappings", volume=vol).total != 0:
  721. vol_map = True
  722. if is_dedup != vg.is_dedup or vg_replica or vol_map:
  723. raise exception.ManageExistingInvalidReference(
  724. existing_ref=existing_ref,
  725. reason=_('Manage volume type invalid.'))
  726. vol.name = new_name
  727. vg_name = vg.name
  728. LOG.debug("Manage new volume name: %s", new_name)
  729. vg.name = vg_new_name
  730. LOG.debug("Manage volume group name: %s", vg_new_name)
  731. vg.save()
  732. LOG.debug("Manage volume: %s in K2.", vol_name)
  733. vol.save()
  734. except Exception as ex:
  735. vg_rs = self.client.search("volume_groups", name=vg_new_name)
  736. if hasattr(vg_rs, 'hits') and vg_rs.total != 0:
  737. vg = vg_rs.hits[0]
  738. if vg_name and vg.name == vg_new_name:
  739. vg.name = vg_name
  740. LOG.debug("Updating vg new name to old name: %s ", vg_name)
  741. vg.save()
  742. LOG.exception(_LE("manage volume: %s failed."), vol_name)
  743. raise exception.ManageExistingInvalidReference(
  744. existing_ref=existing_ref,
  745. reason=six.text_type(ex.message))
  746. def manage_existing_get_size(self, volume, existing_ref):
  747. vol_name = existing_ref['source-name']
  748. v_rs = self.client.search("volumes", name=vol_name)
  749. if hasattr(v_rs, 'hits') and v_rs.total != 0:
  750. vol = v_rs.hits[0]
  751. size = vol.size / units.Mi
  752. return math.ceil(size)
  753. else:
  754. raise exception.ManageExistingInvalidReference(
  755. existing_ref=existing_ref,
  756. reason=_('Unable to get size of manage volume.'))
  757. def after_volume_copy(self, ctxt, volume, new_volume, remote=None):
  758. self.delete_volume(volume)
  759. vg_name_old = self.get_volume_group_name(volume.id)
  760. vol_name_old = self.get_volume_name(volume.id)
  761. vg_name_new = self.get_volume_group_name(new_volume.id)
  762. vol_name_new = self.get_volume_name(new_volume.id)
  763. vg_new = self.client.search("volume_groups", name=vg_name_new).hits[0]
  764. vg_new.name = vg_name_old
  765. vg_new.save()
  766. vol_new = self.client.search("volumes", name=vol_name_new).hits[0]
  767. vol_new.name = vol_name_old
  768. vol_new.save()
  769. def retype(self, ctxt, volume, new_type, diff, host):
  770. old_type = volume.get('volume_type')
  771. vg_name = self.get_volume_group_name(volume.id)
  772. old_rep_type = self._get_replica_status(vg_name)
  773. new_rep_type = self._get_is_replica(new_type)
  774. new_prov_type = self._get_is_dedup(new_type)
  775. old_prov_type = self._get_is_dedup(old_type)
  776. # Change dedup<->nodedup with add/remove replication is complex in K2
  777. # since K2 does not have api to change dedup<->nodedup.
  778. if new_prov_type == old_prov_type:
  779. if not old_rep_type and new_rep_type:
  780. self._add_replication(volume)
  781. return True
  782. elif old_rep_type and not new_rep_type:
  783. self._delete_replication(volume)
  784. return True
  785. elif not new_rep_type and not old_rep_type:
  786. LOG.debug("Use '--migration-policy on-demand' to change 'dedup "
  787. "without replication'<->'nodedup without replication'.")
  788. return False
  789. else:
  790. LOG.error(_LE('Change from type1: %(type1)s to type2: %(type2)s '
  791. 'is not supported directly in K2.'),
  792. {'type1': old_type, 'type2': new_type})
  793. return False
  794. def _add_replication(self, volume):
  795. vg_name = self.get_volume_group_name(volume.id)
  796. vol_name = self.get_volume_name(volume.id)
  797. LOG.debug("Searching volume group with name: %(name)s",
  798. {'name': vg_name})
  799. vg = self.client.search("volume_groups", name=vg_name).hits[0]
  800. LOG.debug("Searching volume with name: %(name)s",
  801. {'name': vol_name})
  802. vol = self.client.search("volumes", name=vol_name).hits[0]
  803. self._create_volume_replica(volume, vg, vol, self.replica.rpo)
  804. def _delete_replication(self, volume):
  805. vg_name = self.get_volume_group_name(volume.id)
  806. vol_name = self.get_volume_name(volume.id)
  807. self._delete_volume_replica(volume, vg_name, vol_name)