Coverage for drivers/linstorvolumemanager.py : 10%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1#!/usr/bin/env python3
2#
3# Copyright (C) 2020 Vates SAS - ronan.abhamon@vates.fr
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program. If not, see <https://www.gnu.org/licenses/>.
16#
19import distutils.util
20import errno
21import glob
22import json
23import linstor
24import os.path
25import re
26import shutil
27import socket
28import stat
29import time
30import util
31import uuid
33# Persistent prefix to add to RAW persistent volumes.
34PERSISTENT_PREFIX = 'xcp-persistent-'
36# Contains the data of the "/var/lib/linstor" directory.
37DATABASE_VOLUME_NAME = PERSISTENT_PREFIX + 'database'
38DATABASE_SIZE = 1 << 30 # 1GB.
39DATABASE_PATH = '/var/lib/linstor'
40DATABASE_MKFS = 'mkfs.ext4'
42REG_DRBDADM_PRIMARY = re.compile("([^\\s]+)\\s+role:Primary")
43REG_DRBDSETUP_IP = re.compile('[^\\s]+\\s+(.*):.*$')
45DRBD_BY_RES_PATH = '/dev/drbd/by-res/'
47PLUGIN = 'linstor-manager'
50# ==============================================================================
52def get_local_volume_openers(resource_name, volume):
53 if not resource_name or volume is None:
54 raise Exception('Cannot get DRBD openers without resource name and/or volume.')
56 path = '/sys/kernel/debug/drbd/resources/{}/volumes/{}/openers'.format(
57 resource_name, volume
58 )
60 with open(path, 'r') as openers:
61 # Not a big cost, so read all lines directly.
62 lines = openers.readlines()
64 result = {}
66 opener_re = re.compile('(.*)\\s+([0-9]+)\\s+([0-9]+)')
67 for line in lines:
68 match = opener_re.match(line)
69 assert match
71 groups = match.groups()
72 process_name = groups[0]
73 pid = groups[1]
74 open_duration_ms = groups[2]
75 result[pid] = {
76 'process-name': process_name,
77 'open-duration': open_duration_ms
78 }
80 return json.dumps(result)
82def get_all_volume_openers(resource_name, volume):
83 PLUGIN_CMD = 'getDrbdOpeners'
85 volume = str(volume)
86 openers = {}
88 # Make sure this call never stucks because this function can be called
89 # during HA init and in this case we can wait forever.
90 session = util.timeout_call(10, util.get_localAPI_session)
92 hosts = session.xenapi.host.get_all_records()
93 for host_ref, host_record in hosts.items():
94 node_name = host_record['hostname']
95 try:
96 if not session.xenapi.host_metrics.get_record(
97 host_record['metrics']
98 )['live']:
99 # Ensure we call plugin on online hosts only.
100 continue
102 openers[node_name] = json.loads(
103 session.xenapi.host.call_plugin(host_ref, PLUGIN, PLUGIN_CMD, {
104 'resourceName': resource_name,
105 'volume': volume
106 })
107 )
108 except Exception as e:
109 util.SMlog('Failed to get openers of `{}` on `{}`: {}'.format(
110 resource_name, node_name, e
111 ))
113 return openers
116# ==============================================================================
118def round_up(value, divisor):
119 assert divisor
120 divisor = int(divisor)
121 return ((int(value) + divisor - 1) // divisor) * divisor
124def round_down(value, divisor):
125 assert divisor
126 value = int(value)
127 return value - (value % int(divisor))
130# ==============================================================================
132def get_remote_host_ip(node_name):
133 (ret, stdout, stderr) = util.doexec([
134 'drbdsetup', 'show', DATABASE_VOLUME_NAME, '--json'
135 ])
136 if ret != 0:
137 return
139 try:
140 conf = json.loads(stdout)
141 if not conf:
142 return
144 for connection in conf[0]['connections']:
145 if connection['net']['_name'] == node_name:
146 value = connection['path']['_remote_host']
147 res = REG_DRBDSETUP_IP.match(value)
148 if res:
149 return res.groups()[0]
150 break
151 except Exception:
152 pass
155def _get_controller_uri():
156 PLUGIN_CMD = 'hasControllerRunning'
158 # Try to find controller using drbdadm.
159 (ret, stdout, stderr) = util.doexec([
160 'drbdadm', 'status', DATABASE_VOLUME_NAME
161 ])
162 if ret == 0:
163 # If we are here, the database device exists locally.
165 if stdout.startswith('{} role:Primary'.format(DATABASE_VOLUME_NAME)):
166 # Nice case, we have the controller running on this local host.
167 return 'linstor://localhost'
169 # Try to find the host using DRBD connections.
170 res = REG_DRBDADM_PRIMARY.search(stdout)
171 if res:
172 node_name = res.groups()[0]
173 ip = get_remote_host_ip(node_name)
174 if ip:
175 return 'linstor://' + ip
177 # Worst case: we use many hosts in the pool (>= 4), so we can't find the
178 # primary using drbdadm because we don't have all connections to the
179 # replicated volume. `drbdadm status xcp-persistent-database` returns
180 # 3 connections by default.
181 try:
182 session = util.timeout_call(10, util.get_localAPI_session)
184 for host_ref, host_record in session.xenapi.host.get_all_records().items():
185 node_name = host_record['hostname']
186 try:
187 if distutils.util.strtobool(
188 session.xenapi.host.call_plugin(host_ref, PLUGIN, PLUGIN_CMD, {})
189 ):
190 return 'linstor://' + host_record['address']
191 except Exception as e:
192 # Can throw and exception if a host is offline. So catch it.
193 util.SMlog('Unable to search controller on `{}`: {}'.format(
194 node_name, e
195 ))
196 except:
197 # Not found, maybe we are trying to create the SR...
198 pass
200def get_controller_uri():
201 retries = 0
202 while True:
203 uri = _get_controller_uri()
204 if uri:
205 return uri
207 retries += 1
208 if retries >= 10:
209 break
210 time.sleep(1)
213def get_controller_node_name():
214 PLUGIN_CMD = 'hasControllerRunning'
216 (ret, stdout, stderr) = util.doexec([
217 'drbdadm', 'status', DATABASE_VOLUME_NAME
218 ])
220 if ret == 0:
221 if stdout.startswith('{} role:Primary'.format(DATABASE_VOLUME_NAME)):
222 return 'localhost'
224 res = REG_DRBDADM_PRIMARY.search(stdout)
225 if res:
226 return res.groups()[0]
228 session = util.timeout_call(5, util.get_localAPI_session)
230 for host_ref, host_record in session.xenapi.host.get_all_records().items():
231 node_name = host_record['hostname']
232 try:
233 if not session.xenapi.host_metrics.get_record(
234 host_record['metrics']
235 )['live']:
236 continue
238 if distutils.util.strtobool(session.xenapi.host.call_plugin(
239 host_ref, PLUGIN, PLUGIN_CMD, {}
240 )):
241 return node_name
242 except Exception as e:
243 util.SMlog('Failed to call plugin to get controller on `{}`: {}'.format(
244 node_name, e
245 ))
248def demote_drbd_resource(node_name, resource_name):
249 PLUGIN_CMD = 'demoteDrbdResource'
251 session = util.timeout_call(5, util.get_localAPI_session)
253 for host_ref, host_record in session.xenapi.host.get_all_records().items():
254 if host_record['hostname'] != node_name:
255 continue
257 try:
258 session.xenapi.host.call_plugin(
259 host_ref, PLUGIN, PLUGIN_CMD, {'resource_name': resource_name}
260 )
261 except Exception as e:
262 util.SMlog('Failed to demote resource `{}` on `{}`: {}'.format(
263 resource_name, node_name, e
264 ))
265 raise Exception(
266 'Can\'t demote resource `{}`, unable to find node `{}`'
267 .format(resource_name, node_name)
268 )
270# ==============================================================================
272class LinstorVolumeManagerError(Exception):
273 ERR_GENERIC = 0,
274 ERR_VOLUME_EXISTS = 1,
275 ERR_VOLUME_NOT_EXISTS = 2,
276 ERR_VOLUME_DESTROY = 3
278 def __init__(self, message, code=ERR_GENERIC):
279 super(LinstorVolumeManagerError, self).__init__(message)
280 self._code = code
282 @property
283 def code(self):
284 return self._code
287# ==============================================================================
289# Note:
290# If a storage pool is not accessible after a network change:
291# linstor node interface modify <NODE> default --ip <IP>
294class LinstorVolumeManager(object):
295 """
296 API to manager LINSTOR volumes in XCP-ng.
297 A volume in this context is a physical part of the storage layer.
298 """
300 __slots__ = (
301 '_linstor', '_logger',
302 '_uri', '_base_group_name',
303 '_redundancy', '_group_name',
304 '_volumes', '_storage_pools',
305 '_storage_pools_time',
306 '_kv_cache', '_resource_cache', '_volume_info_cache',
307 '_kv_cache_dirty', '_resource_cache_dirty', '_volume_info_cache_dirty'
308 )
310 DEV_ROOT_PATH = DRBD_BY_RES_PATH
312 # Default sector size.
313 BLOCK_SIZE = 512
315 # List of volume properties.
316 PROP_METADATA = 'metadata'
317 PROP_NOT_EXISTS = 'not-exists'
318 PROP_VOLUME_NAME = 'volume-name'
319 PROP_IS_READONLY_TIMESTAMP = 'readonly-timestamp'
321 # A volume can only be locked for a limited duration.
322 # The goal is to give enough time to slaves to execute some actions on
323 # a device before an UUID update or a coalesce for example.
324 # Expiration is expressed in seconds.
325 LOCKED_EXPIRATION_DELAY = 1 * 60
327 # Used when volume uuid is being updated.
328 PROP_UPDATING_UUID_SRC = 'updating-uuid-src'
330 # States of property PROP_NOT_EXISTS.
331 STATE_EXISTS = '0'
332 STATE_NOT_EXISTS = '1'
333 STATE_CREATING = '2'
335 # Property namespaces.
336 NAMESPACE_SR = 'xcp/sr'
337 NAMESPACE_VOLUME = 'xcp/volume'
339 # Regex to match properties.
340 REG_PROP = '^([^/]+)/{}$'
342 REG_METADATA = re.compile(REG_PROP.format(PROP_METADATA))
343 REG_NOT_EXISTS = re.compile(REG_PROP.format(PROP_NOT_EXISTS))
344 REG_VOLUME_NAME = re.compile(REG_PROP.format(PROP_VOLUME_NAME))
345 REG_UPDATING_UUID_SRC = re.compile(REG_PROP.format(PROP_UPDATING_UUID_SRC))
347 # Prefixes of SR/VOLUME in the LINSTOR DB.
348 # A LINSTOR (resource, group, ...) name cannot start with a number.
349 # So we add a prefix behind our SR/VOLUME uuids.
350 PREFIX_SR = 'xcp-sr-'
351 PREFIX_VOLUME = 'xcp-volume-'
353 # Limit request number when storage pool info is asked, we fetch
354 # the current pool status after N elapsed seconds.
355 STORAGE_POOLS_FETCH_INTERVAL = 15
357 @staticmethod
358 def default_logger(*args):
359 print(args)
361 # --------------------------------------------------------------------------
362 # API.
363 # --------------------------------------------------------------------------
365 class VolumeInfo(object):
366 __slots__ = (
367 'name',
368 'allocated_size', # Allocated size, place count is not used.
369 'virtual_size', # Total virtual available size of this volume
370 # (i.e. the user size at creation).
371 'diskful' # Array of nodes that have a diskful volume.
372 )
374 def __init__(self, name):
375 self.name = name
376 self.allocated_size = 0
377 self.virtual_size = 0
378 self.diskful = []
380 def __repr__(self):
381 return 'VolumeInfo("{}", {}, {}, {})'.format(
382 self.name, self.allocated_size, self.virtual_size,
383 self.diskful
384 )
386 # --------------------------------------------------------------------------
388 def __init__(
389 self, uri, group_name, repair=False, logger=default_logger.__func__,
390 attempt_count=30
391 ):
392 """
393 Create a new LinstorVolumeManager object.
394 :param str uri: URI to communicate with the LINSTOR controller.
395 :param str group_name: The SR goup name to use.
396 :param bool repair: If true we try to remove bad volumes due to a crash
397 or unexpected behavior.
398 :param function logger: Function to log messages.
399 :param int attempt_count: Number of attempts to join the controller.
400 """
402 self._linstor = self._create_linstor_instance(
403 uri, attempt_count=attempt_count
404 )
405 self._base_group_name = group_name
407 # Ensure group exists.
408 group_name = self._build_group_name(group_name)
409 groups = self._linstor.resource_group_list_raise([group_name])
410 groups = groups.resource_groups
411 if not groups:
412 raise LinstorVolumeManagerError(
413 'Unable to find `{}` Linstor SR'.format(group_name)
414 )
416 # Ok. ;)
417 self._logger = logger
418 self._redundancy = groups[0].select_filter.place_count
419 self._group_name = group_name
420 self._volumes = set()
421 self._storage_pools_time = 0
423 # To increate performance and limit request count to LINSTOR services,
424 # we use caches.
425 self._kv_cache = self._create_kv_cache()
426 self._resource_cache = None
427 self._resource_cache_dirty = True
428 self._volume_info_cache = None
429 self._volume_info_cache_dirty = True
430 self._build_volumes(repair=repair)
432 @property
433 def group_name(self):
434 """
435 Give the used group name.
436 :return: The group name.
437 :rtype: str
438 """
439 return self._base_group_name
441 @property
442 def redundancy(self):
443 """
444 Give the used redundancy.
445 :return: The redundancy.
446 :rtype: int
447 """
448 return self._redundancy
450 @property
451 def volumes(self):
452 """
453 Give the volumes uuid set.
454 :return: The volumes uuid set.
455 :rtype: set(str)
456 """
457 return self._volumes
459 @property
460 def max_volume_size_allowed(self):
461 """
462 Give the max volume size currently available in B.
463 :return: The current size.
464 :rtype: int
465 """
467 candidates = self._find_best_size_candidates()
468 if not candidates:
469 raise LinstorVolumeManagerError(
470 'Failed to get max volume size allowed'
471 )
473 size = candidates[0].max_volume_size
474 if size < 0:
475 raise LinstorVolumeManagerError(
476 'Invalid max volume size allowed given: {}'.format(size)
477 )
478 return self.round_down_volume_size(size * 1024)
480 @property
481 def physical_size(self):
482 """
483 Give the total physical size of the SR.
484 :return: The physical size.
485 :rtype: int
486 """
487 return self._compute_size('total_capacity')
489 @property
490 def physical_free_size(self):
491 """
492 Give the total free physical size of the SR.
493 :return: The physical free size.
494 :rtype: int
495 """
496 return self._compute_size('free_capacity')
498 @property
499 def allocated_volume_size(self):
500 """
501 Give the allocated size for all volumes. The place count is not
502 used here. When thick lvm is used, the size for one volume should
503 be equal to the virtual volume size. With thin lvm, the size is equal
504 or lower to the volume size.
505 :return: The allocated size of all volumes.
506 :rtype: int
507 """
509 # Paths: /res_name/vol_number/size
510 sizes = {}
512 for resource in self._get_resource_cache().resources:
513 if resource.name not in sizes:
514 current = sizes[resource.name] = {}
515 else:
516 current = sizes[resource.name]
518 for volume in resource.volumes:
519 # We ignore diskless pools of the form "DfltDisklessStorPool".
520 if volume.storage_pool_name != self._group_name:
521 continue
523 current_size = volume.allocated_size
524 if current_size < 0:
525 raise LinstorVolumeManagerError(
526 'Failed to get allocated size of `{}` on `{}`'
527 .format(resource.name, volume.storage_pool_name)
528 )
529 current[volume.number] = max(current_size, current.get(volume.number) or 0)
531 total_size = 0
532 for volumes in sizes.values():
533 for size in volumes.values():
534 total_size += size
536 return total_size * 1024
538 def get_min_physical_size(self):
539 """
540 Give the minimum physical size of the SR.
541 I.e. the size of the smallest disk + the number of pools.
542 :return: The physical min size.
543 :rtype: tuple(int, int)
544 """
545 size = None
546 pool_count = 0
547 for pool in self._get_storage_pools(force=True):
548 space = pool.free_space
549 if space:
550 pool_count += 1
551 current_size = space.total_capacity
552 if current_size < 0:
553 raise LinstorVolumeManagerError(
554 'Failed to get pool total_capacity attr of `{}`'
555 .format(pool.node_name)
556 )
557 if size is None or current_size < size:
558 size = current_size
559 return (pool_count, (size or 0) * 1024)
561 @property
562 def metadata(self):
563 """
564 Get the metadata of the SR.
565 :return: Dictionary that contains metadata.
566 :rtype: dict(str, dict)
567 """
569 sr_properties = self._get_sr_properties()
570 metadata = sr_properties.get(self.PROP_METADATA)
571 if metadata is not None:
572 metadata = json.loads(metadata)
573 if isinstance(metadata, dict):
574 return metadata
575 raise LinstorVolumeManagerError(
576 'Expected dictionary in SR metadata: {}'.format(
577 self._group_name
578 )
579 )
581 return {}
583 @metadata.setter
584 def metadata(self, metadata):
585 """
586 Set the metadata of the SR.
587 :param dict metadata: Dictionary that contains metadata.
588 """
590 assert isinstance(metadata, dict)
591 sr_properties = self._get_sr_properties()
592 sr_properties[self.PROP_METADATA] = json.dumps(metadata)
594 @property
595 def disconnected_hosts(self):
596 """
597 Get the list of disconnected hosts.
598 :return: Set that contains disconnected hosts.
599 :rtype: set(str)
600 """
602 disconnected_hosts = set()
603 for pool in self._get_storage_pools():
604 for report in pool.reports:
605 if report.ret_code & linstor.consts.WARN_NOT_CONNECTED == \
606 linstor.consts.WARN_NOT_CONNECTED:
607 disconnected_hosts.add(pool.node_name)
608 break
609 return disconnected_hosts
611 def check_volume_exists(self, volume_uuid):
612 """
613 Check if a volume exists in the SR.
614 :return: True if volume exists.
615 :rtype: bool
616 """
617 return volume_uuid in self._volumes
619 def create_volume(
620 self, volume_uuid, size, persistent=True, volume_name=None
621 ):
622 """
623 Create a new volume on the SR.
624 :param str volume_uuid: The volume uuid to use.
625 :param int size: volume size in B.
626 :param bool persistent: If false the volume will be unavailable
627 on the next constructor call LinstorSR(...).
628 :param str volume_name: If set, this name is used in the LINSTOR
629 database instead of a generated name.
630 :return: The current device path of the volume.
631 :rtype: str
632 """
634 self._logger('Creating LINSTOR volume {}...'.format(volume_uuid))
635 if not volume_name:
636 volume_name = self.build_volume_name(util.gen_uuid())
637 volume_properties = self._create_volume_with_properties(
638 volume_uuid, volume_name, size, place_resources=True
639 )
641 # Volume created! Now try to find the device path.
642 try:
643 self._logger(
644 'Find device path of LINSTOR volume {}...'.format(volume_uuid)
645 )
646 device_path = self._find_device_path(volume_uuid, volume_name)
647 if persistent:
648 volume_properties[self.PROP_NOT_EXISTS] = self.STATE_EXISTS
649 self._volumes.add(volume_uuid)
650 self._logger(
651 'LINSTOR volume {} created!'.format(volume_uuid)
652 )
653 return device_path
654 except Exception as e:
655 # There is an issue to find the path.
656 # At this point the volume has just been created, so force flag can be used.
657 self._destroy_volume(volume_uuid, force=True)
658 raise
660 def mark_volume_as_persistent(self, volume_uuid):
661 """
662 Mark volume as persistent if created with persistent=False.
663 :param str volume_uuid: The volume uuid to mark.
664 """
666 self._ensure_volume_exists(volume_uuid)
668 # Mark volume as persistent.
669 volume_properties = self._get_volume_properties(volume_uuid)
670 volume_properties[self.PROP_NOT_EXISTS] = self.STATE_EXISTS
672 def destroy_volume(self, volume_uuid):
673 """
674 Destroy a volume.
675 :param str volume_uuid: The volume uuid to destroy.
676 """
678 self._ensure_volume_exists(volume_uuid)
679 self.ensure_volume_is_not_locked(volume_uuid)
681 # Mark volume as destroyed.
682 volume_properties = self._get_volume_properties(volume_uuid)
683 volume_properties[self.PROP_NOT_EXISTS] = self.STATE_NOT_EXISTS
685 try:
686 self._volumes.remove(volume_uuid)
687 self._destroy_volume(volume_uuid)
688 except Exception as e:
689 raise LinstorVolumeManagerError(
690 str(e),
691 LinstorVolumeManagerError.ERR_VOLUME_DESTROY
692 )
694 def lock_volume(self, volume_uuid, locked=True):
695 """
696 Prevent modifications of the volume properties during
697 "self.LOCKED_EXPIRATION_DELAY" seconds. The SR must be locked
698 when used. This method is useful to attach/detach correctly a volume on
699 a slave. Without it the GC can rename a volume, in this case the old
700 volume path can be used by a slave...
701 :param str volume_uuid: The volume uuid to protect/unprotect.
702 :param bool locked: Lock/unlock the volume.
703 """
705 self._ensure_volume_exists(volume_uuid)
707 self._logger(
708 '{} volume {} as locked'.format(
709 'Mark' if locked else 'Unmark',
710 volume_uuid
711 )
712 )
714 volume_properties = self._get_volume_properties(volume_uuid)
715 if locked:
716 volume_properties[
717 self.PROP_IS_READONLY_TIMESTAMP
718 ] = str(time.time())
719 elif self.PROP_IS_READONLY_TIMESTAMP in volume_properties:
720 volume_properties.pop(self.PROP_IS_READONLY_TIMESTAMP)
722 def ensure_volume_is_not_locked(self, volume_uuid, timeout=None):
723 """
724 Ensure a volume is not locked. Wait if necessary.
725 :param str volume_uuid: The volume uuid to check.
726 :param int timeout: If the volume is always locked after the expiration
727 of the timeout, an exception is thrown.
728 """
729 return self.ensure_volume_list_is_not_locked([volume_uuid], timeout)
731 def ensure_volume_list_is_not_locked(self, volume_uuids, timeout=None):
732 checked = set()
733 for volume_uuid in volume_uuids:
734 if volume_uuid in self._volumes:
735 checked.add(volume_uuid)
737 if not checked:
738 return
740 waiting = False
742 volume_properties = self._get_kv_cache()
744 start = time.time()
745 while True:
746 # Can't delete in for loop, use a copy of the list.
747 remaining = checked.copy()
748 for volume_uuid in checked:
749 volume_properties.namespace = \
750 self._build_volume_namespace(volume_uuid)
751 timestamp = volume_properties.get(
752 self.PROP_IS_READONLY_TIMESTAMP
753 )
754 if timestamp is None:
755 remaining.remove(volume_uuid)
756 continue
758 now = time.time()
759 if now - float(timestamp) > self.LOCKED_EXPIRATION_DELAY:
760 self._logger(
761 'Remove readonly timestamp on {}'.format(volume_uuid)
762 )
763 volume_properties.pop(self.PROP_IS_READONLY_TIMESTAMP)
764 remaining.remove(volume_uuid)
765 continue
767 if not waiting:
768 self._logger(
769 'Volume {} is locked, waiting...'.format(volume_uuid)
770 )
771 waiting = True
772 break
774 if not remaining:
775 break
776 checked = remaining
778 if timeout is not None and now - start > timeout:
779 raise LinstorVolumeManagerError(
780 'volume `{}` is locked and timeout has been reached'
781 .format(volume_uuid),
782 LinstorVolumeManagerError.ERR_VOLUME_NOT_EXISTS
783 )
785 # We must wait to use the volume. After that we can modify it
786 # ONLY if the SR is locked to avoid bad reads on the slaves.
787 time.sleep(1)
788 volume_properties = self._create_kv_cache()
790 if waiting:
791 self._logger('No volume locked now!')
793 def remove_volume_if_diskless(self, volume_uuid):
794 """
795 Remove disless path from local node.
796 :param str volume_uuid: The volume uuid to remove.
797 """
799 self._ensure_volume_exists(volume_uuid)
801 volume_properties = self._get_volume_properties(volume_uuid)
802 volume_name = volume_properties.get(self.PROP_VOLUME_NAME)
804 node_name = socket.gethostname()
805 result = self._linstor.resource_delete_if_diskless(
806 node_name=node_name, rsc_name=volume_name
807 )
808 if not linstor.Linstor.all_api_responses_no_error(result):
809 raise LinstorVolumeManagerError(
810 'Unable to delete diskless path of `{}` on node `{}`: {}'
811 .format(volume_name, node_name, ', '.join(
812 [str(x) for x in result]))
813 )
815 def introduce_volume(self, volume_uuid):
816 pass # TODO: Implement me.
818 def resize_volume(self, volume_uuid, new_size):
819 """
820 Resize a volume.
821 :param str volume_uuid: The volume uuid to resize.
822 :param int new_size: New size in B.
823 """
825 volume_name = self.get_volume_name(volume_uuid)
826 self.ensure_volume_is_not_locked(volume_uuid)
827 new_size = self.round_up_volume_size(new_size)
829 result = self._linstor.volume_dfn_modify(
830 rsc_name=volume_name,
831 volume_nr=0,
832 size=new_size // 1024
833 )
835 self._mark_resource_cache_as_dirty()
837 error_str = self._get_error_str(result)
838 if error_str:
839 raise LinstorVolumeManagerError(
840 'Could not resize volume `{}` from SR `{}`: {}'
841 .format(volume_uuid, self._group_name, error_str)
842 )
844 def get_volume_name(self, volume_uuid):
845 """
846 Get the name of a particular volume.
847 :param str volume_uuid: The volume uuid of the name to get.
848 :return: The volume name.
849 :rtype: str
850 """
852 self._ensure_volume_exists(volume_uuid)
853 volume_properties = self._get_volume_properties(volume_uuid)
854 volume_name = volume_properties.get(self.PROP_VOLUME_NAME)
855 if volume_name:
856 return volume_name
857 raise LinstorVolumeManagerError(
858 'Failed to get volume name of {}'.format(volume_uuid)
859 )
861 def get_volume_size(self, volume_uuid):
862 """
863 Get the size of a particular volume.
864 :param str volume_uuid: The volume uuid of the size to get.
865 :return: The volume size.
866 :rtype: int
867 """
869 volume_name = self.get_volume_name(volume_uuid)
870 dfns = self._linstor.resource_dfn_list_raise(
871 query_volume_definitions=True,
872 filter_by_resource_definitions=[volume_name]
873 ).resource_definitions
875 size = dfns[0].volume_definitions[0].size
876 if size < 0:
877 raise LinstorVolumeManagerError(
878 'Failed to get volume size of: {}'.format(volume_uuid)
879 )
880 return size * 1024
882 def set_auto_promote_timeout(self, volume_uuid, timeout):
883 """
884 Define the blocking time of open calls when a DRBD
885 is already open on another host.
886 :param str volume_uuid: The volume uuid to modify.
887 """
889 volume_name = self.get_volume_name(volume_uuid)
890 result = self._linstor.resource_dfn_modify(volume_name, {
891 'DrbdOptions/Resource/auto-promote-timeout': timeout
892 })
893 error_str = self._get_error_str(result)
894 if error_str:
895 raise LinstorVolumeManagerError(
896 'Could not change the auto promote timeout of `{}`: {}'
897 .format(volume_uuid, error_str)
898 )
900 def get_volume_info(self, volume_uuid):
901 """
902 Get the volume info of a particular volume.
903 :param str volume_uuid: The volume uuid of the volume info to get.
904 :return: The volume info.
905 :rtype: VolumeInfo
906 """
908 volume_name = self.get_volume_name(volume_uuid)
909 return self._get_volumes_info()[volume_name]
911 def get_device_path(self, volume_uuid):
912 """
913 Get the dev path of a volume, create a diskless if necessary.
914 :param str volume_uuid: The volume uuid to get the dev path.
915 :return: The current device path of the volume.
916 :rtype: str
917 """
919 volume_name = self.get_volume_name(volume_uuid)
920 return self._find_device_path(volume_uuid, volume_name)
922 def get_volume_uuid_from_device_path(self, device_path):
923 """
924 Get the volume uuid of a device_path.
925 :param str device_path: The dev path to find the volume uuid.
926 :return: The volume uuid of the local device path.
927 :rtype: str
928 """
930 expected_volume_name = \
931 self.get_volume_name_from_device_path(device_path)
933 volume_names = self.get_volumes_with_name()
934 for volume_uuid, volume_name in volume_names.items():
935 if volume_name == expected_volume_name:
936 return volume_uuid
938 raise LinstorVolumeManagerError(
939 'Unable to find volume uuid from dev path `{}`'.format(device_path)
940 )
942 def get_volume_name_from_device_path(self, device_path):
943 """
944 Get the volume name of a device_path.
945 :param str device_path: The dev path to find the volume name.
946 :return: The volume name of the device path.
947 :rtype: str
948 """
950 # Assume that we have a path like this:
951 # - "/dev/drbd/by-res/xcp-volume-<UUID>/0"
952 # - "../xcp-volume-<UUID>/0"
953 if device_path.startswith(DRBD_BY_RES_PATH):
954 prefix_len = len(DRBD_BY_RES_PATH)
955 else:
956 assert device_path.startswith('../')
957 prefix_len = 3
959 res_name_end = device_path.find('/', prefix_len)
960 assert res_name_end != -1
961 return device_path[prefix_len:res_name_end]
963 def update_volume_uuid(self, volume_uuid, new_volume_uuid, force=False):
964 """
965 Change the uuid of a volume.
966 :param str volume_uuid: The volume to modify.
967 :param str new_volume_uuid: The new volume uuid to use.
968 :param bool force: If true we doesn't check if volume_uuid is in the
969 volume list. I.e. the volume can be marked as deleted but the volume
970 can still be in the LINSTOR KV store if the deletion has failed.
971 In specific cases like "undo" after a failed clone we must rename a bad
972 deleted VDI.
973 """
975 assert volume_uuid != new_volume_uuid
977 self._logger(
978 'Trying to update volume UUID {} to {}...'
979 .format(volume_uuid, new_volume_uuid)
980 )
981 if not force:
982 self._ensure_volume_exists(volume_uuid)
983 self.ensure_volume_is_not_locked(volume_uuid)
985 if new_volume_uuid in self._volumes:
986 raise LinstorVolumeManagerError(
987 'Volume `{}` already exists'.format(new_volume_uuid),
988 LinstorVolumeManagerError.ERR_VOLUME_EXISTS
989 )
991 volume_properties = self._get_volume_properties(volume_uuid)
992 if volume_properties.get(self.PROP_UPDATING_UUID_SRC):
993 raise LinstorVolumeManagerError(
994 'Cannot update volume uuid {}: invalid state'
995 .format(volume_uuid)
996 )
998 # 1. Copy in temp variables metadata and volume_name.
999 metadata = volume_properties.get(self.PROP_METADATA)
1000 volume_name = volume_properties.get(self.PROP_VOLUME_NAME)
1002 # 2. Switch to new volume namespace.
1003 volume_properties.namespace = self._build_volume_namespace(
1004 new_volume_uuid
1005 )
1007 if list(volume_properties.items()):
1008 raise LinstorVolumeManagerError(
1009 'Cannot update volume uuid {} to {}: '
1010 .format(volume_uuid, new_volume_uuid) +
1011 'this last one is not empty'
1012 )
1014 try:
1015 # 3. Mark new volume properties with PROP_UPDATING_UUID_SRC.
1016 # If we crash after that, the new properties can be removed
1017 # properly.
1018 volume_properties[self.PROP_NOT_EXISTS] = self.STATE_NOT_EXISTS
1019 volume_properties[self.PROP_UPDATING_UUID_SRC] = volume_uuid
1021 # 4. Copy the properties.
1022 # Note: On new volumes, during clone for example, the metadata
1023 # may be missing. So we must test it to avoid this error:
1024 # "None has to be a str/unicode, but is <type 'NoneType'>"
1025 if metadata:
1026 volume_properties[self.PROP_METADATA] = metadata
1027 volume_properties[self.PROP_VOLUME_NAME] = volume_name
1029 # 5. Ok!
1030 volume_properties[self.PROP_NOT_EXISTS] = self.STATE_EXISTS
1031 except Exception as e:
1032 try:
1033 # Clear the new volume properties in case of failure.
1034 assert volume_properties.namespace == \
1035 self._build_volume_namespace(new_volume_uuid)
1036 volume_properties.clear()
1037 except Exception as e:
1038 self._logger(
1039 'Failed to clear new volume properties: {} (ignoring...)'
1040 .format(e)
1041 )
1042 raise LinstorVolumeManagerError(
1043 'Failed to copy volume properties: {}'.format(e)
1044 )
1046 try:
1047 # 6. After this point, it's ok we can remove the
1048 # PROP_UPDATING_UUID_SRC property and clear the src properties
1049 # without problems.
1051 # 7. Switch to old volume namespace.
1052 volume_properties.namespace = self._build_volume_namespace(
1053 volume_uuid
1054 )
1055 volume_properties.clear()
1057 # 8. Switch a last time to new volume namespace.
1058 volume_properties.namespace = self._build_volume_namespace(
1059 new_volume_uuid
1060 )
1061 volume_properties.pop(self.PROP_UPDATING_UUID_SRC)
1062 except Exception as e:
1063 raise LinstorVolumeManagerError(
1064 'Failed to clear volume properties '
1065 'after volume uuid update: {}'.format(e)
1066 )
1068 self._volumes.remove(volume_uuid)
1069 self._volumes.add(new_volume_uuid)
1071 self._logger(
1072 'UUID update succeeded of {} to {}! (properties={})'
1073 .format(
1074 volume_uuid, new_volume_uuid,
1075 self._get_filtered_properties(volume_properties)
1076 )
1077 )
1079 def update_volume_name(self, volume_uuid, volume_name):
1080 """
1081 Change the volume name of a volume.
1082 :param str volume_uuid: The volume to modify.
1083 :param str volume_name: The volume_name to use.
1084 """
1086 self._ensure_volume_exists(volume_uuid)
1087 self.ensure_volume_is_not_locked(volume_uuid)
1088 if not volume_name.startswith(self.PREFIX_VOLUME):
1089 raise LinstorVolumeManagerError(
1090 'Volume name `{}` must be start with `{}`'
1091 .format(volume_name, self.PREFIX_VOLUME)
1092 )
1094 if volume_name not in self._fetch_resource_names():
1095 raise LinstorVolumeManagerError(
1096 'Volume `{}` doesn\'t exist'.format(volume_name)
1097 )
1099 volume_properties = self._get_volume_properties(volume_uuid)
1100 volume_properties[self.PROP_VOLUME_NAME] = volume_name
1102 def get_usage_states(self, volume_uuid):
1103 """
1104 Check if a volume is currently used.
1105 :param str volume_uuid: The volume uuid to check.
1106 :return: A dictionnary that contains states.
1107 :rtype: dict(str, bool or None)
1108 """
1110 states = {}
1112 volume_name = self.get_volume_name(volume_uuid)
1113 for resource_state in self._linstor.resource_list_raise(
1114 filter_by_resources=[volume_name]
1115 ).resource_states:
1116 states[resource_state.node_name] = resource_state.in_use
1118 return states
1120 def get_volume_openers(self, volume_uuid):
1121 """
1122 Get openers of a volume.
1123 :param str volume_uuid: The volume uuid to monitor.
1124 :return: A dictionnary that contains openers.
1125 :rtype: dict(str, obj)
1126 """
1127 return get_all_volume_openers(self.get_volume_name(volume_uuid), '0')
1129 def get_volumes_with_name(self):
1130 """
1131 Give a volume dictionnary that contains names actually owned.
1132 :return: A volume/name dict.
1133 :rtype: dict(str, str)
1134 """
1135 return self._get_volumes_by_property(self.REG_VOLUME_NAME)
1137 def get_volumes_with_info(self):
1138 """
1139 Give a volume dictionnary that contains VolumeInfos.
1140 :return: A volume/VolumeInfo dict.
1141 :rtype: dict(str, VolumeInfo)
1142 """
1144 volumes = {}
1146 all_volume_info = self._get_volumes_info()
1147 volume_names = self.get_volumes_with_name()
1148 for volume_uuid, volume_name in volume_names.items():
1149 if volume_name:
1150 volume_info = all_volume_info.get(volume_name)
1151 if volume_info:
1152 volumes[volume_uuid] = volume_info
1153 continue
1155 # Well I suppose if this volume is not available,
1156 # LINSTOR has been used directly without using this API.
1157 volumes[volume_uuid] = self.VolumeInfo('')
1159 return volumes
1161 def get_volumes_with_metadata(self):
1162 """
1163 Give a volume dictionnary that contains metadata.
1164 :return: A volume/metadata dict.
1165 :rtype: dict(str, dict)
1166 """
1168 volumes = {}
1170 metadata = self._get_volumes_by_property(self.REG_METADATA)
1171 for volume_uuid, volume_metadata in metadata.items():
1172 if volume_metadata:
1173 volume_metadata = json.loads(volume_metadata)
1174 if isinstance(volume_metadata, dict):
1175 volumes[volume_uuid] = volume_metadata
1176 continue
1177 raise LinstorVolumeManagerError(
1178 'Expected dictionary in volume metadata: {}'
1179 .format(volume_uuid)
1180 )
1182 volumes[volume_uuid] = {}
1184 return volumes
1186 def get_volume_metadata(self, volume_uuid):
1187 """
1188 Get the metadata of a volume.
1189 :return: Dictionary that contains metadata.
1190 :rtype: dict
1191 """
1193 self._ensure_volume_exists(volume_uuid)
1194 volume_properties = self._get_volume_properties(volume_uuid)
1195 metadata = volume_properties.get(self.PROP_METADATA)
1196 if metadata:
1197 metadata = json.loads(metadata)
1198 if isinstance(metadata, dict):
1199 return metadata
1200 raise LinstorVolumeManagerError(
1201 'Expected dictionary in volume metadata: {}'
1202 .format(volume_uuid)
1203 )
1204 return {}
1206 def set_volume_metadata(self, volume_uuid, metadata):
1207 """
1208 Set the metadata of a volume.
1209 :param dict metadata: Dictionary that contains metadata.
1210 """
1212 self._ensure_volume_exists(volume_uuid)
1213 self.ensure_volume_is_not_locked(volume_uuid)
1215 assert isinstance(metadata, dict)
1216 volume_properties = self._get_volume_properties(volume_uuid)
1217 volume_properties[self.PROP_METADATA] = json.dumps(metadata)
1219 def update_volume_metadata(self, volume_uuid, metadata):
1220 """
1221 Update the metadata of a volume. It modify only the given keys.
1222 It doesn't remove unreferenced key instead of set_volume_metadata.
1223 :param dict metadata: Dictionary that contains metadata.
1224 """
1226 self._ensure_volume_exists(volume_uuid)
1227 self.ensure_volume_is_not_locked(volume_uuid)
1229 assert isinstance(metadata, dict)
1230 volume_properties = self._get_volume_properties(volume_uuid)
1232 current_metadata = json.loads(
1233 volume_properties.get(self.PROP_METADATA, '{}')
1234 )
1235 if not isinstance(metadata, dict):
1236 raise LinstorVolumeManagerError(
1237 'Expected dictionary in volume metadata: {}'
1238 .format(volume_uuid)
1239 )
1241 for key, value in metadata.items():
1242 current_metadata[key] = value
1243 volume_properties[self.PROP_METADATA] = json.dumps(current_metadata)
1245 def shallow_clone_volume(self, volume_uuid, clone_uuid, persistent=True):
1246 """
1247 Clone a volume. Do not copy the data, this method creates a new volume
1248 with the same size.
1249 :param str volume_uuid: The volume to clone.
1250 :param str clone_uuid: The cloned volume.
1251 :param bool persistent: If false the volume will be unavailable
1252 on the next constructor call LinstorSR(...).
1253 :return: The current device path of the cloned volume.
1254 :rtype: str
1255 """
1257 volume_name = self.get_volume_name(volume_uuid)
1258 self.ensure_volume_is_not_locked(volume_uuid)
1260 # 1. Find ideal nodes + size to use.
1261 ideal_node_names, size = self._get_volume_node_names_and_size(
1262 volume_name
1263 )
1264 if size <= 0:
1265 raise LinstorVolumeManagerError(
1266 'Invalid size of {} for volume `{}`'.format(size, volume_name)
1267 )
1269 # 2. Create clone!
1270 return self.create_volume(clone_uuid, size, persistent)
1272 def remove_resourceless_volumes(self):
1273 """
1274 Remove all volumes without valid or non-empty name
1275 (i.e. without LINSTOR resource). It's different than
1276 LinstorVolumeManager constructor that takes a `repair` param that
1277 removes volumes with `PROP_NOT_EXISTS` to 1.
1278 """
1280 resource_names = self._fetch_resource_names()
1281 for volume_uuid, volume_name in self.get_volumes_with_name().items():
1282 if not volume_name or volume_name not in resource_names:
1283 # Don't force, we can be sure of what's happening.
1284 self.destroy_volume(volume_uuid)
1286 def destroy(self):
1287 """
1288 Destroy this SR. Object should not be used after that.
1289 :param bool force: Try to destroy volumes before if true.
1290 """
1292 # 1. Ensure volume list is empty. No cost.
1293 if self._volumes:
1294 raise LinstorVolumeManagerError(
1295 'Cannot destroy LINSTOR volume manager: '
1296 'It exists remaining volumes'
1297 )
1299 # 2. Fetch ALL resource names.
1300 # This list may therefore contain volumes created outside
1301 # the scope of the driver.
1302 resource_names = self._fetch_resource_names(ignore_deleted=False)
1303 try:
1304 resource_names.remove(DATABASE_VOLUME_NAME)
1305 except KeyError:
1306 # Really strange to reach that point.
1307 # Normally we always have the database volume in the list.
1308 pass
1310 # 3. Ensure the resource name list is entirely empty...
1311 if resource_names:
1312 raise LinstorVolumeManagerError(
1313 'Cannot destroy LINSTOR volume manager: '
1314 'It exists remaining volumes (created externally or being deleted)'
1315 )
1317 # 4. Destroying...
1318 controller_is_running = self._controller_is_running()
1319 uri = 'linstor://localhost'
1320 try:
1321 if controller_is_running:
1322 self._start_controller(start=False)
1324 # 4.1. Umount LINSTOR database.
1325 self._mount_database_volume(
1326 self.build_device_path(DATABASE_VOLUME_NAME),
1327 mount=False,
1328 force=True
1329 )
1331 # 4.2. Refresh instance.
1332 self._start_controller(start=True)
1333 self._linstor = self._create_linstor_instance(
1334 uri, keep_uri_unmodified=True
1335 )
1337 # 4.3. Destroy database volume.
1338 self._destroy_resource(DATABASE_VOLUME_NAME)
1340 # 4.4. Refresh linstor connection.
1341 # Without we get this error:
1342 # "Cannot delete resource group 'xcp-sr-linstor_group_thin_device' because it has existing resource definitions.."
1343 # Because the deletion of the databse was not seen by Linstor for some reason.
1344 # It seems a simple refresh of the Linstor connection make it aware of the deletion.
1345 self._linstor.disconnect()
1346 self._linstor.connect()
1348 # 4.5. Destroy group and storage pools.
1349 self._destroy_resource_group(self._linstor, self._group_name)
1350 for pool in self._get_storage_pools(force=True):
1351 self._destroy_storage_pool(
1352 self._linstor, pool.name, pool.node_name
1353 )
1354 except Exception as e:
1355 self._start_controller(start=controller_is_running)
1356 raise e
1358 try:
1359 self._start_controller(start=False)
1360 for file in glob.glob(DATABASE_PATH + '/'):
1361 os.remove(file)
1362 except Exception as e:
1363 util.SMlog(
1364 'Ignoring failure after LINSTOR SR destruction: {}'
1365 .format(e)
1366 )
1368 def find_up_to_date_diskful_nodes(self, volume_uuid):
1369 """
1370 Find all nodes that contain a specific volume using diskful disks.
1371 The disk must be up to data to be used.
1372 :param str volume_uuid: The volume to use.
1373 :return: The available nodes.
1374 :rtype: tuple(set(str), str)
1375 """
1377 volume_name = self.get_volume_name(volume_uuid)
1379 in_use_by = None
1380 node_names = set()
1382 resource_states = filter(
1383 lambda resource_state: resource_state.name == volume_name,
1384 self._get_resource_cache().resource_states
1385 )
1387 for resource_state in resource_states:
1388 volume_state = resource_state.volume_states[0]
1389 if volume_state.disk_state == 'UpToDate':
1390 node_names.add(resource_state.node_name)
1391 if resource_state.in_use:
1392 in_use_by = resource_state.node_name
1394 return (node_names, in_use_by)
1396 def invalidate_resource_cache(self):
1397 """
1398 If resources are impacted by external commands like vhdutil,
1399 it's necessary to call this function to invalidate current resource
1400 cache.
1401 """
1402 self._mark_resource_cache_as_dirty()
1404 def has_node(self, node_name):
1405 """
1406 Check if a node exists in the LINSTOR database.
1407 :rtype: bool
1408 """
1409 result = self._linstor.node_list()
1410 error_str = self._get_error_str(result)
1411 if error_str:
1412 raise LinstorVolumeManagerError(
1413 'Failed to list nodes using `{}`: {}'
1414 .format(node_name, error_str)
1415 )
1416 return bool(result[0].node(node_name))
1418 def create_node(self, node_name, ip):
1419 """
1420 Create a new node in the LINSTOR database.
1421 :param str node_name: Node name to use.
1422 :param str ip: Host IP to communicate.
1423 """
1424 result = self._linstor.node_create(
1425 node_name,
1426 linstor.consts.VAL_NODE_TYPE_CMBD,
1427 ip
1428 )
1429 errors = self._filter_errors(result)
1430 if errors:
1431 error_str = self._get_error_str(errors)
1432 raise LinstorVolumeManagerError(
1433 'Failed to create node `{}`: {}'.format(node_name, error_str)
1434 )
1436 def destroy_node(self, node_name):
1437 """
1438 Destroy a node in the LINSTOR database.
1439 :param str node_name: Node name to remove.
1440 """
1441 result = self._linstor.node_delete(node_name)
1442 errors = self._filter_errors(result)
1443 if errors:
1444 error_str = self._get_error_str(errors)
1445 raise LinstorVolumeManagerError(
1446 'Failed to destroy node `{}`: {}'.format(node_name, error_str)
1447 )
1449 def create_node_interface(self, node_name, name, ip):
1450 """
1451 Create a new node interface in the LINSTOR database.
1452 :param str node_name: Node name of the interface to use.
1453 :param str name: Interface to create.
1454 :param str ip: IP of the interface.
1455 """
1456 result = self._linstor.netinterface_create(node_name, name, ip)
1457 errors = self._filter_errors(result)
1458 if errors:
1459 error_str = self._get_error_str(errors)
1460 raise LinstorVolumeManagerError(
1461 'Failed to create node interface on `{}`: {}'.format(node_name, error_str)
1462 )
1464 def destroy_node_interface(self, node_name, name):
1465 """
1466 Destroy a node interface in the LINSTOR database.
1467 :param str node_name: Node name of the interface to remove.
1468 :param str name: Interface to remove.
1469 """
1470 result = self._linstor.netinterface_delete(node_name, name)
1471 errors = self._filter_errors(result)
1472 if errors:
1473 error_str = self._get_error_str(errors)
1474 raise LinstorVolumeManagerError(
1475 'Failed to destroy node interface on `{}`: {}'.format(node_name, error_str)
1476 )
1478 def modify_node_interface(self, node_name, name, ip):
1479 """
1480 Modify a node interface in the LINSTOR database. Create it if necessary.
1481 :param str node_name: Node name of the interface to use.
1482 :param str name: Interface to modify or create.
1483 :param str ip: IP of the interface.
1484 """
1485 result = self._linstor.netinterface_create(node_name, name, ip)
1486 errors = self._filter_errors(result)
1487 if not errors:
1488 return
1490 if self._check_errors(errors, [linstor.consts.FAIL_EXISTS_NET_IF]):
1491 result = self._linstor.netinterface_modify(node_name, name, ip)
1492 errors = self._filter_errors(result)
1493 if not errors:
1494 return
1496 error_str = self._get_error_str(errors)
1497 raise LinstorVolumeManagerError(
1498 'Unable to modify interface on `{}`: {}'.format(node_name, error_str)
1499 )
1501 def list_node_interfaces(self, node_name):
1502 """
1503 List all node interfaces.
1504 :param str node_name: Node name to use to list interfaces.
1505 :rtype: list
1506 :
1507 """
1508 result = self._linstor.net_interface_list(node_name)
1509 if not result:
1510 raise LinstorVolumeManagerError(
1511 'Unable to list interfaces on `{}`: no list received'.format(node_name)
1512 )
1514 interfaces = {}
1515 for interface in result:
1516 interface = interface._rest_data
1517 interfaces[interface['name']] = {
1518 'address': interface['address'],
1519 'active': interface['is_active']
1520 }
1521 return interfaces
1523 def set_node_preferred_interface(self, node_name, name):
1524 """
1525 Set the preferred interface to use on a node.
1526 :param str node_name: Node name of the interface.
1527 :param str name: Preferred interface to use.
1528 """
1529 result = self._linstor.node_modify(node_name, property_dict={'PrefNic': name})
1530 errors = self._filter_errors(result)
1531 if errors:
1532 error_str = self._get_error_str(errors)
1533 raise LinstorVolumeManagerError(
1534 'Failed to set preferred node interface on `{}`: {}'.format(node_name, error_str)
1535 )
1537 def get_nodes_info(self):
1538 """
1539 Get all nodes + statuses, used or not by the pool.
1540 :rtype: dict(str, dict)
1541 """
1542 try:
1543 nodes = {}
1544 for node in self._linstor.node_list_raise().nodes:
1545 nodes[node.name] = node.connection_status
1546 return nodes
1547 except Exception as e:
1548 raise LinstorVolumeManagerError(
1549 'Failed to get all nodes: `{}`'.format(e)
1550 )
1552 def get_storage_pools_info(self):
1553 """
1554 Give all storage pools of current group name.
1555 :rtype: dict(str, list)
1556 """
1557 storage_pools = {}
1558 for pool in self._get_storage_pools(force=True):
1559 if pool.node_name not in storage_pools:
1560 storage_pools[pool.node_name] = []
1562 size = -1
1563 capacity = -1
1565 space = pool.free_space
1566 if space:
1567 size = space.free_capacity
1568 if size < 0:
1569 size = -1
1570 else:
1571 size *= 1024
1572 capacity = space.total_capacity
1573 if capacity <= 0:
1574 capacity = -1
1575 else:
1576 capacity *= 1024
1578 storage_pools[pool.node_name].append({
1579 'storage-pool-name': pool.name,
1580 'uuid': pool.uuid,
1581 'free-size': size,
1582 'capacity': capacity
1583 })
1585 return storage_pools
1587 def get_resources_info(self):
1588 """
1589 Give all resources of current group name.
1590 :rtype: dict(str, list)
1591 """
1592 resources = {}
1593 resource_list = self._linstor.resource_list_raise()
1594 for resource in resource_list.resources:
1595 if resource.name not in resources:
1596 resources[resource.name] = {}
1598 resources[resource.name][resource.node_name] = {
1599 'volumes': [],
1600 'diskful': linstor.consts.FLAG_DISKLESS not in resource.flags,
1601 'tie-breaker': linstor.consts.FLAG_TIE_BREAKER in resource.flags
1602 }
1604 for volume in resource.volumes:
1605 # We ignore diskless pools of the form "DfltDisklessStorPool".
1606 if volume.storage_pool_name != self._group_name:
1607 continue
1609 usable_size = volume.usable_size
1610 if usable_size < 0:
1611 usable_size = -1
1612 else:
1613 usable_size *= 1024
1615 allocated_size = volume.allocated_size
1616 if allocated_size < 0:
1617 allocated_size = -1
1618 else:
1619 allocated_size *= 1024
1621 resources[resource.name][resource.node_name]['volumes'].append({
1622 'storage-pool-name': volume.storage_pool_name,
1623 'uuid': volume.uuid,
1624 'number': volume.number,
1625 'device-path': volume.device_path,
1626 'usable-size': usable_size,
1627 'allocated-size': allocated_size
1628 })
1630 for resource_state in resource_list.resource_states:
1631 resource = resources[resource_state.rsc_name][resource_state.node_name]
1632 resource['in-use'] = resource_state.in_use
1634 volumes = resource['volumes']
1635 for volume_state in resource_state.volume_states:
1636 volume = next((x for x in volumes if x['number'] == volume_state.number), None)
1637 if volume:
1638 volume['disk-state'] = volume_state.disk_state
1640 return resources
1642 def get_database_path(self):
1643 """
1644 Get the database path.
1645 :return: The current database path.
1646 :rtype: str
1647 """
1648 return self._request_database_path(self._linstor)
1650 @classmethod
1651 def create_sr(
1652 cls, group_name, ips, redundancy,
1653 thin_provisioning, auto_quorum,
1654 logger=default_logger.__func__
1655 ):
1656 """
1657 Create a new SR on the given nodes.
1658 :param str group_name: The SR group_name to use.
1659 :param set(str) ips: Node ips.
1660 :param int redundancy: How many copy of volumes should we store?
1661 :param bool thin_provisioning: Use thin or thick provisioning.
1662 :param bool auto_quorum: DB quorum is monitored by LINSTOR.
1663 :param function logger: Function to log messages.
1664 :return: A new LinstorSr instance.
1665 :rtype: LinstorSr
1666 """
1668 try:
1669 cls._start_controller(start=True)
1670 sr = cls._create_sr(
1671 group_name,
1672 ips,
1673 redundancy,
1674 thin_provisioning,
1675 auto_quorum,
1676 logger
1677 )
1678 finally:
1679 # Controller must be stopped and volume unmounted because
1680 # it is the role of the drbd-reactor daemon to do the right
1681 # actions.
1682 cls._start_controller(start=False)
1683 cls._mount_volume(
1684 cls.build_device_path(DATABASE_VOLUME_NAME),
1685 DATABASE_PATH,
1686 mount=False
1687 )
1688 return sr
1690 @classmethod
1691 def _create_sr(
1692 cls, group_name, ips, redundancy,
1693 thin_provisioning, auto_quorum,
1694 logger=default_logger.__func__
1695 ):
1696 # 1. Check if SR already exists.
1697 uri = 'linstor://localhost'
1699 lin = cls._create_linstor_instance(uri, keep_uri_unmodified=True)
1701 node_names = list(ips.keys())
1702 for node_name, ip in ips.items():
1703 while True:
1704 # Try to create node.
1705 result = lin.node_create(
1706 node_name,
1707 linstor.consts.VAL_NODE_TYPE_CMBD,
1708 ip
1709 )
1711 errors = cls._filter_errors(result)
1712 if cls._check_errors(
1713 errors, [linstor.consts.FAIL_EXISTS_NODE]
1714 ):
1715 # If it already exists, remove, then recreate.
1716 result = lin.node_delete(node_name)
1717 error_str = cls._get_error_str(result)
1718 if error_str:
1719 raise LinstorVolumeManagerError(
1720 'Failed to remove old node `{}`: {}'
1721 .format(node_name, error_str)
1722 )
1723 elif not errors:
1724 break # Created!
1725 else:
1726 raise LinstorVolumeManagerError(
1727 'Failed to create node `{}` with ip `{}`: {}'.format(
1728 node_name, ip, cls._get_error_str(errors)
1729 )
1730 )
1732 driver_pool_name = group_name
1733 base_group_name = group_name
1734 group_name = cls._build_group_name(group_name)
1735 pools = lin.storage_pool_list_raise(filter_by_stor_pools=[group_name])
1736 pools = pools.storage_pools
1737 if pools:
1738 existing_node_names = [pool.node_name for pool in pools]
1739 raise LinstorVolumeManagerError(
1740 'Unable to create SR `{}`. It already exists on node(s): {}'
1741 .format(group_name, existing_node_names)
1742 )
1744 if lin.resource_group_list_raise(
1745 [group_name]
1746 ).resource_groups:
1747 if not lin.resource_dfn_list_raise().resource_definitions:
1748 backup_path = cls._create_database_backup_path()
1749 logger(
1750 'Group name already exists `{}` without LVs. '
1751 'Ignoring and moving the config files in {}'.format(group_name, backup_path)
1752 )
1753 cls._move_files(DATABASE_PATH, backup_path)
1754 else:
1755 raise LinstorVolumeManagerError(
1756 'Unable to create SR `{}`: The group name already exists'
1757 .format(group_name)
1758 )
1760 if thin_provisioning:
1761 driver_pool_parts = driver_pool_name.split('/')
1762 if not len(driver_pool_parts) == 2:
1763 raise LinstorVolumeManagerError(
1764 'Invalid group name using thin provisioning. '
1765 'Expected format: \'VG/LV`\''
1766 )
1768 # 2. Create storage pool on each node + resource group.
1769 reg_volume_group_not_found = re.compile(
1770 ".*Volume group '.*' not found$"
1771 )
1773 i = 0
1774 try:
1775 # 2.a. Create storage pools.
1776 storage_pool_count = 0
1777 while i < len(node_names):
1778 node_name = node_names[i]
1780 result = lin.storage_pool_create(
1781 node_name=node_name,
1782 storage_pool_name=group_name,
1783 storage_driver='LVM_THIN' if thin_provisioning else 'LVM',
1784 driver_pool_name=driver_pool_name
1785 )
1787 errors = linstor.Linstor.filter_api_call_response_errors(
1788 result
1789 )
1790 if errors:
1791 if len(errors) == 1 and errors[0].is_error(
1792 linstor.consts.FAIL_STOR_POOL_CONFIGURATION_ERROR
1793 ) and reg_volume_group_not_found.match(errors[0].message):
1794 logger(
1795 'Volume group `{}` not found on `{}`. Ignoring...'
1796 .format(group_name, node_name)
1797 )
1798 cls._destroy_storage_pool(lin, group_name, node_name)
1799 else:
1800 error_str = cls._get_error_str(result)
1801 raise LinstorVolumeManagerError(
1802 'Could not create SP `{}` on node `{}`: {}'
1803 .format(group_name, node_name, error_str)
1804 )
1805 else:
1806 storage_pool_count += 1
1807 i += 1
1809 if not storage_pool_count:
1810 raise LinstorVolumeManagerError(
1811 'Unable to create SR `{}`: No VG group found'.format(
1812 group_name,
1813 )
1814 )
1816 # 2.b. Create resource group.
1817 rg_creation_attempt = 0
1818 while True:
1819 result = lin.resource_group_create(
1820 name=group_name,
1821 place_count=redundancy,
1822 storage_pool=group_name,
1823 diskless_on_remaining=False
1824 )
1825 error_str = cls._get_error_str(result)
1826 if not error_str:
1827 break
1829 errors = cls._filter_errors(result)
1830 if cls._check_errors(errors, [linstor.consts.FAIL_EXISTS_RSC_GRP]):
1831 rg_creation_attempt += 1
1832 if rg_creation_attempt < 2:
1833 try:
1834 cls._destroy_resource_group(lin, group_name)
1835 except Exception as e:
1836 error_str = 'Failed to destroy old and empty RG: {}'.format(e)
1837 else:
1838 continue
1840 raise LinstorVolumeManagerError(
1841 'Could not create RG `{}`: {}'.format(group_name, error_str)
1842 )
1844 # 2.c. Create volume group.
1845 result = lin.volume_group_create(group_name)
1846 error_str = cls._get_error_str(result)
1847 if error_str:
1848 raise LinstorVolumeManagerError(
1849 'Could not create VG `{}`: {}'.format(
1850 group_name, error_str
1851 )
1852 )
1854 # 3. Create the LINSTOR database volume and mount it.
1855 try:
1856 logger('Creating database volume...')
1857 volume_path = cls._create_database_volume(
1858 lin, group_name, node_names, redundancy, auto_quorum
1859 )
1860 except LinstorVolumeManagerError as e:
1861 if e.code != LinstorVolumeManagerError.ERR_VOLUME_EXISTS:
1862 logger('Destroying database volume after creation fail...')
1863 cls._force_destroy_database_volume(lin, group_name)
1864 raise
1866 try:
1867 logger('Mounting database volume...')
1869 # First we must disable the controller to move safely the
1870 # LINSTOR config.
1871 cls._start_controller(start=False)
1873 cls._mount_database_volume(volume_path)
1874 except Exception as e:
1875 # Ensure we are connected because controller has been
1876 # restarted during mount call.
1877 logger('Destroying database volume after mount fail...')
1879 try:
1880 cls._start_controller(start=True)
1881 except Exception:
1882 pass
1884 lin = cls._create_linstor_instance(
1885 uri, keep_uri_unmodified=True
1886 )
1887 cls._force_destroy_database_volume(lin, group_name)
1888 raise e
1890 cls._start_controller(start=True)
1891 lin = cls._create_linstor_instance(uri, keep_uri_unmodified=True)
1893 # 4. Remove storage pools/resource/volume group in the case of errors.
1894 except Exception as e:
1895 logger('Destroying resource group and storage pools after fail...')
1896 try:
1897 cls._destroy_resource_group(lin, group_name)
1898 except Exception as e2:
1899 logger('Failed to destroy resource group: {}'.format(e2))
1900 pass
1901 j = 0
1902 i = min(i, len(node_names) - 1)
1903 while j <= i:
1904 try:
1905 cls._destroy_storage_pool(lin, group_name, node_names[j])
1906 except Exception as e2:
1907 logger('Failed to destroy resource group: {}'.format(e2))
1908 pass
1909 j += 1
1910 raise e
1912 # 5. Return new instance.
1913 instance = cls.__new__(cls)
1914 instance._linstor = lin
1915 instance._logger = logger
1916 instance._redundancy = redundancy
1917 instance._base_group_name = base_group_name
1918 instance._group_name = group_name
1919 instance._volumes = set()
1920 instance._storage_pools_time = 0
1921 instance._kv_cache = instance._create_kv_cache()
1922 instance._resource_cache = None
1923 instance._resource_cache_dirty = True
1924 instance._volume_info_cache = None
1925 instance._volume_info_cache_dirty = True
1926 return instance
1928 @classmethod
1929 def build_device_path(cls, volume_name):
1930 """
1931 Build a device path given a volume name.
1932 :param str volume_name: The volume name to use.
1933 :return: A valid or not device path.
1934 :rtype: str
1935 """
1937 return '{}{}/0'.format(cls.DEV_ROOT_PATH, volume_name)
1939 @classmethod
1940 def build_volume_name(cls, base_name):
1941 """
1942 Build a volume name given a base name (i.e. a UUID).
1943 :param str volume_name: The volume name to use.
1944 :return: A valid or not device path.
1945 :rtype: str
1946 """
1947 return '{}{}'.format(cls.PREFIX_VOLUME, base_name)
1949 @classmethod
1950 def round_up_volume_size(cls, volume_size):
1951 """
1952 Align volume size on higher multiple of BLOCK_SIZE.
1953 :param int volume_size: The volume size to align.
1954 :return: An aligned volume size.
1955 :rtype: int
1956 """
1957 return round_up(volume_size, cls.BLOCK_SIZE)
1959 @classmethod
1960 def round_down_volume_size(cls, volume_size):
1961 """
1962 Align volume size on lower multiple of BLOCK_SIZE.
1963 :param int volume_size: The volume size to align.
1964 :return: An aligned volume size.
1965 :rtype: int
1966 """
1967 return round_down(volume_size, cls.BLOCK_SIZE)
1969 # --------------------------------------------------------------------------
1970 # Private helpers.
1971 # --------------------------------------------------------------------------
1973 def _create_kv_cache(self):
1974 self._kv_cache = self._create_linstor_kv('/')
1975 self._kv_cache_dirty = False
1976 return self._kv_cache
1978 def _get_kv_cache(self):
1979 if self._kv_cache_dirty:
1980 self._kv_cache = self._create_kv_cache()
1981 return self._kv_cache
1983 def _create_resource_cache(self):
1984 self._resource_cache = self._linstor.resource_list_raise()
1985 self._resource_cache_dirty = False
1986 return self._resource_cache
1988 def _get_resource_cache(self):
1989 if self._resource_cache_dirty:
1990 self._resource_cache = self._create_resource_cache()
1991 return self._resource_cache
1993 def _mark_resource_cache_as_dirty(self):
1994 self._resource_cache_dirty = True
1995 self._volume_info_cache_dirty = True
1997 # --------------------------------------------------------------------------
1999 def _ensure_volume_exists(self, volume_uuid):
2000 if volume_uuid not in self._volumes:
2001 raise LinstorVolumeManagerError(
2002 'volume `{}` doesn\'t exist'.format(volume_uuid),
2003 LinstorVolumeManagerError.ERR_VOLUME_NOT_EXISTS
2004 )
2006 def _find_best_size_candidates(self):
2007 result = self._linstor.resource_group_qmvs(self._group_name)
2008 error_str = self._get_error_str(result)
2009 if error_str:
2010 raise LinstorVolumeManagerError(
2011 'Failed to get max volume size allowed of SR `{}`: {}'.format(
2012 self._group_name,
2013 error_str
2014 )
2015 )
2016 return result[0].candidates
2018 def _fetch_resource_names(self, ignore_deleted=True):
2019 resource_names = set()
2020 dfns = self._linstor.resource_dfn_list_raise().resource_definitions
2021 for dfn in dfns:
2022 if dfn.resource_group_name == self._group_name and (
2023 ignore_deleted or
2024 linstor.consts.FLAG_DELETE not in dfn.flags
2025 ):
2026 resource_names.add(dfn.name)
2027 return resource_names
2029 def _get_volumes_info(self, volume_name=None):
2030 all_volume_info = {}
2032 if not self._volume_info_cache_dirty:
2033 return self._volume_info_cache
2035 for resource in self._get_resource_cache().resources:
2036 if resource.name not in all_volume_info:
2037 current = all_volume_info[resource.name] = self.VolumeInfo(
2038 resource.name
2039 )
2040 else:
2041 current = all_volume_info[resource.name]
2043 if linstor.consts.FLAG_DISKLESS not in resource.flags:
2044 current.diskful.append(resource.node_name)
2046 for volume in resource.volumes:
2047 # We ignore diskless pools of the form "DfltDisklessStorPool".
2048 if volume.storage_pool_name == self._group_name:
2049 if volume.allocated_size < 0:
2050 raise LinstorVolumeManagerError(
2051 'Failed to get allocated size of `{}` on `{}`'
2052 .format(resource.name, volume.storage_pool_name)
2053 )
2054 allocated_size = volume.allocated_size
2056 current.allocated_size = current.allocated_size and \
2057 max(current.allocated_size, allocated_size) or \
2058 allocated_size
2060 usable_size = volume.usable_size
2061 if usable_size > 0 and (
2062 usable_size < current.virtual_size or
2063 not current.virtual_size
2064 ):
2065 current.virtual_size = usable_size
2067 if current.virtual_size <= 0:
2068 raise LinstorVolumeManagerError(
2069 'Failed to get usable size of `{}` on `{}`'
2070 .format(resource.name, volume.storage_pool_name)
2071 )
2073 for current in all_volume_info.values():
2074 current.allocated_size *= 1024
2075 current.virtual_size *= 1024
2077 self._volume_info_cache_dirty = False
2078 self._volume_info_cache = all_volume_info
2080 return all_volume_info
2082 def _get_volume_node_names_and_size(self, volume_name):
2083 node_names = set()
2084 size = -1
2085 for resource in self._linstor.resource_list_raise(
2086 filter_by_resources=[volume_name]
2087 ).resources:
2088 for volume in resource.volumes:
2089 # We ignore diskless pools of the form "DfltDisklessStorPool".
2090 if volume.storage_pool_name == self._group_name:
2091 node_names.add(resource.node_name)
2093 current_size = volume.usable_size
2094 if current_size < 0:
2095 raise LinstorVolumeManagerError(
2096 'Failed to get usable size of `{}` on `{}`'
2097 .format(resource.name, volume.storage_pool_name)
2098 )
2100 if size < 0:
2101 size = current_size
2102 else:
2103 size = min(size, current_size)
2105 return (node_names, size * 1024)
2107 def _compute_size(self, attr):
2108 capacity = 0
2109 for pool in self._get_storage_pools(force=True):
2110 space = pool.free_space
2111 if space:
2112 size = getattr(space, attr)
2113 if size < 0:
2114 raise LinstorVolumeManagerError(
2115 'Failed to get pool {} attr of `{}`'
2116 .format(attr, pool.node_name)
2117 )
2118 capacity += size
2119 return capacity * 1024
2121 def _get_node_names(self):
2122 node_names = set()
2123 for pool in self._get_storage_pools():
2124 node_names.add(pool.node_name)
2125 return node_names
2127 def _get_storage_pools(self, force=False):
2128 cur_time = time.time()
2129 elsaped_time = cur_time - self._storage_pools_time
2131 if force or elsaped_time >= self.STORAGE_POOLS_FETCH_INTERVAL:
2132 self._storage_pools = self._linstor.storage_pool_list_raise(
2133 filter_by_stor_pools=[self._group_name]
2134 ).storage_pools
2135 self._storage_pools_time = time.time()
2137 return self._storage_pools
2139 def _create_volume(
2140 self, volume_uuid, volume_name, size, place_resources
2141 ):
2142 size = self.round_up_volume_size(size)
2143 self._mark_resource_cache_as_dirty()
2145 def create_definition():
2146 self._check_volume_creation_errors(
2147 self._linstor.resource_group_spawn(
2148 rsc_grp_name=self._group_name,
2149 rsc_dfn_name=volume_name,
2150 vlm_sizes=['{}B'.format(size)],
2151 definitions_only=True
2152 ),
2153 volume_uuid,
2154 self._group_name
2155 )
2156 self._configure_volume_peer_slots(self._linstor, volume_name)
2158 def clean():
2159 try:
2160 self._destroy_volume(volume_uuid, force=True)
2161 except Exception as e:
2162 self._logger(
2163 'Unable to destroy volume {} after creation fail: {}'
2164 .format(volume_uuid, e)
2165 )
2167 def create():
2168 try:
2169 create_definition()
2170 if place_resources:
2171 # Basic case when we use the default redundancy of the group.
2172 self._check_volume_creation_errors(
2173 self._linstor.resource_auto_place(
2174 rsc_name=volume_name,
2175 place_count=self._redundancy,
2176 diskless_on_remaining=False
2177 ),
2178 volume_uuid,
2179 self._group_name
2180 )
2181 except LinstorVolumeManagerError as e:
2182 if e.code != LinstorVolumeManagerError.ERR_VOLUME_EXISTS:
2183 clean()
2184 raise
2185 except Exception:
2186 clean()
2187 raise
2189 util.retry(create, maxretry=5)
2191 def _create_volume_with_properties(
2192 self, volume_uuid, volume_name, size, place_resources
2193 ):
2194 if self.check_volume_exists(volume_uuid):
2195 raise LinstorVolumeManagerError(
2196 'Could not create volume `{}` from SR `{}`, it already exists'
2197 .format(volume_uuid, self._group_name) + ' in properties',
2198 LinstorVolumeManagerError.ERR_VOLUME_EXISTS
2199 )
2201 if volume_name in self._fetch_resource_names():
2202 raise LinstorVolumeManagerError(
2203 'Could not create volume `{}` from SR `{}`, '.format(
2204 volume_uuid, self._group_name
2205 ) + 'resource of the same name already exists in LINSTOR'
2206 )
2208 # I am paranoid.
2209 volume_properties = self._get_volume_properties(volume_uuid)
2210 if (volume_properties.get(self.PROP_NOT_EXISTS) is not None):
2211 raise LinstorVolumeManagerError(
2212 'Could not create volume `{}`, '.format(volume_uuid) +
2213 'properties already exist'
2214 )
2216 try:
2217 volume_properties[self.PROP_NOT_EXISTS] = self.STATE_CREATING
2218 volume_properties[self.PROP_VOLUME_NAME] = volume_name
2220 self._create_volume(
2221 volume_uuid, volume_name, size, place_resources
2222 )
2224 assert volume_properties.namespace == \
2225 self._build_volume_namespace(volume_uuid)
2226 return volume_properties
2227 except LinstorVolumeManagerError as e:
2228 # Do not destroy existing resource!
2229 # In theory we can't get this error because we check this event
2230 # before the `self._create_volume` case.
2231 # It can only happen if the same volume uuid is used in the same
2232 # call in another host.
2233 if e.code != LinstorVolumeManagerError.ERR_VOLUME_EXISTS:
2234 self._destroy_volume(volume_uuid, force=True)
2235 raise
2237 def _find_device_path(self, volume_uuid, volume_name):
2238 current_device_path = self._request_device_path(
2239 volume_uuid, volume_name, activate=True
2240 )
2242 # We use realpath here to get the /dev/drbd<id> path instead of
2243 # /dev/drbd/by-res/<resource_name>.
2244 expected_device_path = self.build_device_path(volume_name)
2245 util.wait_for_path(expected_device_path, 5)
2247 device_realpath = os.path.realpath(expected_device_path)
2248 if current_device_path != device_realpath:
2249 raise LinstorVolumeManagerError(
2250 'Invalid path, current={}, expected={} (realpath={})'
2251 .format(
2252 current_device_path,
2253 expected_device_path,
2254 device_realpath
2255 )
2256 )
2257 return expected_device_path
2259 def _request_device_path(self, volume_uuid, volume_name, activate=False):
2260 node_name = socket.gethostname()
2262 resource = next(filter(
2263 lambda resource: resource.node_name == node_name and
2264 resource.name == volume_name,
2265 self._get_resource_cache().resources
2266 ), None)
2268 if not resource:
2269 if activate:
2270 self._mark_resource_cache_as_dirty()
2271 self._activate_device_path(
2272 self._linstor, node_name, volume_name
2273 )
2274 return self._request_device_path(volume_uuid, volume_name)
2275 raise LinstorVolumeManagerError(
2276 'Empty dev path for `{}`, but definition "seems" to exist'
2277 .format(volume_uuid)
2278 )
2279 # Contains a path of the /dev/drbd<id> form.
2280 return resource.volumes[0].device_path
2282 def _destroy_resource(self, resource_name, force=False):
2283 result = self._linstor.resource_dfn_delete(resource_name)
2284 error_str = self._get_error_str(result)
2285 if not error_str:
2286 self._mark_resource_cache_as_dirty()
2287 return
2289 if not force:
2290 self._mark_resource_cache_as_dirty()
2291 raise LinstorVolumeManagerError(
2292 'Could not destroy resource `{}` from SR `{}`: {}'
2293 .format(resource_name, self._group_name, error_str)
2294 )
2296 # If force is used, ensure there is no opener.
2297 all_openers = get_all_volume_openers(resource_name, '0')
2298 for openers in all_openers.values():
2299 if openers:
2300 self._mark_resource_cache_as_dirty()
2301 raise LinstorVolumeManagerError(
2302 'Could not force destroy resource `{}` from SR `{}`: {} (openers=`{}`)'
2303 .format(resource_name, self._group_name, error_str, all_openers)
2304 )
2306 # Maybe the resource is blocked in primary mode. DRBD/LINSTOR issue?
2307 resource_states = filter(
2308 lambda resource_state: resource_state.name == resource_name,
2309 self._get_resource_cache().resource_states
2310 )
2312 # Mark only after computation of states.
2313 self._mark_resource_cache_as_dirty()
2315 for resource_state in resource_states:
2316 volume_state = resource_state.volume_states[0]
2317 if resource_state.in_use:
2318 demote_drbd_resource(resource_state.node_name, resource_name)
2319 break
2320 self._destroy_resource(resource_name)
2322 def _destroy_volume(self, volume_uuid, force=False):
2323 volume_properties = self._get_volume_properties(volume_uuid)
2324 try:
2325 volume_name = volume_properties.get(self.PROP_VOLUME_NAME)
2326 if volume_name in self._fetch_resource_names():
2327 self._destroy_resource(volume_name, force)
2329 # Assume this call is atomic.
2330 volume_properties.clear()
2331 except Exception as e:
2332 raise LinstorVolumeManagerError(
2333 'Cannot destroy volume `{}`: {}'.format(volume_uuid, e)
2334 )
2336 def _build_volumes(self, repair):
2337 properties = self._kv_cache
2338 resource_names = self._fetch_resource_names()
2340 self._volumes = set()
2342 updating_uuid_volumes = self._get_volumes_by_property(
2343 self.REG_UPDATING_UUID_SRC, ignore_inexisting_volumes=False
2344 )
2345 if updating_uuid_volumes and not repair:
2346 raise LinstorVolumeManagerError(
2347 'Cannot build LINSTOR volume list: '
2348 'It exists invalid "updating uuid volumes", repair is required'
2349 )
2351 existing_volumes = self._get_volumes_by_property(
2352 self.REG_NOT_EXISTS, ignore_inexisting_volumes=False
2353 )
2354 for volume_uuid, not_exists in existing_volumes.items():
2355 properties.namespace = self._build_volume_namespace(volume_uuid)
2357 src_uuid = properties.get(self.PROP_UPDATING_UUID_SRC)
2358 if src_uuid:
2359 self._logger(
2360 'Ignoring volume during manager initialization with prop '
2361 ' PROP_UPDATING_UUID_SRC: {} (properties={})'
2362 .format(
2363 volume_uuid,
2364 self._get_filtered_properties(properties)
2365 )
2366 )
2367 continue
2369 # Insert volume in list if the volume exists. Or if the volume
2370 # is being created and a slave wants to use it (repair = False).
2371 #
2372 # If we are on the master and if repair is True and state is
2373 # Creating, it's probably a bug or crash: the creation process has
2374 # been stopped.
2375 if not_exists == self.STATE_EXISTS or (
2376 not repair and not_exists == self.STATE_CREATING
2377 ):
2378 self._volumes.add(volume_uuid)
2379 continue
2381 if not repair:
2382 self._logger(
2383 'Ignoring bad volume during manager initialization: {} '
2384 '(properties={})'.format(
2385 volume_uuid,
2386 self._get_filtered_properties(properties)
2387 )
2388 )
2389 continue
2391 # Remove bad volume.
2392 try:
2393 self._logger(
2394 'Removing bad volume during manager initialization: {} '
2395 '(properties={})'.format(
2396 volume_uuid,
2397 self._get_filtered_properties(properties)
2398 )
2399 )
2400 volume_name = properties.get(self.PROP_VOLUME_NAME)
2402 # Little optimization, don't call `self._destroy_volume`,
2403 # we already have resource name list.
2404 if volume_name in resource_names:
2405 self._destroy_resource(volume_name, force=True)
2407 # Assume this call is atomic.
2408 properties.clear()
2409 except Exception as e:
2410 # Do not raise, we don't want to block user action.
2411 self._logger(
2412 'Cannot clean volume {}: {}'.format(volume_uuid, e)
2413 )
2415 # The volume can't be removed, maybe it's still in use,
2416 # in this case rename it with the "DELETED_" prefix.
2417 # This prefix is mandatory if it exists a snap transaction to
2418 # rollback because the original VDI UUID can try to be renamed
2419 # with the UUID we are trying to delete...
2420 if not volume_uuid.startswith('DELETED_'):
2421 self.update_volume_uuid(
2422 volume_uuid, 'DELETED_' + volume_uuid, force=True
2423 )
2425 for dest_uuid, src_uuid in updating_uuid_volumes.items():
2426 dest_namespace = self._build_volume_namespace(dest_uuid)
2428 properties.namespace = dest_namespace
2429 if int(properties.get(self.PROP_NOT_EXISTS)):
2430 properties.clear()
2431 continue
2433 properties.namespace = self._build_volume_namespace(src_uuid)
2434 properties.clear()
2436 properties.namespace = dest_namespace
2437 properties.pop(self.PROP_UPDATING_UUID_SRC)
2439 if src_uuid in self._volumes:
2440 self._volumes.remove(src_uuid)
2441 self._volumes.add(dest_uuid)
2443 def _get_sr_properties(self):
2444 return self._create_linstor_kv(self._build_sr_namespace())
2446 def _get_volumes_by_property(
2447 self, reg_prop, ignore_inexisting_volumes=True
2448 ):
2449 base_properties = self._get_kv_cache()
2450 base_properties.namespace = self._build_volume_namespace()
2452 volume_properties = {}
2453 for volume_uuid in self._volumes:
2454 volume_properties[volume_uuid] = ''
2456 for key, value in base_properties.items():
2457 res = reg_prop.match(key)
2458 if res:
2459 volume_uuid = res.groups()[0]
2460 if not ignore_inexisting_volumes or \
2461 volume_uuid in self._volumes:
2462 volume_properties[volume_uuid] = value
2464 return volume_properties
2466 def _create_linstor_kv(self, namespace):
2467 return linstor.KV(
2468 self._group_name,
2469 uri=self._linstor.controller_host(),
2470 namespace=namespace
2471 )
2473 def _get_volume_properties(self, volume_uuid):
2474 properties = self._get_kv_cache()
2475 properties.namespace = self._build_volume_namespace(volume_uuid)
2476 return properties
2478 @classmethod
2479 def _build_sr_namespace(cls):
2480 return '/{}/'.format(cls.NAMESPACE_SR)
2482 @classmethod
2483 def _build_volume_namespace(cls, volume_uuid=None):
2484 # Return a path to all volumes if `volume_uuid` is not given.
2485 if volume_uuid is None:
2486 return '/{}/'.format(cls.NAMESPACE_VOLUME)
2487 return '/{}/{}/'.format(cls.NAMESPACE_VOLUME, volume_uuid)
2489 @classmethod
2490 def _get_error_str(cls, result):
2491 return ', '.join([
2492 err.message for err in cls._filter_errors(result)
2493 ])
2495 @classmethod
2496 def _create_linstor_instance(
2497 cls, uri, keep_uri_unmodified=False, attempt_count=30
2498 ):
2499 retry = False
2501 def connect(uri):
2502 if not uri:
2503 uri = get_controller_uri()
2504 if not uri:
2505 raise LinstorVolumeManagerError(
2506 'Unable to find controller uri...'
2507 )
2508 instance = linstor.Linstor(uri, keep_alive=True)
2509 instance.connect()
2510 return instance
2512 try:
2513 return connect(uri)
2514 except (linstor.errors.LinstorNetworkError, LinstorVolumeManagerError):
2515 pass
2517 if not keep_uri_unmodified:
2518 uri = None
2520 return util.retry(
2521 lambda: connect(uri),
2522 maxretry=attempt_count,
2523 period=1,
2524 exceptions=[
2525 linstor.errors.LinstorNetworkError,
2526 LinstorVolumeManagerError
2527 ]
2528 )
2530 @classmethod
2531 def _configure_volume_peer_slots(cls, lin, volume_name):
2532 result = lin.resource_dfn_modify(volume_name, {}, peer_slots=3)
2533 error_str = cls._get_error_str(result)
2534 if error_str:
2535 raise LinstorVolumeManagerError(
2536 'Could not configure volume peer slots of {}: {}'
2537 .format(volume_name, error_str)
2538 )
2540 @classmethod
2541 def _activate_device_path(cls, lin, node_name, volume_name):
2542 result = lin.resource_make_available(node_name, volume_name, diskful=False)
2543 if linstor.Linstor.all_api_responses_no_error(result):
2544 return
2545 errors = linstor.Linstor.filter_api_call_response_errors(result)
2546 if len(errors) == 1 and errors[0].is_error(
2547 linstor.consts.FAIL_EXISTS_RSC
2548 ):
2549 return
2551 raise LinstorVolumeManagerError(
2552 'Unable to activate device path of `{}` on node `{}`: {}'
2553 .format(volume_name, node_name, ', '.join(
2554 [str(x) for x in result]))
2555 )
2557 @classmethod
2558 def _request_database_path(cls, lin, activate=False):
2559 node_name = socket.gethostname()
2561 try:
2562 resource = next(filter(
2563 lambda resource: resource.node_name == node_name and
2564 resource.name == DATABASE_VOLUME_NAME,
2565 lin.resource_list_raise().resources
2566 ), None)
2567 except Exception as e:
2568 raise LinstorVolumeManagerError(
2569 'Unable to get resources during database creation: {}'
2570 .format(e)
2571 )
2573 if not resource:
2574 if activate:
2575 cls._activate_device_path(
2576 lin, node_name, DATABASE_VOLUME_NAME
2577 )
2578 return cls._request_database_path(
2579 DATABASE_VOLUME_NAME, DATABASE_VOLUME_NAME
2580 )
2581 raise LinstorVolumeManagerError(
2582 'Empty dev path for `{}`, but definition "seems" to exist'
2583 .format(DATABASE_PATH)
2584 )
2585 # Contains a path of the /dev/drbd<id> form.
2586 return resource.volumes[0].device_path
2588 @classmethod
2589 def _create_database_volume(
2590 cls, lin, group_name, node_names, redundancy, auto_quorum
2591 ):
2592 try:
2593 dfns = lin.resource_dfn_list_raise().resource_definitions
2594 except Exception as e:
2595 raise LinstorVolumeManagerError(
2596 'Unable to get definitions during database creation: {}'
2597 .format(e)
2598 )
2600 if dfns:
2601 raise LinstorVolumeManagerError(
2602 'Could not create volume `{}` from SR `{}`, '.format(
2603 DATABASE_VOLUME_NAME, group_name
2604 ) + 'LINSTOR volume list must be empty.'
2605 )
2607 # Workaround to use thin lvm. Without this line an error is returned:
2608 # "Not enough available nodes"
2609 # I don't understand why but this command protect against this bug.
2610 try:
2611 pools = lin.storage_pool_list_raise(
2612 filter_by_stor_pools=[group_name]
2613 )
2614 except Exception as e:
2615 raise LinstorVolumeManagerError(
2616 'Failed to get storage pool list before database creation: {}'
2617 .format(e)
2618 )
2620 # Ensure we have a correct list of storage pools.
2621 nodes_with_pool = [pool.node_name for pool in pools.storage_pools]
2622 assert nodes_with_pool # We must have at least one storage pool!
2623 for node_name in nodes_with_pool:
2624 assert node_name in node_names
2625 util.SMlog('Nodes with storage pool: {}'.format(nodes_with_pool))
2627 # Create the database definition.
2628 size = cls.round_up_volume_size(DATABASE_SIZE)
2629 cls._check_volume_creation_errors(lin.resource_group_spawn(
2630 rsc_grp_name=group_name,
2631 rsc_dfn_name=DATABASE_VOLUME_NAME,
2632 vlm_sizes=['{}B'.format(size)],
2633 definitions_only=True
2634 ), DATABASE_VOLUME_NAME, group_name)
2635 cls._configure_volume_peer_slots(lin, DATABASE_VOLUME_NAME)
2637 # Create real resources on the first nodes.
2638 resources = []
2640 diskful_nodes = []
2641 diskless_nodes = []
2642 for node_name in node_names:
2643 if node_name in nodes_with_pool:
2644 diskful_nodes.append(node_name)
2645 else:
2646 diskless_nodes.append(node_name)
2648 assert diskful_nodes
2649 for node_name in diskful_nodes[:redundancy]:
2650 util.SMlog('Create database diskful on {}'.format(node_name))
2651 resources.append(linstor.ResourceData(
2652 node_name=node_name,
2653 rsc_name=DATABASE_VOLUME_NAME,
2654 storage_pool=group_name
2655 ))
2656 # Create diskless resources on the remaining set.
2657 for node_name in diskful_nodes[redundancy:] + diskless_nodes:
2658 util.SMlog('Create database diskless on {}'.format(node_name))
2659 resources.append(linstor.ResourceData(
2660 node_name=node_name,
2661 rsc_name=DATABASE_VOLUME_NAME,
2662 diskless=True
2663 ))
2665 result = lin.resource_create(resources)
2666 error_str = cls._get_error_str(result)
2667 if error_str:
2668 raise LinstorVolumeManagerError(
2669 'Could not create database volume from SR `{}`: {}'.format(
2670 group_name, error_str
2671 )
2672 )
2674 # We must modify the quorum. Otherwise we can't use correctly the
2675 # drbd-reactor daemon.
2676 if auto_quorum:
2677 result = lin.resource_dfn_modify(DATABASE_VOLUME_NAME, {
2678 'DrbdOptions/auto-quorum': 'disabled',
2679 'DrbdOptions/Resource/quorum': 'majority'
2680 })
2681 error_str = cls._get_error_str(result)
2682 if error_str:
2683 raise LinstorVolumeManagerError(
2684 'Could not activate quorum on database volume: {}'
2685 .format(error_str)
2686 )
2688 # Create database and ensure path exists locally and
2689 # on replicated devices.
2690 current_device_path = cls._request_database_path(lin, activate=True)
2692 # Ensure diskless paths exist on other hosts. Otherwise PBDs can't be
2693 # plugged.
2694 for node_name in node_names:
2695 cls._activate_device_path(lin, node_name, DATABASE_VOLUME_NAME)
2697 # We use realpath here to get the /dev/drbd<id> path instead of
2698 # /dev/drbd/by-res/<resource_name>.
2699 expected_device_path = cls.build_device_path(DATABASE_VOLUME_NAME)
2700 util.wait_for_path(expected_device_path, 5)
2702 device_realpath = os.path.realpath(expected_device_path)
2703 if current_device_path != device_realpath:
2704 raise LinstorVolumeManagerError(
2705 'Invalid path, current={}, expected={} (realpath={})'
2706 .format(
2707 current_device_path,
2708 expected_device_path,
2709 device_realpath
2710 )
2711 )
2713 try:
2714 util.retry(
2715 lambda: util.pread2([DATABASE_MKFS, expected_device_path]),
2716 maxretry=5
2717 )
2718 except Exception as e:
2719 raise LinstorVolumeManagerError(
2720 'Failed to execute {} on database volume: {}'
2721 .format(DATABASE_MKFS, e)
2722 )
2724 return expected_device_path
2726 @classmethod
2727 def _destroy_database_volume(cls, lin, group_name):
2728 error_str = cls._get_error_str(
2729 lin.resource_dfn_delete(DATABASE_VOLUME_NAME)
2730 )
2731 if error_str:
2732 raise LinstorVolumeManagerError(
2733 'Could not destroy resource `{}` from SR `{}`: {}'
2734 .format(DATABASE_VOLUME_NAME, group_name, error_str)
2735 )
2737 @classmethod
2738 def _mount_database_volume(cls, volume_path, mount=True, force=False):
2739 try:
2740 # 1. Create a backup config folder.
2741 database_not_empty = bool(os.listdir(DATABASE_PATH))
2742 backup_path = cls._create_database_backup_path()
2744 # 2. Move the config in the mounted volume.
2745 if database_not_empty:
2746 cls._move_files(DATABASE_PATH, backup_path)
2748 cls._mount_volume(volume_path, DATABASE_PATH, mount)
2750 if database_not_empty:
2751 cls._move_files(backup_path, DATABASE_PATH, force)
2753 # 3. Remove useless backup directory.
2754 try:
2755 os.rmdir(backup_path)
2756 except Exception as e:
2757 raise LinstorVolumeManagerError(
2758 'Failed to remove backup path {} of LINSTOR config: {}'
2759 .format(backup_path, e)
2760 )
2761 except Exception as e:
2762 def force_exec(fn):
2763 try:
2764 fn()
2765 except Exception:
2766 pass
2768 if mount == cls._is_mounted(DATABASE_PATH):
2769 force_exec(lambda: cls._move_files(
2770 DATABASE_PATH, backup_path
2771 ))
2772 force_exec(lambda: cls._mount_volume(
2773 volume_path, DATABASE_PATH, not mount
2774 ))
2776 if mount != cls._is_mounted(DATABASE_PATH):
2777 force_exec(lambda: cls._move_files(
2778 backup_path, DATABASE_PATH
2779 ))
2781 force_exec(lambda: os.rmdir(backup_path))
2782 raise e
2784 @classmethod
2785 def _force_destroy_database_volume(cls, lin, group_name):
2786 try:
2787 cls._destroy_database_volume(lin, group_name)
2788 except Exception:
2789 pass
2791 @classmethod
2792 def _destroy_storage_pool(cls, lin, group_name, node_name):
2793 def destroy():
2794 result = lin.storage_pool_delete(node_name, group_name)
2795 errors = cls._filter_errors(result)
2796 if cls._check_errors(errors, [
2797 linstor.consts.FAIL_NOT_FOUND_STOR_POOL,
2798 linstor.consts.FAIL_NOT_FOUND_STOR_POOL_DFN
2799 ]):
2800 return
2802 if errors:
2803 raise LinstorVolumeManagerError(
2804 'Failed to destroy SP `{}` on node `{}`: {}'.format(
2805 group_name,
2806 node_name,
2807 cls._get_error_str(errors)
2808 )
2809 )
2811 # We must retry to avoid errors like:
2812 # "can not be deleted as volumes / snapshot-volumes are still using it"
2813 # after LINSTOR database volume destruction.
2814 return util.retry(destroy, maxretry=10)
2816 @classmethod
2817 def _destroy_resource_group(cls, lin, group_name):
2818 def destroy():
2819 result = lin.resource_group_delete(group_name)
2820 errors = cls._filter_errors(result)
2821 if cls._check_errors(errors, [
2822 linstor.consts.FAIL_NOT_FOUND_RSC_GRP
2823 ]):
2824 return
2826 if errors:
2827 raise LinstorVolumeManagerError(
2828 'Failed to destroy RG `{}`: {}'
2829 .format(group_name, cls._get_error_str(errors))
2830 )
2832 return util.retry(destroy, maxretry=10)
2834 @classmethod
2835 def _build_group_name(cls, base_name):
2836 # If thin provisioning is used we have a path like this:
2837 # `VG/LV`. "/" is not accepted by LINSTOR.
2838 return '{}{}'.format(cls.PREFIX_SR, base_name.replace('/', '_'))
2840 @classmethod
2841 def _check_volume_creation_errors(cls, result, volume_uuid, group_name):
2842 errors = cls._filter_errors(result)
2843 if cls._check_errors(errors, [
2844 linstor.consts.FAIL_EXISTS_RSC, linstor.consts.FAIL_EXISTS_RSC_DFN
2845 ]):
2846 raise LinstorVolumeManagerError(
2847 'Failed to create volume `{}` from SR `{}`, it already exists'
2848 .format(volume_uuid, group_name),
2849 LinstorVolumeManagerError.ERR_VOLUME_EXISTS
2850 )
2852 if errors:
2853 raise LinstorVolumeManagerError(
2854 'Failed to create volume `{}` from SR `{}`: {}'.format(
2855 volume_uuid,
2856 group_name,
2857 cls._get_error_str(errors)
2858 )
2859 )
2861 @classmethod
2862 def _move_files(cls, src_dir, dest_dir, force=False):
2863 def listdir(dir):
2864 ignored = ['lost+found']
2865 return [file for file in os.listdir(dir) if file not in ignored]
2867 try:
2868 if not force:
2869 files = listdir(dest_dir)
2870 if files:
2871 raise LinstorVolumeManagerError(
2872 'Cannot move files from {} to {} because destination '
2873 'contains: {}'.format(src_dir, dest_dir, files)
2874 )
2875 except LinstorVolumeManagerError:
2876 raise
2877 except Exception as e:
2878 raise LinstorVolumeManagerError(
2879 'Cannot list dir {}: {}'.format(dest_dir, e)
2880 )
2882 try:
2883 for file in listdir(src_dir):
2884 try:
2885 dest_file = os.path.join(dest_dir, file)
2886 if not force and os.path.exists(dest_file):
2887 raise LinstorVolumeManagerError(
2888 'Cannot move {} because it already exists in the '
2889 'destination'.format(file)
2890 )
2891 shutil.move(os.path.join(src_dir, file), dest_file)
2892 except LinstorVolumeManagerError:
2893 raise
2894 except Exception as e:
2895 raise LinstorVolumeManagerError(
2896 'Cannot move {}: {}'.format(file, e)
2897 )
2898 except Exception as e:
2899 if not force:
2900 try:
2901 cls._move_files(dest_dir, src_dir, force=True)
2902 except Exception:
2903 pass
2905 raise LinstorVolumeManagerError(
2906 'Failed to move files from {} to {}: {}'.format(
2907 src_dir, dest_dir, e
2908 )
2909 )
2911 @staticmethod
2912 def _create_database_backup_path():
2913 path = DATABASE_PATH + '-' + str(uuid.uuid4())
2914 try:
2915 os.mkdir(path)
2916 return path
2917 except Exception as e:
2918 raise LinstorVolumeManagerError(
2919 'Failed to create backup path {} of LINSTOR config: {}'
2920 .format(path, e)
2921 )
2923 @staticmethod
2924 def _get_filtered_properties(properties):
2925 return dict(properties.items())
2927 @staticmethod
2928 def _filter_errors(result):
2929 return [
2930 err for err in result
2931 if hasattr(err, 'is_error') and err.is_error()
2932 ]
2934 @staticmethod
2935 def _check_errors(result, codes):
2936 for err in result:
2937 for code in codes:
2938 if err.is_error(code):
2939 return True
2940 return False
2942 @classmethod
2943 def _controller_is_running(cls):
2944 return cls._service_is_running('linstor-controller')
2946 @classmethod
2947 def _start_controller(cls, start=True):
2948 return cls._start_service('linstor-controller', start)
2950 @staticmethod
2951 def _start_service(name, start=True):
2952 action = 'start' if start else 'stop'
2953 (ret, out, err) = util.doexec([
2954 'systemctl', action, name
2955 ])
2956 if ret != 0:
2957 raise LinstorVolumeManagerError(
2958 'Failed to {} {}: {} {}'
2959 .format(action, name, out, err)
2960 )
2962 @staticmethod
2963 def _service_is_running(name):
2964 (ret, out, err) = util.doexec([
2965 'systemctl', 'is-active', '--quiet', name
2966 ])
2967 return not ret
2969 @staticmethod
2970 def _is_mounted(mountpoint):
2971 (ret, out, err) = util.doexec(['mountpoint', '-q', mountpoint])
2972 return ret == 0
2974 @classmethod
2975 def _mount_volume(cls, volume_path, mountpoint, mount=True):
2976 if mount:
2977 try:
2978 util.pread(['mount', volume_path, mountpoint])
2979 except Exception as e:
2980 raise LinstorVolumeManagerError(
2981 'Failed to mount volume {} on {}: {}'
2982 .format(volume_path, mountpoint, e)
2983 )
2984 else:
2985 try:
2986 if cls._is_mounted(mountpoint):
2987 util.pread(['umount', mountpoint])
2988 except Exception as e:
2989 raise LinstorVolumeManagerError(
2990 'Failed to umount volume {} on {}: {}'
2991 .format(volume_path, mountpoint, e)
2992 )
2995# ==============================================================================
2997# Check if a path is a DRBD resource and log the process name/pid
2998# that opened it.
2999def log_drbd_openers(path):
3000 # Ignore if it's not a symlink to DRBD resource.
3001 if not path.startswith(DRBD_BY_RES_PATH):
3002 return
3004 # Compute resource name.
3005 res_name_end = path.find('/', len(DRBD_BY_RES_PATH))
3006 if res_name_end == -1:
3007 return
3008 res_name = path[len(DRBD_BY_RES_PATH):res_name_end]
3010 volume_end = path.rfind('/')
3011 if volume_end == res_name_end:
3012 return
3013 volume = path[volume_end + 1:]
3015 try:
3016 # Ensure path is a DRBD.
3017 drbd_path = os.path.realpath(path)
3018 stats = os.stat(drbd_path)
3019 if not stat.S_ISBLK(stats.st_mode) or os.major(stats.st_rdev) != 147:
3020 return
3022 # Find where the device is open.
3023 (ret, stdout, stderr) = util.doexec(['drbdadm', 'status', res_name])
3024 if ret != 0:
3025 util.SMlog('Failed to execute `drbdadm status` on `{}`: {}'.format(
3026 res_name, stderr
3027 ))
3028 return
3030 # Is it a local device?
3031 if stdout.startswith('{} role:Primary'.format(res_name)):
3032 util.SMlog(
3033 'DRBD resource `{}` is open on local host: {}'
3034 .format(path, get_local_volume_openers(res_name, volume))
3035 )
3036 return
3038 # Is it a remote device?
3039 util.SMlog(
3040 'DRBD resource `{}` is open on hosts: {}'
3041 .format(path, get_all_volume_openers(res_name, volume))
3042 )
3043 except Exception as e:
3044 util.SMlog(
3045 'Got exception while trying to determine where DRBD resource ' +
3046 '`{}` is open: {}'.format(path, e)
3047 )