Hide keyboard shortcuts

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# 

17 

18 

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 

32 

33# Persistent prefix to add to RAW persistent volumes. 

34PERSISTENT_PREFIX = 'xcp-persistent-' 

35 

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' 

41 

42REG_DRBDADM_PRIMARY = re.compile("([^\\s]+)\\s+role:Primary") 

43REG_DRBDSETUP_IP = re.compile('[^\\s]+\\s+(.*):.*$') 

44 

45DRBD_BY_RES_PATH = '/dev/drbd/by-res/' 

46 

47PLUGIN = 'linstor-manager' 

48 

49 

50# ============================================================================== 

51 

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.') 

55 

56 path = '/sys/kernel/debug/drbd/resources/{}/volumes/{}/openers'.format( 

57 resource_name, volume 

58 ) 

59 

60 with open(path, 'r') as openers: 

61 # Not a big cost, so read all lines directly. 

62 lines = openers.readlines() 

63 

64 result = {} 

65 

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 

70 

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 } 

79 

80 return json.dumps(result) 

81 

82def get_all_volume_openers(resource_name, volume): 

83 PLUGIN_CMD = 'getDrbdOpeners' 

84 

85 volume = str(volume) 

86 openers = {} 

87 

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) 

91 

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 

101 

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 )) 

112 

113 return openers 

114 

115 

116# ============================================================================== 

117 

118def round_up(value, divisor): 

119 assert divisor 

120 divisor = int(divisor) 

121 return ((int(value) + divisor - 1) // divisor) * divisor 

122 

123 

124def round_down(value, divisor): 

125 assert divisor 

126 value = int(value) 

127 return value - (value % int(divisor)) 

128 

129 

130# ============================================================================== 

131 

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 

138 

139 try: 

140 conf = json.loads(stdout) 

141 if not conf: 

142 return 

143 

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 

153 

154 

155def _get_controller_uri(): 

156 PLUGIN_CMD = 'hasControllerRunning' 

157 

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. 

164 

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' 

168 

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 

176 

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) 

183 

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 

199 

200def get_controller_uri(): 

201 retries = 0 

202 while True: 

203 uri = _get_controller_uri() 

204 if uri: 

205 return uri 

206 

207 retries += 1 

208 if retries >= 10: 

209 break 

210 time.sleep(1) 

211 

212 

213def get_controller_node_name(): 

214 PLUGIN_CMD = 'hasControllerRunning' 

215 

216 (ret, stdout, stderr) = util.doexec([ 

217 'drbdadm', 'status', DATABASE_VOLUME_NAME 

218 ]) 

219 

220 if ret == 0: 

221 if stdout.startswith('{} role:Primary'.format(DATABASE_VOLUME_NAME)): 

222 return 'localhost' 

223 

224 res = REG_DRBDADM_PRIMARY.search(stdout) 

225 if res: 

226 return res.groups()[0] 

227 

228 session = util.timeout_call(5, util.get_localAPI_session) 

229 

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 

237 

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 )) 

246 

247 

248def demote_drbd_resource(node_name, resource_name): 

249 PLUGIN_CMD = 'demoteDrbdResource' 

250 

251 session = util.timeout_call(5, util.get_localAPI_session) 

252 

253 for host_ref, host_record in session.xenapi.host.get_all_records().items(): 

254 if host_record['hostname'] != node_name: 

255 continue 

256 

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 ) 

269 

270# ============================================================================== 

271 

272class LinstorVolumeManagerError(Exception): 

273 ERR_GENERIC = 0, 

274 ERR_VOLUME_EXISTS = 1, 

275 ERR_VOLUME_NOT_EXISTS = 2, 

276 ERR_VOLUME_DESTROY = 3 

277 

278 def __init__(self, message, code=ERR_GENERIC): 

279 super(LinstorVolumeManagerError, self).__init__(message) 

280 self._code = code 

281 

282 @property 

283 def code(self): 

284 return self._code 

285 

286 

287# ============================================================================== 

288 

289# Note: 

290# If a storage pool is not accessible after a network change: 

291# linstor node interface modify <NODE> default --ip <IP> 

292 

293 

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 """ 

299 

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 ) 

309 

310 DEV_ROOT_PATH = DRBD_BY_RES_PATH 

311 

312 # Default sector size. 

313 BLOCK_SIZE = 512 

314 

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' 

320 

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 

326 

327 # Used when volume uuid is being updated. 

328 PROP_UPDATING_UUID_SRC = 'updating-uuid-src' 

329 

330 # States of property PROP_NOT_EXISTS. 

331 STATE_EXISTS = '0' 

332 STATE_NOT_EXISTS = '1' 

333 STATE_CREATING = '2' 

334 

335 # Property namespaces. 

336 NAMESPACE_SR = 'xcp/sr' 

337 NAMESPACE_VOLUME = 'xcp/volume' 

338 

339 # Regex to match properties. 

340 REG_PROP = '^([^/]+)/{}$' 

341 

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)) 

346 

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-' 

352 

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 

356 

357 @staticmethod 

358 def default_logger(*args): 

359 print(args) 

360 

361 # -------------------------------------------------------------------------- 

362 # API. 

363 # -------------------------------------------------------------------------- 

364 

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 ) 

373 

374 def __init__(self, name): 

375 self.name = name 

376 self.allocated_size = 0 

377 self.virtual_size = 0 

378 self.diskful = [] 

379 

380 def __repr__(self): 

381 return 'VolumeInfo("{}", {}, {}, {})'.format( 

382 self.name, self.allocated_size, self.virtual_size, 

383 self.diskful 

384 ) 

385 

386 # -------------------------------------------------------------------------- 

387 

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 """ 

401 

402 self._linstor = self._create_linstor_instance( 

403 uri, attempt_count=attempt_count 

404 ) 

405 self._base_group_name = group_name 

406 

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 ) 

415 

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 

422 

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) 

431 

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 

440 

441 @property 

442 def redundancy(self): 

443 """ 

444 Give the used redundancy. 

445 :return: The redundancy. 

446 :rtype: int 

447 """ 

448 return self._redundancy 

449 

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 

458 

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 """ 

466 

467 candidates = self._find_best_size_candidates() 

468 if not candidates: 

469 raise LinstorVolumeManagerError( 

470 'Failed to get max volume size allowed' 

471 ) 

472 

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) 

479 

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') 

488 

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') 

497 

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 """ 

508 

509 # Paths: /res_name/vol_number/size 

510 sizes = {} 

511 

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] 

517 

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 

522 

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) 

530 

531 total_size = 0 

532 for volumes in sizes.values(): 

533 for size in volumes.values(): 

534 total_size += size 

535 

536 return total_size * 1024 

537 

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) 

560 

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 """ 

568 

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 ) 

580 

581 return {} 

582 

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 """ 

589 

590 assert isinstance(metadata, dict) 

591 sr_properties = self._get_sr_properties() 

592 sr_properties[self.PROP_METADATA] = json.dumps(metadata) 

593 

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 """ 

601 

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 

610 

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 

618 

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 """ 

633 

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 ) 

640 

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 

659 

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 """ 

665 

666 self._ensure_volume_exists(volume_uuid) 

667 

668 # Mark volume as persistent. 

669 volume_properties = self._get_volume_properties(volume_uuid) 

670 volume_properties[self.PROP_NOT_EXISTS] = self.STATE_EXISTS 

671 

672 def destroy_volume(self, volume_uuid): 

673 """ 

674 Destroy a volume. 

675 :param str volume_uuid: The volume uuid to destroy. 

676 """ 

677 

678 self._ensure_volume_exists(volume_uuid) 

679 self.ensure_volume_is_not_locked(volume_uuid) 

680 

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 

684 

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 ) 

693 

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 """ 

704 

705 self._ensure_volume_exists(volume_uuid) 

706 

707 self._logger( 

708 '{} volume {} as locked'.format( 

709 'Mark' if locked else 'Unmark', 

710 volume_uuid 

711 ) 

712 ) 

713 

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) 

721 

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) 

730 

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) 

736 

737 if not checked: 

738 return 

739 

740 waiting = False 

741 

742 volume_properties = self._get_kv_cache() 

743 

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 

757 

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 

766 

767 if not waiting: 

768 self._logger( 

769 'Volume {} is locked, waiting...'.format(volume_uuid) 

770 ) 

771 waiting = True 

772 break 

773 

774 if not remaining: 

775 break 

776 checked = remaining 

777 

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 ) 

784 

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() 

789 

790 if waiting: 

791 self._logger('No volume locked now!') 

792 

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 """ 

798 

799 self._ensure_volume_exists(volume_uuid) 

800 

801 volume_properties = self._get_volume_properties(volume_uuid) 

802 volume_name = volume_properties.get(self.PROP_VOLUME_NAME) 

803 

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 ) 

814 

815 def introduce_volume(self, volume_uuid): 

816 pass # TODO: Implement me. 

817 

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 """ 

824 

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) 

828 

829 result = self._linstor.volume_dfn_modify( 

830 rsc_name=volume_name, 

831 volume_nr=0, 

832 size=new_size // 1024 

833 ) 

834 

835 self._mark_resource_cache_as_dirty() 

836 

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 ) 

843 

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 """ 

851 

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 ) 

860 

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 """ 

868 

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 

874 

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 

881 

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 """ 

888 

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 ) 

899 

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 """ 

907 

908 volume_name = self.get_volume_name(volume_uuid) 

909 return self._get_volumes_info()[volume_name] 

910 

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 """ 

918 

919 volume_name = self.get_volume_name(volume_uuid) 

920 return self._find_device_path(volume_uuid, volume_name) 

921 

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 """ 

929 

930 expected_volume_name = \ 

931 self.get_volume_name_from_device_path(device_path) 

932 

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 

937 

938 raise LinstorVolumeManagerError( 

939 'Unable to find volume uuid from dev path `{}`'.format(device_path) 

940 ) 

941 

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 """ 

949 

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 

958 

959 res_name_end = device_path.find('/', prefix_len) 

960 assert res_name_end != -1 

961 return device_path[prefix_len:res_name_end] 

962 

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 """ 

974 

975 assert volume_uuid != new_volume_uuid 

976 

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) 

984 

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 ) 

990 

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 ) 

997 

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) 

1001 

1002 # 2. Switch to new volume namespace. 

1003 volume_properties.namespace = self._build_volume_namespace( 

1004 new_volume_uuid 

1005 ) 

1006 

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 ) 

1013 

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 

1020 

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 

1028 

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 ) 

1045 

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. 

1050 

1051 # 7. Switch to old volume namespace. 

1052 volume_properties.namespace = self._build_volume_namespace( 

1053 volume_uuid 

1054 ) 

1055 volume_properties.clear() 

1056 

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 ) 

1067 

1068 self._volumes.remove(volume_uuid) 

1069 self._volumes.add(new_volume_uuid) 

1070 

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 ) 

1078 

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 """ 

1085 

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 ) 

1093 

1094 if volume_name not in self._fetch_resource_names(): 

1095 raise LinstorVolumeManagerError( 

1096 'Volume `{}` doesn\'t exist'.format(volume_name) 

1097 ) 

1098 

1099 volume_properties = self._get_volume_properties(volume_uuid) 

1100 volume_properties[self.PROP_VOLUME_NAME] = volume_name 

1101 

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 """ 

1109 

1110 states = {} 

1111 

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 

1117 

1118 return states 

1119 

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') 

1128 

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) 

1136 

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 """ 

1143 

1144 volumes = {} 

1145 

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 

1154 

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('') 

1158 

1159 return volumes 

1160 

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 """ 

1167 

1168 volumes = {} 

1169 

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 ) 

1181 

1182 volumes[volume_uuid] = {} 

1183 

1184 return volumes 

1185 

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 """ 

1192 

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 {} 

1205 

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 """ 

1211 

1212 self._ensure_volume_exists(volume_uuid) 

1213 self.ensure_volume_is_not_locked(volume_uuid) 

1214 

1215 assert isinstance(metadata, dict) 

1216 volume_properties = self._get_volume_properties(volume_uuid) 

1217 volume_properties[self.PROP_METADATA] = json.dumps(metadata) 

1218 

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 """ 

1225 

1226 self._ensure_volume_exists(volume_uuid) 

1227 self.ensure_volume_is_not_locked(volume_uuid) 

1228 

1229 assert isinstance(metadata, dict) 

1230 volume_properties = self._get_volume_properties(volume_uuid) 

1231 

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 ) 

1240 

1241 for key, value in metadata.items(): 

1242 current_metadata[key] = value 

1243 volume_properties[self.PROP_METADATA] = json.dumps(current_metadata) 

1244 

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 """ 

1256 

1257 volume_name = self.get_volume_name(volume_uuid) 

1258 self.ensure_volume_is_not_locked(volume_uuid) 

1259 

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 ) 

1268 

1269 # 2. Create clone! 

1270 return self.create_volume(clone_uuid, size, persistent) 

1271 

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 """ 

1279 

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) 

1285 

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 """ 

1291 

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 ) 

1298 

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 

1309 

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 ) 

1316 

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) 

1323 

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 ) 

1330 

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 ) 

1336 

1337 # 4.3. Destroy database volume. 

1338 self._destroy_resource(DATABASE_VOLUME_NAME) 

1339 

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() 

1347 

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 

1357 

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 ) 

1367 

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 """ 

1376 

1377 volume_name = self.get_volume_name(volume_uuid) 

1378 

1379 in_use_by = None 

1380 node_names = set() 

1381 

1382 resource_states = filter( 

1383 lambda resource_state: resource_state.name == volume_name, 

1384 self._get_resource_cache().resource_states 

1385 ) 

1386 

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 

1393 

1394 return (node_names, in_use_by) 

1395 

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() 

1403 

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)) 

1417 

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 ) 

1435 

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 ) 

1448 

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 ) 

1463 

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 ) 

1477 

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 

1489 

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 

1495 

1496 error_str = self._get_error_str(errors) 

1497 raise LinstorVolumeManagerError( 

1498 'Unable to modify interface on `{}`: {}'.format(node_name, error_str) 

1499 ) 

1500 

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 ) 

1513 

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 

1522 

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 ) 

1536 

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 ) 

1551 

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] = [] 

1561 

1562 size = -1 

1563 capacity = -1 

1564 

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 

1577 

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 }) 

1584 

1585 return storage_pools 

1586 

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] = {} 

1597 

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 } 

1603 

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 

1608 

1609 usable_size = volume.usable_size 

1610 if usable_size < 0: 

1611 usable_size = -1 

1612 else: 

1613 usable_size *= 1024 

1614 

1615 allocated_size = volume.allocated_size 

1616 if allocated_size < 0: 

1617 allocated_size = -1 

1618 else: 

1619 allocated_size *= 1024 

1620 

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 }) 

1629 

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 

1633 

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 

1639 

1640 return resources 

1641 

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) 

1649 

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 """ 

1667 

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 

1689 

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' 

1698 

1699 lin = cls._create_linstor_instance(uri, keep_uri_unmodified=True) 

1700 

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 ) 

1710 

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 ) 

1731 

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 ) 

1743 

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 ) 

1759 

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 ) 

1767 

1768 # 2. Create storage pool on each node + resource group. 

1769 reg_volume_group_not_found = re.compile( 

1770 ".*Volume group '.*' not found$" 

1771 ) 

1772 

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] 

1779 

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 ) 

1786 

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 

1808 

1809 if not storage_pool_count: 

1810 raise LinstorVolumeManagerError( 

1811 'Unable to create SR `{}`: No VG group found'.format( 

1812 group_name, 

1813 ) 

1814 ) 

1815 

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 

1828 

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 

1839 

1840 raise LinstorVolumeManagerError( 

1841 'Could not create RG `{}`: {}'.format(group_name, error_str) 

1842 ) 

1843 

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 ) 

1853 

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 

1865 

1866 try: 

1867 logger('Mounting database volume...') 

1868 

1869 # First we must disable the controller to move safely the 

1870 # LINSTOR config. 

1871 cls._start_controller(start=False) 

1872 

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...') 

1878 

1879 try: 

1880 cls._start_controller(start=True) 

1881 except Exception: 

1882 pass 

1883 

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 

1889 

1890 cls._start_controller(start=True) 

1891 lin = cls._create_linstor_instance(uri, keep_uri_unmodified=True) 

1892 

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 

1911 

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 

1927 

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 """ 

1936 

1937 return '{}{}/0'.format(cls.DEV_ROOT_PATH, volume_name) 

1938 

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) 

1948 

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) 

1958 

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) 

1968 

1969 # -------------------------------------------------------------------------- 

1970 # Private helpers. 

1971 # -------------------------------------------------------------------------- 

1972 

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 

1977 

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 

1982 

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 

1987 

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 

1992 

1993 def _mark_resource_cache_as_dirty(self): 

1994 self._resource_cache_dirty = True 

1995 self._volume_info_cache_dirty = True 

1996 

1997 # -------------------------------------------------------------------------- 

1998 

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 ) 

2005 

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 

2017 

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 

2028 

2029 def _get_volumes_info(self, volume_name=None): 

2030 all_volume_info = {} 

2031 

2032 if not self._volume_info_cache_dirty: 

2033 return self._volume_info_cache 

2034 

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] 

2042 

2043 if linstor.consts.FLAG_DISKLESS not in resource.flags: 

2044 current.diskful.append(resource.node_name) 

2045 

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 

2055 

2056 current.allocated_size = current.allocated_size and \ 

2057 max(current.allocated_size, allocated_size) or \ 

2058 allocated_size 

2059 

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 

2066 

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 ) 

2072 

2073 for current in all_volume_info.values(): 

2074 current.allocated_size *= 1024 

2075 current.virtual_size *= 1024 

2076 

2077 self._volume_info_cache_dirty = False 

2078 self._volume_info_cache = all_volume_info 

2079 

2080 return all_volume_info 

2081 

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) 

2092 

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 ) 

2099 

2100 if size < 0: 

2101 size = current_size 

2102 else: 

2103 size = min(size, current_size) 

2104 

2105 return (node_names, size * 1024) 

2106 

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 

2120 

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 

2126 

2127 def _get_storage_pools(self, force=False): 

2128 cur_time = time.time() 

2129 elsaped_time = cur_time - self._storage_pools_time 

2130 

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() 

2136 

2137 return self._storage_pools 

2138 

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() 

2144 

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) 

2157 

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 ) 

2166 

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 

2188 

2189 util.retry(create, maxretry=5) 

2190 

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 ) 

2200 

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 ) 

2207 

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 ) 

2215 

2216 try: 

2217 volume_properties[self.PROP_NOT_EXISTS] = self.STATE_CREATING 

2218 volume_properties[self.PROP_VOLUME_NAME] = volume_name 

2219 

2220 self._create_volume( 

2221 volume_uuid, volume_name, size, place_resources 

2222 ) 

2223 

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 

2236 

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 ) 

2241 

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) 

2246 

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 

2258 

2259 def _request_device_path(self, volume_uuid, volume_name, activate=False): 

2260 node_name = socket.gethostname() 

2261 

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) 

2267 

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 

2281 

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 

2288 

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 ) 

2295 

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 ) 

2305 

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 ) 

2311 

2312 # Mark only after computation of states. 

2313 self._mark_resource_cache_as_dirty() 

2314 

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) 

2321 

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) 

2328 

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 ) 

2335 

2336 def _build_volumes(self, repair): 

2337 properties = self._kv_cache 

2338 resource_names = self._fetch_resource_names() 

2339 

2340 self._volumes = set() 

2341 

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 ) 

2350 

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) 

2356 

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 

2368 

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 

2380 

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 

2390 

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) 

2401 

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) 

2406 

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 ) 

2414 

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 ) 

2424 

2425 for dest_uuid, src_uuid in updating_uuid_volumes.items(): 

2426 dest_namespace = self._build_volume_namespace(dest_uuid) 

2427 

2428 properties.namespace = dest_namespace 

2429 if int(properties.get(self.PROP_NOT_EXISTS)): 

2430 properties.clear() 

2431 continue 

2432 

2433 properties.namespace = self._build_volume_namespace(src_uuid) 

2434 properties.clear() 

2435 

2436 properties.namespace = dest_namespace 

2437 properties.pop(self.PROP_UPDATING_UUID_SRC) 

2438 

2439 if src_uuid in self._volumes: 

2440 self._volumes.remove(src_uuid) 

2441 self._volumes.add(dest_uuid) 

2442 

2443 def _get_sr_properties(self): 

2444 return self._create_linstor_kv(self._build_sr_namespace()) 

2445 

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() 

2451 

2452 volume_properties = {} 

2453 for volume_uuid in self._volumes: 

2454 volume_properties[volume_uuid] = '' 

2455 

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 

2463 

2464 return volume_properties 

2465 

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 ) 

2472 

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 

2477 

2478 @classmethod 

2479 def _build_sr_namespace(cls): 

2480 return '/{}/'.format(cls.NAMESPACE_SR) 

2481 

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) 

2488 

2489 @classmethod 

2490 def _get_error_str(cls, result): 

2491 return ', '.join([ 

2492 err.message for err in cls._filter_errors(result) 

2493 ]) 

2494 

2495 @classmethod 

2496 def _create_linstor_instance( 

2497 cls, uri, keep_uri_unmodified=False, attempt_count=30 

2498 ): 

2499 retry = False 

2500 

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 

2511 

2512 try: 

2513 return connect(uri) 

2514 except (linstor.errors.LinstorNetworkError, LinstorVolumeManagerError): 

2515 pass 

2516 

2517 if not keep_uri_unmodified: 

2518 uri = None 

2519 

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 ) 

2529 

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 ) 

2539 

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 

2550 

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 ) 

2556 

2557 @classmethod 

2558 def _request_database_path(cls, lin, activate=False): 

2559 node_name = socket.gethostname() 

2560 

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 ) 

2572 

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 

2587 

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 ) 

2599 

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 ) 

2606 

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 ) 

2619 

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)) 

2626 

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) 

2636 

2637 # Create real resources on the first nodes. 

2638 resources = [] 

2639 

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) 

2647 

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 )) 

2664 

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 ) 

2673 

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 ) 

2687 

2688 # Create database and ensure path exists locally and 

2689 # on replicated devices. 

2690 current_device_path = cls._request_database_path(lin, activate=True) 

2691 

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) 

2696 

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) 

2701 

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 ) 

2712 

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 ) 

2723 

2724 return expected_device_path 

2725 

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 ) 

2736 

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() 

2743 

2744 # 2. Move the config in the mounted volume. 

2745 if database_not_empty: 

2746 cls._move_files(DATABASE_PATH, backup_path) 

2747 

2748 cls._mount_volume(volume_path, DATABASE_PATH, mount) 

2749 

2750 if database_not_empty: 

2751 cls._move_files(backup_path, DATABASE_PATH, force) 

2752 

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 

2767 

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 )) 

2775 

2776 if mount != cls._is_mounted(DATABASE_PATH): 

2777 force_exec(lambda: cls._move_files( 

2778 backup_path, DATABASE_PATH 

2779 )) 

2780 

2781 force_exec(lambda: os.rmdir(backup_path)) 

2782 raise e 

2783 

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 

2790 

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 

2801 

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 ) 

2810 

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) 

2815 

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 

2825 

2826 if errors: 

2827 raise LinstorVolumeManagerError( 

2828 'Failed to destroy RG `{}`: {}' 

2829 .format(group_name, cls._get_error_str(errors)) 

2830 ) 

2831 

2832 return util.retry(destroy, maxretry=10) 

2833 

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('/', '_')) 

2839 

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 ) 

2851 

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 ) 

2860 

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] 

2866 

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 ) 

2881 

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 

2904 

2905 raise LinstorVolumeManagerError( 

2906 'Failed to move files from {} to {}: {}'.format( 

2907 src_dir, dest_dir, e 

2908 ) 

2909 ) 

2910 

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 ) 

2922 

2923 @staticmethod 

2924 def _get_filtered_properties(properties): 

2925 return dict(properties.items()) 

2926 

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 ] 

2933 

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 

2941 

2942 @classmethod 

2943 def _controller_is_running(cls): 

2944 return cls._service_is_running('linstor-controller') 

2945 

2946 @classmethod 

2947 def _start_controller(cls, start=True): 

2948 return cls._start_service('linstor-controller', start) 

2949 

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 ) 

2961 

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 

2968 

2969 @staticmethod 

2970 def _is_mounted(mountpoint): 

2971 (ret, out, err) = util.doexec(['mountpoint', '-q', mountpoint]) 

2972 return ret == 0 

2973 

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 ) 

2993 

2994 

2995# ============================================================================== 

2996 

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 

3003 

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] 

3009 

3010 volume_end = path.rfind('/') 

3011 if volume_end == res_name_end: 

3012 return 

3013 volume = path[volume_end + 1:] 

3014 

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 

3021 

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 

3029 

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 

3037 

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 )