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

828 

829 retry_count = 30 

830 while True: 

831 result = self._linstor.volume_dfn_modify( 

832 rsc_name=volume_name, 

833 volume_nr=0, 

834 size=new_size 

835 ) 

836 

837 self._mark_resource_cache_as_dirty() 

838 

839 error_str = self._get_error_str(result) 

840 if not error_str: 

841 break 

842 

843 # After volume creation, DRBD volume can be unusable during many seconds. 

844 # So we must retry the definition change if the device is not up to date. 

845 # Often the case for thick provisioning. 

846 if retry_count and error_str.find('non-UpToDate DRBD device') >= 0: 

847 time.sleep(2) 

848 retry_count -= 1 

849 continue 

850 

851 raise LinstorVolumeManagerError( 

852 'Could not resize volume `{}` from SR `{}`: {}' 

853 .format(volume_uuid, self._group_name, error_str) 

854 ) 

855 

856 def get_volume_name(self, volume_uuid): 

857 """ 

858 Get the name of a particular volume. 

859 :param str volume_uuid: The volume uuid of the name to get. 

860 :return: The volume name. 

861 :rtype: str 

862 """ 

863 

864 self._ensure_volume_exists(volume_uuid) 

865 volume_properties = self._get_volume_properties(volume_uuid) 

866 volume_name = volume_properties.get(self.PROP_VOLUME_NAME) 

867 if volume_name: 

868 return volume_name 

869 raise LinstorVolumeManagerError( 

870 'Failed to get volume name of {}'.format(volume_uuid) 

871 ) 

872 

873 def get_volume_size(self, volume_uuid): 

874 """ 

875 Get the size of a particular volume. 

876 :param str volume_uuid: The volume uuid of the size to get. 

877 :return: The volume size. 

878 :rtype: int 

879 """ 

880 

881 volume_name = self.get_volume_name(volume_uuid) 

882 dfns = self._linstor.resource_dfn_list_raise( 

883 query_volume_definitions=True, 

884 filter_by_resource_definitions=[volume_name] 

885 ).resource_definitions 

886 

887 size = dfns[0].volume_definitions[0].size 

888 if size < 0: 

889 raise LinstorVolumeManagerError( 

890 'Failed to get volume size of: {}'.format(volume_uuid) 

891 ) 

892 return size * 1024 

893 

894 def set_auto_promote_timeout(self, volume_uuid, timeout): 

895 """ 

896 Define the blocking time of open calls when a DRBD 

897 is already open on another host. 

898 :param str volume_uuid: The volume uuid to modify. 

899 """ 

900 

901 volume_name = self.get_volume_name(volume_uuid) 

902 result = self._linstor.resource_dfn_modify(volume_name, { 

903 'DrbdOptions/Resource/auto-promote-timeout': timeout 

904 }) 

905 error_str = self._get_error_str(result) 

906 if error_str: 

907 raise LinstorVolumeManagerError( 

908 'Could not change the auto promote timeout of `{}`: {}' 

909 .format(volume_uuid, error_str) 

910 ) 

911 

912 def get_volume_info(self, volume_uuid): 

913 """ 

914 Get the volume info of a particular volume. 

915 :param str volume_uuid: The volume uuid of the volume info to get. 

916 :return: The volume info. 

917 :rtype: VolumeInfo 

918 """ 

919 

920 volume_name = self.get_volume_name(volume_uuid) 

921 return self._get_volumes_info()[volume_name] 

922 

923 def get_device_path(self, volume_uuid): 

924 """ 

925 Get the dev path of a volume, create a diskless if necessary. 

926 :param str volume_uuid: The volume uuid to get the dev path. 

927 :return: The current device path of the volume. 

928 :rtype: str 

929 """ 

930 

931 volume_name = self.get_volume_name(volume_uuid) 

932 return self._find_device_path(volume_uuid, volume_name) 

933 

934 def get_volume_uuid_from_device_path(self, device_path): 

935 """ 

936 Get the volume uuid of a device_path. 

937 :param str device_path: The dev path to find the volume uuid. 

938 :return: The volume uuid of the local device path. 

939 :rtype: str 

940 """ 

941 

942 expected_volume_name = \ 

943 self.get_volume_name_from_device_path(device_path) 

944 

945 volume_names = self.get_volumes_with_name() 

946 for volume_uuid, volume_name in volume_names.items(): 

947 if volume_name == expected_volume_name: 

948 return volume_uuid 

949 

950 raise LinstorVolumeManagerError( 

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

952 ) 

953 

954 def get_volume_name_from_device_path(self, device_path): 

955 """ 

956 Get the volume name of a device_path. 

957 :param str device_path: The dev path to find the volume name. 

958 :return: The volume name of the device path. 

959 :rtype: str 

960 """ 

961 

962 # Assume that we have a path like this: 

963 # - "/dev/drbd/by-res/xcp-volume-<UUID>/0" 

964 # - "../xcp-volume-<UUID>/0" 

965 if device_path.startswith(DRBD_BY_RES_PATH): 

966 prefix_len = len(DRBD_BY_RES_PATH) 

967 else: 

968 assert device_path.startswith('../') 

969 prefix_len = 3 

970 

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

972 assert res_name_end != -1 

973 return device_path[prefix_len:res_name_end] 

974 

975 def update_volume_uuid(self, volume_uuid, new_volume_uuid, force=False): 

976 """ 

977 Change the uuid of a volume. 

978 :param str volume_uuid: The volume to modify. 

979 :param str new_volume_uuid: The new volume uuid to use. 

980 :param bool force: If true we doesn't check if volume_uuid is in the 

981 volume list. I.e. the volume can be marked as deleted but the volume 

982 can still be in the LINSTOR KV store if the deletion has failed. 

983 In specific cases like "undo" after a failed clone we must rename a bad 

984 deleted VDI. 

985 """ 

986 

987 self._logger( 

988 'Trying to update volume UUID {} to {}...' 

989 .format(volume_uuid, new_volume_uuid) 

990 ) 

991 assert volume_uuid != new_volume_uuid, 'can\'t update volume UUID, same value' 

992 

993 if not force: 

994 self._ensure_volume_exists(volume_uuid) 

995 self.ensure_volume_is_not_locked(volume_uuid) 

996 

997 if new_volume_uuid in self._volumes: 

998 raise LinstorVolumeManagerError( 

999 'Volume `{}` already exists'.format(new_volume_uuid), 

1000 LinstorVolumeManagerError.ERR_VOLUME_EXISTS 

1001 ) 

1002 

1003 volume_properties = self._get_volume_properties(volume_uuid) 

1004 if volume_properties.get(self.PROP_UPDATING_UUID_SRC): 

1005 raise LinstorVolumeManagerError( 

1006 'Cannot update volume uuid {}: invalid state' 

1007 .format(volume_uuid) 

1008 ) 

1009 

1010 # 1. Copy in temp variables metadata and volume_name. 

1011 metadata = volume_properties.get(self.PROP_METADATA) 

1012 volume_name = volume_properties.get(self.PROP_VOLUME_NAME) 

1013 

1014 # 2. Switch to new volume namespace. 

1015 volume_properties.namespace = self._build_volume_namespace( 

1016 new_volume_uuid 

1017 ) 

1018 

1019 if list(volume_properties.items()): 

1020 raise LinstorVolumeManagerError( 

1021 'Cannot update volume uuid {} to {}: ' 

1022 .format(volume_uuid, new_volume_uuid) + 

1023 'this last one is not empty' 

1024 ) 

1025 

1026 try: 

1027 # 3. Mark new volume properties with PROP_UPDATING_UUID_SRC. 

1028 # If we crash after that, the new properties can be removed 

1029 # properly. 

1030 volume_properties[self.PROP_NOT_EXISTS] = self.STATE_NOT_EXISTS 

1031 volume_properties[self.PROP_UPDATING_UUID_SRC] = volume_uuid 

1032 

1033 # 4. Copy the properties. 

1034 # Note: On new volumes, during clone for example, the metadata 

1035 # may be missing. So we must test it to avoid this error: 

1036 # "None has to be a str/unicode, but is <type 'NoneType'>" 

1037 if metadata: 

1038 volume_properties[self.PROP_METADATA] = metadata 

1039 volume_properties[self.PROP_VOLUME_NAME] = volume_name 

1040 

1041 # 5. Ok! 

1042 volume_properties[self.PROP_NOT_EXISTS] = self.STATE_EXISTS 

1043 except Exception as e: 

1044 try: 

1045 # Clear the new volume properties in case of failure. 

1046 assert volume_properties.namespace == \ 

1047 self._build_volume_namespace(new_volume_uuid) 

1048 volume_properties.clear() 

1049 except Exception as e: 

1050 self._logger( 

1051 'Failed to clear new volume properties: {} (ignoring...)' 

1052 .format(e) 

1053 ) 

1054 raise LinstorVolumeManagerError( 

1055 'Failed to copy volume properties: {}'.format(e) 

1056 ) 

1057 

1058 try: 

1059 # 6. After this point, it's ok we can remove the 

1060 # PROP_UPDATING_UUID_SRC property and clear the src properties 

1061 # without problems. 

1062 

1063 # 7. Switch to old volume namespace. 

1064 volume_properties.namespace = self._build_volume_namespace( 

1065 volume_uuid 

1066 ) 

1067 volume_properties.clear() 

1068 

1069 # 8. Switch a last time to new volume namespace. 

1070 volume_properties.namespace = self._build_volume_namespace( 

1071 new_volume_uuid 

1072 ) 

1073 volume_properties.pop(self.PROP_UPDATING_UUID_SRC) 

1074 except Exception as e: 

1075 raise LinstorVolumeManagerError( 

1076 'Failed to clear volume properties ' 

1077 'after volume uuid update: {}'.format(e) 

1078 ) 

1079 

1080 self._volumes.remove(volume_uuid) 

1081 self._volumes.add(new_volume_uuid) 

1082 

1083 self._logger( 

1084 'UUID update succeeded of {} to {}! (properties={})' 

1085 .format( 

1086 volume_uuid, new_volume_uuid, 

1087 self._get_filtered_properties(volume_properties) 

1088 ) 

1089 ) 

1090 

1091 def update_volume_name(self, volume_uuid, volume_name): 

1092 """ 

1093 Change the volume name of a volume. 

1094 :param str volume_uuid: The volume to modify. 

1095 :param str volume_name: The volume_name to use. 

1096 """ 

1097 

1098 self._ensure_volume_exists(volume_uuid) 

1099 self.ensure_volume_is_not_locked(volume_uuid) 

1100 if not volume_name.startswith(self.PREFIX_VOLUME): 

1101 raise LinstorVolumeManagerError( 

1102 'Volume name `{}` must be start with `{}`' 

1103 .format(volume_name, self.PREFIX_VOLUME) 

1104 ) 

1105 

1106 if volume_name not in self._fetch_resource_names(): 

1107 raise LinstorVolumeManagerError( 

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

1109 ) 

1110 

1111 volume_properties = self._get_volume_properties(volume_uuid) 

1112 volume_properties[self.PROP_VOLUME_NAME] = volume_name 

1113 

1114 def get_usage_states(self, volume_uuid): 

1115 """ 

1116 Check if a volume is currently used. 

1117 :param str volume_uuid: The volume uuid to check. 

1118 :return: A dictionnary that contains states. 

1119 :rtype: dict(str, bool or None) 

1120 """ 

1121 

1122 states = {} 

1123 

1124 volume_name = self.get_volume_name(volume_uuid) 

1125 for resource_state in self._linstor.resource_list_raise( 

1126 filter_by_resources=[volume_name] 

1127 ).resource_states: 

1128 states[resource_state.node_name] = resource_state.in_use 

1129 

1130 return states 

1131 

1132 def get_volume_openers(self, volume_uuid): 

1133 """ 

1134 Get openers of a volume. 

1135 :param str volume_uuid: The volume uuid to monitor. 

1136 :return: A dictionnary that contains openers. 

1137 :rtype: dict(str, obj) 

1138 """ 

1139 return get_all_volume_openers(self.get_volume_name(volume_uuid), '0') 

1140 

1141 def get_volumes_with_name(self): 

1142 """ 

1143 Give a volume dictionnary that contains names actually owned. 

1144 :return: A volume/name dict. 

1145 :rtype: dict(str, str) 

1146 """ 

1147 return self._get_volumes_by_property(self.REG_VOLUME_NAME) 

1148 

1149 def get_volumes_with_info(self): 

1150 """ 

1151 Give a volume dictionnary that contains VolumeInfos. 

1152 :return: A volume/VolumeInfo dict. 

1153 :rtype: dict(str, VolumeInfo) 

1154 """ 

1155 

1156 volumes = {} 

1157 

1158 all_volume_info = self._get_volumes_info() 

1159 volume_names = self.get_volumes_with_name() 

1160 for volume_uuid, volume_name in volume_names.items(): 

1161 if volume_name: 

1162 volume_info = all_volume_info.get(volume_name) 

1163 if volume_info: 

1164 volumes[volume_uuid] = volume_info 

1165 continue 

1166 

1167 # Well I suppose if this volume is not available, 

1168 # LINSTOR has been used directly without using this API. 

1169 volumes[volume_uuid] = self.VolumeInfo('') 

1170 

1171 return volumes 

1172 

1173 def get_volumes_with_metadata(self): 

1174 """ 

1175 Give a volume dictionnary that contains metadata. 

1176 :return: A volume/metadata dict. 

1177 :rtype: dict(str, dict) 

1178 """ 

1179 

1180 volumes = {} 

1181 

1182 metadata = self._get_volumes_by_property(self.REG_METADATA) 

1183 for volume_uuid, volume_metadata in metadata.items(): 

1184 if volume_metadata: 

1185 volume_metadata = json.loads(volume_metadata) 

1186 if isinstance(volume_metadata, dict): 

1187 volumes[volume_uuid] = volume_metadata 

1188 continue 

1189 raise LinstorVolumeManagerError( 

1190 'Expected dictionary in volume metadata: {}' 

1191 .format(volume_uuid) 

1192 ) 

1193 

1194 volumes[volume_uuid] = {} 

1195 

1196 return volumes 

1197 

1198 def get_volume_metadata(self, volume_uuid): 

1199 """ 

1200 Get the metadata of a volume. 

1201 :return: Dictionary that contains metadata. 

1202 :rtype: dict 

1203 """ 

1204 

1205 self._ensure_volume_exists(volume_uuid) 

1206 volume_properties = self._get_volume_properties(volume_uuid) 

1207 metadata = volume_properties.get(self.PROP_METADATA) 

1208 if metadata: 

1209 metadata = json.loads(metadata) 

1210 if isinstance(metadata, dict): 

1211 return metadata 

1212 raise LinstorVolumeManagerError( 

1213 'Expected dictionary in volume metadata: {}' 

1214 .format(volume_uuid) 

1215 ) 

1216 return {} 

1217 

1218 def set_volume_metadata(self, volume_uuid, metadata): 

1219 """ 

1220 Set the metadata of a volume. 

1221 :param dict metadata: Dictionary that contains metadata. 

1222 """ 

1223 

1224 self._ensure_volume_exists(volume_uuid) 

1225 self.ensure_volume_is_not_locked(volume_uuid) 

1226 

1227 assert isinstance(metadata, dict) 

1228 volume_properties = self._get_volume_properties(volume_uuid) 

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

1230 

1231 def update_volume_metadata(self, volume_uuid, metadata): 

1232 """ 

1233 Update the metadata of a volume. It modify only the given keys. 

1234 It doesn't remove unreferenced key instead of set_volume_metadata. 

1235 :param dict metadata: Dictionary that contains metadata. 

1236 """ 

1237 

1238 self._ensure_volume_exists(volume_uuid) 

1239 self.ensure_volume_is_not_locked(volume_uuid) 

1240 

1241 assert isinstance(metadata, dict) 

1242 volume_properties = self._get_volume_properties(volume_uuid) 

1243 

1244 current_metadata = json.loads( 

1245 volume_properties.get(self.PROP_METADATA, '{}') 

1246 ) 

1247 if not isinstance(metadata, dict): 

1248 raise LinstorVolumeManagerError( 

1249 'Expected dictionary in volume metadata: {}' 

1250 .format(volume_uuid) 

1251 ) 

1252 

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

1254 current_metadata[key] = value 

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

1256 

1257 def shallow_clone_volume(self, volume_uuid, clone_uuid, persistent=True): 

1258 """ 

1259 Clone a volume. Do not copy the data, this method creates a new volume 

1260 with the same size. 

1261 :param str volume_uuid: The volume to clone. 

1262 :param str clone_uuid: The cloned volume. 

1263 :param bool persistent: If false the volume will be unavailable 

1264 on the next constructor call LinstorSR(...). 

1265 :return: The current device path of the cloned volume. 

1266 :rtype: str 

1267 """ 

1268 

1269 volume_name = self.get_volume_name(volume_uuid) 

1270 self.ensure_volume_is_not_locked(volume_uuid) 

1271 

1272 # 1. Find ideal nodes + size to use. 

1273 ideal_node_names, size = self._get_volume_node_names_and_size( 

1274 volume_name 

1275 ) 

1276 if size <= 0: 

1277 raise LinstorVolumeManagerError( 

1278 'Invalid size of {} for volume `{}`'.format(size, volume_name) 

1279 ) 

1280 

1281 # 2. Create clone! 

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

1283 

1284 def remove_resourceless_volumes(self): 

1285 """ 

1286 Remove all volumes without valid or non-empty name 

1287 (i.e. without LINSTOR resource). It's different than 

1288 LinstorVolumeManager constructor that takes a `repair` param that 

1289 removes volumes with `PROP_NOT_EXISTS` to 1. 

1290 """ 

1291 

1292 resource_names = self._fetch_resource_names() 

1293 for volume_uuid, volume_name in self.get_volumes_with_name().items(): 

1294 if not volume_name or volume_name not in resource_names: 

1295 # Don't force, we can be sure of what's happening. 

1296 self.destroy_volume(volume_uuid) 

1297 

1298 def destroy(self): 

1299 """ 

1300 Destroy this SR. Object should not be used after that. 

1301 :param bool force: Try to destroy volumes before if true. 

1302 """ 

1303 

1304 # 1. Ensure volume list is empty. No cost. 

1305 if self._volumes: 

1306 raise LinstorVolumeManagerError( 

1307 'Cannot destroy LINSTOR volume manager: ' 

1308 'It exists remaining volumes' 

1309 ) 

1310 

1311 # 2. Fetch ALL resource names. 

1312 # This list may therefore contain volumes created outside 

1313 # the scope of the driver. 

1314 resource_names = self._fetch_resource_names(ignore_deleted=False) 

1315 try: 

1316 resource_names.remove(DATABASE_VOLUME_NAME) 

1317 except KeyError: 

1318 # Really strange to reach that point. 

1319 # Normally we always have the database volume in the list. 

1320 pass 

1321 

1322 # 3. Ensure the resource name list is entirely empty... 

1323 if resource_names: 

1324 raise LinstorVolumeManagerError( 

1325 'Cannot destroy LINSTOR volume manager: ' 

1326 'It exists remaining volumes (created externally or being deleted)' 

1327 ) 

1328 

1329 # 4. Destroying... 

1330 controller_is_running = self._controller_is_running() 

1331 uri = 'linstor://localhost' 

1332 try: 

1333 if controller_is_running: 

1334 self._start_controller(start=False) 

1335 

1336 # 4.1. Umount LINSTOR database. 

1337 self._mount_database_volume( 

1338 self.build_device_path(DATABASE_VOLUME_NAME), 

1339 mount=False, 

1340 force=True 

1341 ) 

1342 

1343 # 4.2. Refresh instance. 

1344 self._start_controller(start=True) 

1345 self._linstor = self._create_linstor_instance( 

1346 uri, keep_uri_unmodified=True 

1347 ) 

1348 

1349 # 4.3. Destroy database volume. 

1350 self._destroy_resource(DATABASE_VOLUME_NAME) 

1351 

1352 # 4.4. Refresh linstor connection. 

1353 # Without we get this error: 

1354 # "Cannot delete resource group 'xcp-sr-linstor_group_thin_device' because it has existing resource definitions.." 

1355 # Because the deletion of the databse was not seen by Linstor for some reason. 

1356 # It seems a simple refresh of the Linstor connection make it aware of the deletion. 

1357 self._linstor.disconnect() 

1358 self._linstor.connect() 

1359 

1360 # 4.5. Destroy group and storage pools. 

1361 self._destroy_resource_group(self._linstor, self._group_name) 

1362 for pool in self._get_storage_pools(force=True): 

1363 self._destroy_storage_pool( 

1364 self._linstor, pool.name, pool.node_name 

1365 ) 

1366 except Exception as e: 

1367 self._start_controller(start=controller_is_running) 

1368 raise e 

1369 

1370 try: 

1371 self._start_controller(start=False) 

1372 for file in glob.glob(DATABASE_PATH + '/'): 

1373 os.remove(file) 

1374 except Exception as e: 

1375 util.SMlog( 

1376 'Ignoring failure after LINSTOR SR destruction: {}' 

1377 .format(e) 

1378 ) 

1379 

1380 def find_up_to_date_diskful_nodes(self, volume_uuid): 

1381 """ 

1382 Find all nodes that contain a specific volume using diskful disks. 

1383 The disk must be up to data to be used. 

1384 :param str volume_uuid: The volume to use. 

1385 :return: The available nodes. 

1386 :rtype: tuple(set(str), str) 

1387 """ 

1388 

1389 volume_name = self.get_volume_name(volume_uuid) 

1390 

1391 in_use_by = None 

1392 node_names = set() 

1393 

1394 resource_states = filter( 

1395 lambda resource_state: resource_state.name == volume_name, 

1396 self._get_resource_cache().resource_states 

1397 ) 

1398 

1399 for resource_state in resource_states: 

1400 volume_state = resource_state.volume_states[0] 

1401 if volume_state.disk_state == 'UpToDate': 

1402 node_names.add(resource_state.node_name) 

1403 if resource_state.in_use: 

1404 in_use_by = resource_state.node_name 

1405 

1406 return (node_names, in_use_by) 

1407 

1408 def invalidate_resource_cache(self): 

1409 """ 

1410 If resources are impacted by external commands like vhdutil, 

1411 it's necessary to call this function to invalidate current resource 

1412 cache. 

1413 """ 

1414 self._mark_resource_cache_as_dirty() 

1415 

1416 def has_node(self, node_name): 

1417 """ 

1418 Check if a node exists in the LINSTOR database. 

1419 :rtype: bool 

1420 """ 

1421 result = self._linstor.node_list() 

1422 error_str = self._get_error_str(result) 

1423 if error_str: 

1424 raise LinstorVolumeManagerError( 

1425 'Failed to list nodes using `{}`: {}' 

1426 .format(node_name, error_str) 

1427 ) 

1428 return bool(result[0].node(node_name)) 

1429 

1430 def create_node(self, node_name, ip): 

1431 """ 

1432 Create a new node in the LINSTOR database. 

1433 :param str node_name: Node name to use. 

1434 :param str ip: Host IP to communicate. 

1435 """ 

1436 result = self._linstor.node_create( 

1437 node_name, 

1438 linstor.consts.VAL_NODE_TYPE_CMBD, 

1439 ip 

1440 ) 

1441 errors = self._filter_errors(result) 

1442 if errors: 

1443 error_str = self._get_error_str(errors) 

1444 raise LinstorVolumeManagerError( 

1445 'Failed to create node `{}`: {}'.format(node_name, error_str) 

1446 ) 

1447 

1448 def destroy_node(self, node_name): 

1449 """ 

1450 Destroy a node in the LINSTOR database. 

1451 :param str node_name: Node name to remove. 

1452 """ 

1453 result = self._linstor.node_delete(node_name) 

1454 errors = self._filter_errors(result) 

1455 if errors: 

1456 error_str = self._get_error_str(errors) 

1457 raise LinstorVolumeManagerError( 

1458 'Failed to destroy node `{}`: {}'.format(node_name, error_str) 

1459 ) 

1460 

1461 def create_node_interface(self, node_name, name, ip): 

1462 """ 

1463 Create a new node interface in the LINSTOR database. 

1464 :param str node_name: Node name of the interface to use. 

1465 :param str name: Interface to create. 

1466 :param str ip: IP of the interface. 

1467 """ 

1468 result = self._linstor.netinterface_create(node_name, name, ip) 

1469 errors = self._filter_errors(result) 

1470 if errors: 

1471 error_str = self._get_error_str(errors) 

1472 raise LinstorVolumeManagerError( 

1473 'Failed to create node interface on `{}`: {}'.format(node_name, error_str) 

1474 ) 

1475 

1476 def destroy_node_interface(self, node_name, name): 

1477 """ 

1478 Destroy a node interface in the LINSTOR database. 

1479 :param str node_name: Node name of the interface to remove. 

1480 :param str name: Interface to remove. 

1481 """ 

1482 result = self._linstor.netinterface_delete(node_name, name) 

1483 errors = self._filter_errors(result) 

1484 if errors: 

1485 error_str = self._get_error_str(errors) 

1486 raise LinstorVolumeManagerError( 

1487 'Failed to destroy node interface on `{}`: {}'.format(node_name, error_str) 

1488 ) 

1489 

1490 def modify_node_interface(self, node_name, name, ip): 

1491 """ 

1492 Modify a node interface in the LINSTOR database. Create it if necessary. 

1493 :param str node_name: Node name of the interface to use. 

1494 :param str name: Interface to modify or create. 

1495 :param str ip: IP of the interface. 

1496 """ 

1497 result = self._linstor.netinterface_create(node_name, name, ip) 

1498 errors = self._filter_errors(result) 

1499 if not errors: 

1500 return 

1501 

1502 if self._check_errors(errors, [linstor.consts.FAIL_EXISTS_NET_IF]): 

1503 result = self._linstor.netinterface_modify(node_name, name, ip) 

1504 errors = self._filter_errors(result) 

1505 if not errors: 

1506 return 

1507 

1508 error_str = self._get_error_str(errors) 

1509 raise LinstorVolumeManagerError( 

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

1511 ) 

1512 

1513 def list_node_interfaces(self, node_name): 

1514 """ 

1515 List all node interfaces. 

1516 :param str node_name: Node name to use to list interfaces. 

1517 :rtype: list 

1518 : 

1519 """ 

1520 result = self._linstor.net_interface_list(node_name) 

1521 if not result: 

1522 raise LinstorVolumeManagerError( 

1523 'Unable to list interfaces on `{}`: no list received'.format(node_name) 

1524 ) 

1525 

1526 interfaces = {} 

1527 for interface in result: 

1528 interface = interface._rest_data 

1529 interfaces[interface['name']] = { 

1530 'address': interface['address'], 

1531 'active': interface['is_active'] 

1532 } 

1533 return interfaces 

1534 

1535 def set_node_preferred_interface(self, node_name, name): 

1536 """ 

1537 Set the preferred interface to use on a node. 

1538 :param str node_name: Node name of the interface. 

1539 :param str name: Preferred interface to use. 

1540 """ 

1541 result = self._linstor.node_modify(node_name, property_dict={'PrefNic': name}) 

1542 errors = self._filter_errors(result) 

1543 if errors: 

1544 error_str = self._get_error_str(errors) 

1545 raise LinstorVolumeManagerError( 

1546 'Failed to set preferred node interface on `{}`: {}'.format(node_name, error_str) 

1547 ) 

1548 

1549 def get_nodes_info(self): 

1550 """ 

1551 Get all nodes + statuses, used or not by the pool. 

1552 :rtype: dict(str, dict) 

1553 """ 

1554 try: 

1555 nodes = {} 

1556 for node in self._linstor.node_list_raise().nodes: 

1557 nodes[node.name] = node.connection_status 

1558 return nodes 

1559 except Exception as e: 

1560 raise LinstorVolumeManagerError( 

1561 'Failed to get all nodes: `{}`'.format(e) 

1562 ) 

1563 

1564 def get_storage_pools_info(self): 

1565 """ 

1566 Give all storage pools of current group name. 

1567 :rtype: dict(str, list) 

1568 """ 

1569 storage_pools = {} 

1570 for pool in self._get_storage_pools(force=True): 

1571 if pool.node_name not in storage_pools: 

1572 storage_pools[pool.node_name] = [] 

1573 

1574 size = -1 

1575 capacity = -1 

1576 

1577 space = pool.free_space 

1578 if space: 

1579 size = space.free_capacity 

1580 if size < 0: 

1581 size = -1 

1582 else: 

1583 size *= 1024 

1584 capacity = space.total_capacity 

1585 if capacity <= 0: 

1586 capacity = -1 

1587 else: 

1588 capacity *= 1024 

1589 

1590 storage_pools[pool.node_name].append({ 

1591 'storage-pool-name': pool.name, 

1592 'uuid': pool.uuid, 

1593 'free-size': size, 

1594 'capacity': capacity 

1595 }) 

1596 

1597 return storage_pools 

1598 

1599 def get_resources_info(self): 

1600 """ 

1601 Give all resources of current group name. 

1602 :rtype: dict(str, list) 

1603 """ 

1604 resources = {} 

1605 resource_list = self._linstor.resource_list_raise() 

1606 for resource in resource_list.resources: 

1607 if resource.name not in resources: 

1608 resources[resource.name] = {} 

1609 

1610 resources[resource.name][resource.node_name] = { 

1611 'volumes': [], 

1612 'diskful': linstor.consts.FLAG_DISKLESS not in resource.flags, 

1613 'tie-breaker': linstor.consts.FLAG_TIE_BREAKER in resource.flags 

1614 } 

1615 

1616 for volume in resource.volumes: 

1617 # We ignore diskless pools of the form "DfltDisklessStorPool". 

1618 if volume.storage_pool_name != self._group_name: 

1619 continue 

1620 

1621 usable_size = volume.usable_size 

1622 if usable_size < 0: 

1623 usable_size = -1 

1624 else: 

1625 usable_size *= 1024 

1626 

1627 allocated_size = volume.allocated_size 

1628 if allocated_size < 0: 

1629 allocated_size = -1 

1630 else: 

1631 allocated_size *= 1024 

1632 

1633 resources[resource.name][resource.node_name]['volumes'].append({ 

1634 'storage-pool-name': volume.storage_pool_name, 

1635 'uuid': volume.uuid, 

1636 'number': volume.number, 

1637 'device-path': volume.device_path, 

1638 'usable-size': usable_size, 

1639 'allocated-size': allocated_size 

1640 }) 

1641 

1642 for resource_state in resource_list.resource_states: 

1643 resource = resources[resource_state.rsc_name][resource_state.node_name] 

1644 resource['in-use'] = resource_state.in_use 

1645 

1646 volumes = resource['volumes'] 

1647 for volume_state in resource_state.volume_states: 

1648 volume = next((x for x in volumes if x['number'] == volume_state.number), None) 

1649 if volume: 

1650 volume['disk-state'] = volume_state.disk_state 

1651 

1652 return resources 

1653 

1654 def get_database_path(self): 

1655 """ 

1656 Get the database path. 

1657 :return: The current database path. 

1658 :rtype: str 

1659 """ 

1660 return self._request_database_path(self._linstor) 

1661 

1662 @classmethod 

1663 def create_sr( 

1664 cls, group_name, ips, redundancy, 

1665 thin_provisioning, auto_quorum, 

1666 logger=default_logger.__func__ 

1667 ): 

1668 """ 

1669 Create a new SR on the given nodes. 

1670 :param str group_name: The SR group_name to use. 

1671 :param set(str) ips: Node ips. 

1672 :param int redundancy: How many copy of volumes should we store? 

1673 :param bool thin_provisioning: Use thin or thick provisioning. 

1674 :param bool auto_quorum: DB quorum is monitored by LINSTOR. 

1675 :param function logger: Function to log messages. 

1676 :return: A new LinstorSr instance. 

1677 :rtype: LinstorSr 

1678 """ 

1679 

1680 try: 

1681 cls._start_controller(start=True) 

1682 sr = cls._create_sr( 

1683 group_name, 

1684 ips, 

1685 redundancy, 

1686 thin_provisioning, 

1687 auto_quorum, 

1688 logger 

1689 ) 

1690 finally: 

1691 # Controller must be stopped and volume unmounted because 

1692 # it is the role of the drbd-reactor daemon to do the right 

1693 # actions. 

1694 cls._start_controller(start=False) 

1695 cls._mount_volume( 

1696 cls.build_device_path(DATABASE_VOLUME_NAME), 

1697 DATABASE_PATH, 

1698 mount=False 

1699 ) 

1700 return sr 

1701 

1702 @classmethod 

1703 def _create_sr( 

1704 cls, group_name, ips, redundancy, 

1705 thin_provisioning, auto_quorum, 

1706 logger=default_logger.__func__ 

1707 ): 

1708 # 1. Check if SR already exists. 

1709 uri = 'linstor://localhost' 

1710 

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

1712 

1713 node_names = list(ips.keys()) 

1714 for node_name, ip in ips.items(): 

1715 while True: 

1716 # Try to create node. 

1717 result = lin.node_create( 

1718 node_name, 

1719 linstor.consts.VAL_NODE_TYPE_CMBD, 

1720 ip 

1721 ) 

1722 

1723 errors = cls._filter_errors(result) 

1724 if cls._check_errors( 

1725 errors, [linstor.consts.FAIL_EXISTS_NODE] 

1726 ): 

1727 # If it already exists, remove, then recreate. 

1728 result = lin.node_delete(node_name) 

1729 error_str = cls._get_error_str(result) 

1730 if error_str: 

1731 raise LinstorVolumeManagerError( 

1732 'Failed to remove old node `{}`: {}' 

1733 .format(node_name, error_str) 

1734 ) 

1735 elif not errors: 

1736 break # Created! 

1737 else: 

1738 raise LinstorVolumeManagerError( 

1739 'Failed to create node `{}` with ip `{}`: {}'.format( 

1740 node_name, ip, cls._get_error_str(errors) 

1741 ) 

1742 ) 

1743 

1744 driver_pool_name = group_name 

1745 base_group_name = group_name 

1746 group_name = cls._build_group_name(group_name) 

1747 pools = lin.storage_pool_list_raise(filter_by_stor_pools=[group_name]) 

1748 pools = pools.storage_pools 

1749 if pools: 

1750 existing_node_names = [pool.node_name for pool in pools] 

1751 raise LinstorVolumeManagerError( 

1752 'Unable to create SR `{}`. It already exists on node(s): {}' 

1753 .format(group_name, existing_node_names) 

1754 ) 

1755 

1756 if lin.resource_group_list_raise( 

1757 [group_name] 

1758 ).resource_groups: 

1759 if not lin.resource_dfn_list_raise().resource_definitions: 

1760 backup_path = cls._create_database_backup_path() 

1761 logger( 

1762 'Group name already exists `{}` without LVs. ' 

1763 'Ignoring and moving the config files in {}'.format(group_name, backup_path) 

1764 ) 

1765 cls._move_files(DATABASE_PATH, backup_path) 

1766 else: 

1767 raise LinstorVolumeManagerError( 

1768 'Unable to create SR `{}`: The group name already exists' 

1769 .format(group_name) 

1770 ) 

1771 

1772 if thin_provisioning: 

1773 driver_pool_parts = driver_pool_name.split('/') 

1774 if not len(driver_pool_parts) == 2: 

1775 raise LinstorVolumeManagerError( 

1776 'Invalid group name using thin provisioning. ' 

1777 'Expected format: \'VG/LV`\'' 

1778 ) 

1779 

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

1781 reg_volume_group_not_found = re.compile( 

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

1783 ) 

1784 

1785 i = 0 

1786 try: 

1787 # 2.a. Create storage pools. 

1788 storage_pool_count = 0 

1789 while i < len(node_names): 

1790 node_name = node_names[i] 

1791 

1792 result = lin.storage_pool_create( 

1793 node_name=node_name, 

1794 storage_pool_name=group_name, 

1795 storage_driver='LVM_THIN' if thin_provisioning else 'LVM', 

1796 driver_pool_name=driver_pool_name 

1797 ) 

1798 

1799 errors = linstor.Linstor.filter_api_call_response_errors( 

1800 result 

1801 ) 

1802 if errors: 

1803 if len(errors) == 1 and errors[0].is_error( 

1804 linstor.consts.FAIL_STOR_POOL_CONFIGURATION_ERROR 

1805 ) and reg_volume_group_not_found.match(errors[0].message): 

1806 logger( 

1807 'Volume group `{}` not found on `{}`. Ignoring...' 

1808 .format(group_name, node_name) 

1809 ) 

1810 cls._destroy_storage_pool(lin, group_name, node_name) 

1811 else: 

1812 error_str = cls._get_error_str(result) 

1813 raise LinstorVolumeManagerError( 

1814 'Could not create SP `{}` on node `{}`: {}' 

1815 .format(group_name, node_name, error_str) 

1816 ) 

1817 else: 

1818 storage_pool_count += 1 

1819 i += 1 

1820 

1821 if not storage_pool_count: 

1822 raise LinstorVolumeManagerError( 

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

1824 group_name, 

1825 ) 

1826 ) 

1827 

1828 # 2.b. Create resource group. 

1829 rg_creation_attempt = 0 

1830 while True: 

1831 result = lin.resource_group_create( 

1832 name=group_name, 

1833 place_count=redundancy, 

1834 storage_pool=group_name, 

1835 diskless_on_remaining=False 

1836 ) 

1837 error_str = cls._get_error_str(result) 

1838 if not error_str: 

1839 break 

1840 

1841 errors = cls._filter_errors(result) 

1842 if cls._check_errors(errors, [linstor.consts.FAIL_EXISTS_RSC_GRP]): 

1843 rg_creation_attempt += 1 

1844 if rg_creation_attempt < 2: 

1845 try: 

1846 cls._destroy_resource_group(lin, group_name) 

1847 except Exception as e: 

1848 error_str = 'Failed to destroy old and empty RG: {}'.format(e) 

1849 else: 

1850 continue 

1851 

1852 raise LinstorVolumeManagerError( 

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

1854 ) 

1855 

1856 # 2.c. Create volume group. 

1857 result = lin.volume_group_create(group_name) 

1858 error_str = cls._get_error_str(result) 

1859 if error_str: 

1860 raise LinstorVolumeManagerError( 

1861 'Could not create VG `{}`: {}'.format( 

1862 group_name, error_str 

1863 ) 

1864 ) 

1865 

1866 # 3. Create the LINSTOR database volume and mount it. 

1867 try: 

1868 logger('Creating database volume...') 

1869 volume_path = cls._create_database_volume( 

1870 lin, group_name, node_names, redundancy, auto_quorum 

1871 ) 

1872 except LinstorVolumeManagerError as e: 

1873 if e.code != LinstorVolumeManagerError.ERR_VOLUME_EXISTS: 

1874 logger('Destroying database volume after creation fail...') 

1875 cls._force_destroy_database_volume(lin, group_name) 

1876 raise 

1877 

1878 try: 

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

1880 

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

1882 # LINSTOR config. 

1883 cls._start_controller(start=False) 

1884 

1885 cls._mount_database_volume(volume_path) 

1886 except Exception as e: 

1887 # Ensure we are connected because controller has been 

1888 # restarted during mount call. 

1889 logger('Destroying database volume after mount fail...') 

1890 

1891 try: 

1892 cls._start_controller(start=True) 

1893 except Exception: 

1894 pass 

1895 

1896 lin = cls._create_linstor_instance( 

1897 uri, keep_uri_unmodified=True 

1898 ) 

1899 cls._force_destroy_database_volume(lin, group_name) 

1900 raise e 

1901 

1902 cls._start_controller(start=True) 

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

1904 

1905 # 4. Remove storage pools/resource/volume group in the case of errors. 

1906 except Exception as e: 

1907 logger('Destroying resource group and storage pools after fail...') 

1908 try: 

1909 cls._destroy_resource_group(lin, group_name) 

1910 except Exception as e2: 

1911 logger('Failed to destroy resource group: {}'.format(e2)) 

1912 pass 

1913 j = 0 

1914 i = min(i, len(node_names) - 1) 

1915 while j <= i: 

1916 try: 

1917 cls._destroy_storage_pool(lin, group_name, node_names[j]) 

1918 except Exception as e2: 

1919 logger('Failed to destroy resource group: {}'.format(e2)) 

1920 pass 

1921 j += 1 

1922 raise e 

1923 

1924 # 5. Return new instance. 

1925 instance = cls.__new__(cls) 

1926 instance._linstor = lin 

1927 instance._logger = logger 

1928 instance._redundancy = redundancy 

1929 instance._base_group_name = base_group_name 

1930 instance._group_name = group_name 

1931 instance._volumes = set() 

1932 instance._storage_pools_time = 0 

1933 instance._kv_cache = instance._create_kv_cache() 

1934 instance._resource_cache = None 

1935 instance._resource_cache_dirty = True 

1936 instance._volume_info_cache = None 

1937 instance._volume_info_cache_dirty = True 

1938 return instance 

1939 

1940 @classmethod 

1941 def build_device_path(cls, volume_name): 

1942 """ 

1943 Build a device path given a volume name. 

1944 :param str volume_name: The volume name to use. 

1945 :return: A valid or not device path. 

1946 :rtype: str 

1947 """ 

1948 

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

1950 

1951 @classmethod 

1952 def build_volume_name(cls, base_name): 

1953 """ 

1954 Build a volume name given a base name (i.e. a UUID). 

1955 :param str volume_name: The volume name to use. 

1956 :return: A valid or not device path. 

1957 :rtype: str 

1958 """ 

1959 return '{}{}'.format(cls.PREFIX_VOLUME, base_name) 

1960 

1961 @classmethod 

1962 def round_up_volume_size(cls, volume_size): 

1963 """ 

1964 Align volume size on higher multiple of BLOCK_SIZE. 

1965 :param int volume_size: The volume size to align. 

1966 :return: An aligned volume size. 

1967 :rtype: int 

1968 """ 

1969 return round_up(volume_size, cls.BLOCK_SIZE) 

1970 

1971 @classmethod 

1972 def round_down_volume_size(cls, volume_size): 

1973 """ 

1974 Align volume size on lower multiple of BLOCK_SIZE. 

1975 :param int volume_size: The volume size to align. 

1976 :return: An aligned volume size. 

1977 :rtype: int 

1978 """ 

1979 return round_down(volume_size, cls.BLOCK_SIZE) 

1980 

1981 # -------------------------------------------------------------------------- 

1982 # Private helpers. 

1983 # -------------------------------------------------------------------------- 

1984 

1985 def _create_kv_cache(self): 

1986 self._kv_cache = self._create_linstor_kv('/') 

1987 self._kv_cache_dirty = False 

1988 return self._kv_cache 

1989 

1990 def _get_kv_cache(self): 

1991 if self._kv_cache_dirty: 

1992 self._kv_cache = self._create_kv_cache() 

1993 return self._kv_cache 

1994 

1995 def _create_resource_cache(self): 

1996 self._resource_cache = self._linstor.resource_list_raise() 

1997 self._resource_cache_dirty = False 

1998 return self._resource_cache 

1999 

2000 def _get_resource_cache(self): 

2001 if self._resource_cache_dirty: 

2002 self._resource_cache = self._create_resource_cache() 

2003 return self._resource_cache 

2004 

2005 def _mark_resource_cache_as_dirty(self): 

2006 self._resource_cache_dirty = True 

2007 self._volume_info_cache_dirty = True 

2008 

2009 # -------------------------------------------------------------------------- 

2010 

2011 def _ensure_volume_exists(self, volume_uuid): 

2012 if volume_uuid not in self._volumes: 

2013 raise LinstorVolumeManagerError( 

2014 'volume `{}` doesn\'t exist'.format(volume_uuid), 

2015 LinstorVolumeManagerError.ERR_VOLUME_NOT_EXISTS 

2016 ) 

2017 

2018 def _find_best_size_candidates(self): 

2019 result = self._linstor.resource_group_qmvs(self._group_name) 

2020 error_str = self._get_error_str(result) 

2021 if error_str: 

2022 raise LinstorVolumeManagerError( 

2023 'Failed to get max volume size allowed of SR `{}`: {}'.format( 

2024 self._group_name, 

2025 error_str 

2026 ) 

2027 ) 

2028 return result[0].candidates 

2029 

2030 def _fetch_resource_names(self, ignore_deleted=True): 

2031 resource_names = set() 

2032 dfns = self._linstor.resource_dfn_list_raise().resource_definitions 

2033 for dfn in dfns: 

2034 if dfn.resource_group_name == self._group_name and ( 

2035 ignore_deleted or 

2036 linstor.consts.FLAG_DELETE not in dfn.flags 

2037 ): 

2038 resource_names.add(dfn.name) 

2039 return resource_names 

2040 

2041 def _get_volumes_info(self, volume_name=None): 

2042 all_volume_info = {} 

2043 

2044 if not self._volume_info_cache_dirty: 

2045 return self._volume_info_cache 

2046 

2047 for resource in self._get_resource_cache().resources: 

2048 if resource.name not in all_volume_info: 

2049 current = all_volume_info[resource.name] = self.VolumeInfo( 

2050 resource.name 

2051 ) 

2052 else: 

2053 current = all_volume_info[resource.name] 

2054 

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

2056 current.diskful.append(resource.node_name) 

2057 

2058 for volume in resource.volumes: 

2059 # We ignore diskless pools of the form "DfltDisklessStorPool". 

2060 if volume.storage_pool_name == self._group_name: 

2061 if volume.allocated_size < 0: 

2062 raise LinstorVolumeManagerError( 

2063 'Failed to get allocated size of `{}` on `{}`' 

2064 .format(resource.name, volume.storage_pool_name) 

2065 ) 

2066 allocated_size = volume.allocated_size 

2067 

2068 current.allocated_size = current.allocated_size and \ 

2069 max(current.allocated_size, allocated_size) or \ 

2070 allocated_size 

2071 

2072 usable_size = volume.usable_size 

2073 if usable_size > 0 and ( 

2074 usable_size < current.virtual_size or 

2075 not current.virtual_size 

2076 ): 

2077 current.virtual_size = usable_size 

2078 

2079 if current.virtual_size <= 0: 

2080 raise LinstorVolumeManagerError( 

2081 'Failed to get usable size of `{}` on `{}`' 

2082 .format(resource.name, volume.storage_pool_name) 

2083 ) 

2084 

2085 for current in all_volume_info.values(): 

2086 current.allocated_size *= 1024 

2087 current.virtual_size *= 1024 

2088 

2089 self._volume_info_cache_dirty = False 

2090 self._volume_info_cache = all_volume_info 

2091 

2092 return all_volume_info 

2093 

2094 def _get_volume_node_names_and_size(self, volume_name): 

2095 node_names = set() 

2096 size = -1 

2097 for resource in self._linstor.resource_list_raise( 

2098 filter_by_resources=[volume_name] 

2099 ).resources: 

2100 for volume in resource.volumes: 

2101 # We ignore diskless pools of the form "DfltDisklessStorPool". 

2102 if volume.storage_pool_name == self._group_name: 

2103 node_names.add(resource.node_name) 

2104 

2105 current_size = volume.usable_size 

2106 if current_size < 0: 

2107 raise LinstorVolumeManagerError( 

2108 'Failed to get usable size of `{}` on `{}`' 

2109 .format(resource.name, volume.storage_pool_name) 

2110 ) 

2111 

2112 if size < 0: 

2113 size = current_size 

2114 else: 

2115 size = min(size, current_size) 

2116 

2117 return (node_names, size * 1024) 

2118 

2119 def _compute_size(self, attr): 

2120 capacity = 0 

2121 for pool in self._get_storage_pools(force=True): 

2122 space = pool.free_space 

2123 if space: 

2124 size = getattr(space, attr) 

2125 if size < 0: 

2126 raise LinstorVolumeManagerError( 

2127 'Failed to get pool {} attr of `{}`' 

2128 .format(attr, pool.node_name) 

2129 ) 

2130 capacity += size 

2131 return capacity * 1024 

2132 

2133 def _get_node_names(self): 

2134 node_names = set() 

2135 for pool in self._get_storage_pools(): 

2136 node_names.add(pool.node_name) 

2137 return node_names 

2138 

2139 def _get_storage_pools(self, force=False): 

2140 cur_time = time.time() 

2141 elsaped_time = cur_time - self._storage_pools_time 

2142 

2143 if force or elsaped_time >= self.STORAGE_POOLS_FETCH_INTERVAL: 

2144 self._storage_pools = self._linstor.storage_pool_list_raise( 

2145 filter_by_stor_pools=[self._group_name] 

2146 ).storage_pools 

2147 self._storage_pools_time = time.time() 

2148 

2149 return self._storage_pools 

2150 

2151 def _create_volume( 

2152 self, volume_uuid, volume_name, size, place_resources 

2153 ): 

2154 size = self.round_up_volume_size(size) 

2155 self._mark_resource_cache_as_dirty() 

2156 

2157 def create_definition(): 

2158 self._check_volume_creation_errors( 

2159 self._linstor.resource_group_spawn( 

2160 rsc_grp_name=self._group_name, 

2161 rsc_dfn_name=volume_name, 

2162 vlm_sizes=['{}B'.format(size)], 

2163 definitions_only=True 

2164 ), 

2165 volume_uuid, 

2166 self._group_name 

2167 ) 

2168 self._configure_volume_peer_slots(self._linstor, volume_name) 

2169 

2170 def clean(): 

2171 try: 

2172 self._destroy_volume(volume_uuid, force=True) 

2173 except Exception as e: 

2174 self._logger( 

2175 'Unable to destroy volume {} after creation fail: {}' 

2176 .format(volume_uuid, e) 

2177 ) 

2178 

2179 def create(): 

2180 try: 

2181 create_definition() 

2182 if place_resources: 

2183 # Basic case when we use the default redundancy of the group. 

2184 self._check_volume_creation_errors( 

2185 self._linstor.resource_auto_place( 

2186 rsc_name=volume_name, 

2187 place_count=self._redundancy, 

2188 diskless_on_remaining=False 

2189 ), 

2190 volume_uuid, 

2191 self._group_name 

2192 ) 

2193 except LinstorVolumeManagerError as e: 

2194 if e.code != LinstorVolumeManagerError.ERR_VOLUME_EXISTS: 

2195 clean() 

2196 raise 

2197 except Exception: 

2198 clean() 

2199 raise 

2200 

2201 util.retry(create, maxretry=5) 

2202 

2203 def _create_volume_with_properties( 

2204 self, volume_uuid, volume_name, size, place_resources 

2205 ): 

2206 if self.check_volume_exists(volume_uuid): 

2207 raise LinstorVolumeManagerError( 

2208 'Could not create volume `{}` from SR `{}`, it already exists' 

2209 .format(volume_uuid, self._group_name) + ' in properties', 

2210 LinstorVolumeManagerError.ERR_VOLUME_EXISTS 

2211 ) 

2212 

2213 if volume_name in self._fetch_resource_names(): 

2214 raise LinstorVolumeManagerError( 

2215 'Could not create volume `{}` from SR `{}`, '.format( 

2216 volume_uuid, self._group_name 

2217 ) + 'resource of the same name already exists in LINSTOR' 

2218 ) 

2219 

2220 # I am paranoid. 

2221 volume_properties = self._get_volume_properties(volume_uuid) 

2222 if (volume_properties.get(self.PROP_NOT_EXISTS) is not None): 

2223 raise LinstorVolumeManagerError( 

2224 'Could not create volume `{}`, '.format(volume_uuid) + 

2225 'properties already exist' 

2226 ) 

2227 

2228 try: 

2229 volume_properties[self.PROP_NOT_EXISTS] = self.STATE_CREATING 

2230 volume_properties[self.PROP_VOLUME_NAME] = volume_name 

2231 

2232 self._create_volume( 

2233 volume_uuid, volume_name, size, place_resources 

2234 ) 

2235 

2236 assert volume_properties.namespace == \ 

2237 self._build_volume_namespace(volume_uuid) 

2238 return volume_properties 

2239 except LinstorVolumeManagerError as e: 

2240 # Do not destroy existing resource! 

2241 # In theory we can't get this error because we check this event 

2242 # before the `self._create_volume` case. 

2243 # It can only happen if the same volume uuid is used in the same 

2244 # call in another host. 

2245 if e.code != LinstorVolumeManagerError.ERR_VOLUME_EXISTS: 

2246 self._destroy_volume(volume_uuid, force=True) 

2247 raise 

2248 

2249 def _find_device_path(self, volume_uuid, volume_name): 

2250 current_device_path = self._request_device_path( 

2251 volume_uuid, volume_name, activate=True 

2252 ) 

2253 

2254 # We use realpath here to get the /dev/drbd<id> path instead of 

2255 # /dev/drbd/by-res/<resource_name>. 

2256 expected_device_path = self.build_device_path(volume_name) 

2257 util.wait_for_path(expected_device_path, 5) 

2258 

2259 device_realpath = os.path.realpath(expected_device_path) 

2260 if current_device_path != device_realpath: 

2261 raise LinstorVolumeManagerError( 

2262 'Invalid path, current={}, expected={} (realpath={})' 

2263 .format( 

2264 current_device_path, 

2265 expected_device_path, 

2266 device_realpath 

2267 ) 

2268 ) 

2269 return expected_device_path 

2270 

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

2272 node_name = socket.gethostname() 

2273 

2274 resource = next(filter( 

2275 lambda resource: resource.node_name == node_name and 

2276 resource.name == volume_name, 

2277 self._get_resource_cache().resources 

2278 ), None) 

2279 

2280 if not resource: 

2281 if activate: 

2282 self._mark_resource_cache_as_dirty() 

2283 self._activate_device_path( 

2284 self._linstor, node_name, volume_name 

2285 ) 

2286 return self._request_device_path(volume_uuid, volume_name) 

2287 raise LinstorVolumeManagerError( 

2288 'Empty dev path for `{}`, but definition "seems" to exist' 

2289 .format(volume_uuid) 

2290 ) 

2291 # Contains a path of the /dev/drbd<id> form. 

2292 return resource.volumes[0].device_path 

2293 

2294 def _destroy_resource(self, resource_name, force=False): 

2295 result = self._linstor.resource_dfn_delete(resource_name) 

2296 error_str = self._get_error_str(result) 

2297 if not error_str: 

2298 self._mark_resource_cache_as_dirty() 

2299 return 

2300 

2301 if not force: 

2302 self._mark_resource_cache_as_dirty() 

2303 raise LinstorVolumeManagerError( 

2304 'Could not destroy resource `{}` from SR `{}`: {}' 

2305 .format(resource_name, self._group_name, error_str) 

2306 ) 

2307 

2308 # If force is used, ensure there is no opener. 

2309 all_openers = get_all_volume_openers(resource_name, '0') 

2310 for openers in all_openers.values(): 

2311 if openers: 

2312 self._mark_resource_cache_as_dirty() 

2313 raise LinstorVolumeManagerError( 

2314 'Could not force destroy resource `{}` from SR `{}`: {} (openers=`{}`)' 

2315 .format(resource_name, self._group_name, error_str, all_openers) 

2316 ) 

2317 

2318 # Maybe the resource is blocked in primary mode. DRBD/LINSTOR issue? 

2319 resource_states = filter( 

2320 lambda resource_state: resource_state.name == resource_name, 

2321 self._get_resource_cache().resource_states 

2322 ) 

2323 

2324 # Mark only after computation of states. 

2325 self._mark_resource_cache_as_dirty() 

2326 

2327 for resource_state in resource_states: 

2328 volume_state = resource_state.volume_states[0] 

2329 if resource_state.in_use: 

2330 demote_drbd_resource(resource_state.node_name, resource_name) 

2331 break 

2332 self._destroy_resource(resource_name) 

2333 

2334 def _destroy_volume(self, volume_uuid, force=False): 

2335 volume_properties = self._get_volume_properties(volume_uuid) 

2336 try: 

2337 volume_name = volume_properties.get(self.PROP_VOLUME_NAME) 

2338 if volume_name in self._fetch_resource_names(): 

2339 self._destroy_resource(volume_name, force) 

2340 

2341 # Assume this call is atomic. 

2342 volume_properties.clear() 

2343 except Exception as e: 

2344 raise LinstorVolumeManagerError( 

2345 'Cannot destroy volume `{}`: {}'.format(volume_uuid, e) 

2346 ) 

2347 

2348 def _build_volumes(self, repair): 

2349 properties = self._kv_cache 

2350 resource_names = self._fetch_resource_names() 

2351 

2352 self._volumes = set() 

2353 

2354 updating_uuid_volumes = self._get_volumes_by_property( 

2355 self.REG_UPDATING_UUID_SRC, ignore_inexisting_volumes=False 

2356 ) 

2357 if updating_uuid_volumes and not repair: 

2358 raise LinstorVolumeManagerError( 

2359 'Cannot build LINSTOR volume list: ' 

2360 'It exists invalid "updating uuid volumes", repair is required' 

2361 ) 

2362 

2363 existing_volumes = self._get_volumes_by_property( 

2364 self.REG_NOT_EXISTS, ignore_inexisting_volumes=False 

2365 ) 

2366 for volume_uuid, not_exists in existing_volumes.items(): 

2367 properties.namespace = self._build_volume_namespace(volume_uuid) 

2368 

2369 src_uuid = properties.get(self.PROP_UPDATING_UUID_SRC) 

2370 if src_uuid: 

2371 self._logger( 

2372 'Ignoring volume during manager initialization with prop ' 

2373 ' PROP_UPDATING_UUID_SRC: {} (properties={})' 

2374 .format( 

2375 volume_uuid, 

2376 self._get_filtered_properties(properties) 

2377 ) 

2378 ) 

2379 continue 

2380 

2381 # Insert volume in list if the volume exists. Or if the volume 

2382 # is being created and a slave wants to use it (repair = False). 

2383 # 

2384 # If we are on the master and if repair is True and state is 

2385 # Creating, it's probably a bug or crash: the creation process has 

2386 # been stopped. 

2387 if not_exists == self.STATE_EXISTS or ( 

2388 not repair and not_exists == self.STATE_CREATING 

2389 ): 

2390 self._volumes.add(volume_uuid) 

2391 continue 

2392 

2393 if not repair: 

2394 self._logger( 

2395 'Ignoring bad volume during manager initialization: {} ' 

2396 '(properties={})'.format( 

2397 volume_uuid, 

2398 self._get_filtered_properties(properties) 

2399 ) 

2400 ) 

2401 continue 

2402 

2403 # Remove bad volume. 

2404 try: 

2405 self._logger( 

2406 'Removing bad volume during manager initialization: {} ' 

2407 '(properties={})'.format( 

2408 volume_uuid, 

2409 self._get_filtered_properties(properties) 

2410 ) 

2411 ) 

2412 volume_name = properties.get(self.PROP_VOLUME_NAME) 

2413 

2414 # Little optimization, don't call `self._destroy_volume`, 

2415 # we already have resource name list. 

2416 if volume_name in resource_names: 

2417 self._destroy_resource(volume_name, force=True) 

2418 

2419 # Assume this call is atomic. 

2420 properties.clear() 

2421 except Exception as e: 

2422 # Do not raise, we don't want to block user action. 

2423 self._logger( 

2424 'Cannot clean volume {}: {}'.format(volume_uuid, e) 

2425 ) 

2426 

2427 # The volume can't be removed, maybe it's still in use, 

2428 # in this case rename it with the "DELETED_" prefix. 

2429 # This prefix is mandatory if it exists a snap transaction to 

2430 # rollback because the original VDI UUID can try to be renamed 

2431 # with the UUID we are trying to delete... 

2432 if not volume_uuid.startswith('DELETED_'): 

2433 self.update_volume_uuid( 

2434 volume_uuid, 'DELETED_' + volume_uuid, force=True 

2435 ) 

2436 

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

2438 dest_namespace = self._build_volume_namespace(dest_uuid) 

2439 

2440 properties.namespace = dest_namespace 

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

2442 properties.clear() 

2443 continue 

2444 

2445 properties.namespace = self._build_volume_namespace(src_uuid) 

2446 properties.clear() 

2447 

2448 properties.namespace = dest_namespace 

2449 properties.pop(self.PROP_UPDATING_UUID_SRC) 

2450 

2451 if src_uuid in self._volumes: 

2452 self._volumes.remove(src_uuid) 

2453 self._volumes.add(dest_uuid) 

2454 

2455 def _get_sr_properties(self): 

2456 return self._create_linstor_kv(self._build_sr_namespace()) 

2457 

2458 def _get_volumes_by_property( 

2459 self, reg_prop, ignore_inexisting_volumes=True 

2460 ): 

2461 base_properties = self._get_kv_cache() 

2462 base_properties.namespace = self._build_volume_namespace() 

2463 

2464 volume_properties = {} 

2465 for volume_uuid in self._volumes: 

2466 volume_properties[volume_uuid] = '' 

2467 

2468 for key, value in base_properties.items(): 

2469 res = reg_prop.match(key) 

2470 if res: 

2471 volume_uuid = res.groups()[0] 

2472 if not ignore_inexisting_volumes or \ 

2473 volume_uuid in self._volumes: 

2474 volume_properties[volume_uuid] = value 

2475 

2476 return volume_properties 

2477 

2478 def _create_linstor_kv(self, namespace): 

2479 return linstor.KV( 

2480 self._group_name, 

2481 uri=self._linstor.controller_host(), 

2482 namespace=namespace 

2483 ) 

2484 

2485 def _get_volume_properties(self, volume_uuid): 

2486 properties = self._get_kv_cache() 

2487 properties.namespace = self._build_volume_namespace(volume_uuid) 

2488 return properties 

2489 

2490 @classmethod 

2491 def _build_sr_namespace(cls): 

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

2493 

2494 @classmethod 

2495 def _build_volume_namespace(cls, volume_uuid=None): 

2496 # Return a path to all volumes if `volume_uuid` is not given. 

2497 if volume_uuid is None: 

2498 return '/{}/'.format(cls.NAMESPACE_VOLUME) 

2499 return '/{}/{}/'.format(cls.NAMESPACE_VOLUME, volume_uuid) 

2500 

2501 @classmethod 

2502 def _get_error_str(cls, result): 

2503 return ', '.join([ 

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

2505 ]) 

2506 

2507 @classmethod 

2508 def _create_linstor_instance( 

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

2510 ): 

2511 retry = False 

2512 

2513 def connect(uri): 

2514 if not uri: 

2515 uri = get_controller_uri() 

2516 if not uri: 

2517 raise LinstorVolumeManagerError( 

2518 'Unable to find controller uri...' 

2519 ) 

2520 instance = linstor.Linstor(uri, keep_alive=True) 

2521 instance.connect() 

2522 return instance 

2523 

2524 try: 

2525 return connect(uri) 

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

2527 pass 

2528 

2529 if not keep_uri_unmodified: 

2530 uri = None 

2531 

2532 return util.retry( 

2533 lambda: connect(uri), 

2534 maxretry=attempt_count, 

2535 period=1, 

2536 exceptions=[ 

2537 linstor.errors.LinstorNetworkError, 

2538 LinstorVolumeManagerError 

2539 ] 

2540 ) 

2541 

2542 @classmethod 

2543 def _configure_volume_peer_slots(cls, lin, volume_name): 

2544 result = lin.resource_dfn_modify(volume_name, {}, peer_slots=3) 

2545 error_str = cls._get_error_str(result) 

2546 if error_str: 

2547 raise LinstorVolumeManagerError( 

2548 'Could not configure volume peer slots of {}: {}' 

2549 .format(volume_name, error_str) 

2550 ) 

2551 

2552 @classmethod 

2553 def _activate_device_path(cls, lin, node_name, volume_name): 

2554 result = lin.resource_make_available(node_name, volume_name, diskful=False) 

2555 if linstor.Linstor.all_api_responses_no_error(result): 

2556 return 

2557 errors = linstor.Linstor.filter_api_call_response_errors(result) 

2558 if len(errors) == 1 and errors[0].is_error( 

2559 linstor.consts.FAIL_EXISTS_RSC 

2560 ): 

2561 return 

2562 

2563 raise LinstorVolumeManagerError( 

2564 'Unable to activate device path of `{}` on node `{}`: {}' 

2565 .format(volume_name, node_name, ', '.join( 

2566 [str(x) for x in result])) 

2567 ) 

2568 

2569 @classmethod 

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

2571 node_name = socket.gethostname() 

2572 

2573 try: 

2574 resource = next(filter( 

2575 lambda resource: resource.node_name == node_name and 

2576 resource.name == DATABASE_VOLUME_NAME, 

2577 lin.resource_list_raise().resources 

2578 ), None) 

2579 except Exception as e: 

2580 raise LinstorVolumeManagerError( 

2581 'Unable to get resources during database creation: {}' 

2582 .format(e) 

2583 ) 

2584 

2585 if not resource: 

2586 if activate: 

2587 cls._activate_device_path( 

2588 lin, node_name, DATABASE_VOLUME_NAME 

2589 ) 

2590 return cls._request_database_path( 

2591 DATABASE_VOLUME_NAME, DATABASE_VOLUME_NAME 

2592 ) 

2593 raise LinstorVolumeManagerError( 

2594 'Empty dev path for `{}`, but definition "seems" to exist' 

2595 .format(DATABASE_PATH) 

2596 ) 

2597 # Contains a path of the /dev/drbd<id> form. 

2598 return resource.volumes[0].device_path 

2599 

2600 @classmethod 

2601 def _create_database_volume( 

2602 cls, lin, group_name, node_names, redundancy, auto_quorum 

2603 ): 

2604 try: 

2605 dfns = lin.resource_dfn_list_raise().resource_definitions 

2606 except Exception as e: 

2607 raise LinstorVolumeManagerError( 

2608 'Unable to get definitions during database creation: {}' 

2609 .format(e) 

2610 ) 

2611 

2612 if dfns: 

2613 raise LinstorVolumeManagerError( 

2614 'Could not create volume `{}` from SR `{}`, '.format( 

2615 DATABASE_VOLUME_NAME, group_name 

2616 ) + 'LINSTOR volume list must be empty.' 

2617 ) 

2618 

2619 # Workaround to use thin lvm. Without this line an error is returned: 

2620 # "Not enough available nodes" 

2621 # I don't understand why but this command protect against this bug. 

2622 try: 

2623 pools = lin.storage_pool_list_raise( 

2624 filter_by_stor_pools=[group_name] 

2625 ) 

2626 except Exception as e: 

2627 raise LinstorVolumeManagerError( 

2628 'Failed to get storage pool list before database creation: {}' 

2629 .format(e) 

2630 ) 

2631 

2632 # Ensure we have a correct list of storage pools. 

2633 nodes_with_pool = [pool.node_name for pool in pools.storage_pools] 

2634 assert nodes_with_pool # We must have at least one storage pool! 

2635 for node_name in nodes_with_pool: 

2636 assert node_name in node_names 

2637 util.SMlog('Nodes with storage pool: {}'.format(nodes_with_pool)) 

2638 

2639 # Create the database definition. 

2640 size = cls.round_up_volume_size(DATABASE_SIZE) 

2641 cls._check_volume_creation_errors(lin.resource_group_spawn( 

2642 rsc_grp_name=group_name, 

2643 rsc_dfn_name=DATABASE_VOLUME_NAME, 

2644 vlm_sizes=['{}B'.format(size)], 

2645 definitions_only=True 

2646 ), DATABASE_VOLUME_NAME, group_name) 

2647 cls._configure_volume_peer_slots(lin, DATABASE_VOLUME_NAME) 

2648 

2649 # Create real resources on the first nodes. 

2650 resources = [] 

2651 

2652 diskful_nodes = [] 

2653 diskless_nodes = [] 

2654 for node_name in node_names: 

2655 if node_name in nodes_with_pool: 

2656 diskful_nodes.append(node_name) 

2657 else: 

2658 diskless_nodes.append(node_name) 

2659 

2660 assert diskful_nodes 

2661 for node_name in diskful_nodes[:redundancy]: 

2662 util.SMlog('Create database diskful on {}'.format(node_name)) 

2663 resources.append(linstor.ResourceData( 

2664 node_name=node_name, 

2665 rsc_name=DATABASE_VOLUME_NAME, 

2666 storage_pool=group_name 

2667 )) 

2668 # Create diskless resources on the remaining set. 

2669 for node_name in diskful_nodes[redundancy:] + diskless_nodes: 

2670 util.SMlog('Create database diskless on {}'.format(node_name)) 

2671 resources.append(linstor.ResourceData( 

2672 node_name=node_name, 

2673 rsc_name=DATABASE_VOLUME_NAME, 

2674 diskless=True 

2675 )) 

2676 

2677 result = lin.resource_create(resources) 

2678 error_str = cls._get_error_str(result) 

2679 if error_str: 

2680 raise LinstorVolumeManagerError( 

2681 'Could not create database volume from SR `{}`: {}'.format( 

2682 group_name, error_str 

2683 ) 

2684 ) 

2685 

2686 # We must modify the quorum. Otherwise we can't use correctly the 

2687 # drbd-reactor daemon. 

2688 if auto_quorum: 

2689 result = lin.resource_dfn_modify(DATABASE_VOLUME_NAME, { 

2690 'DrbdOptions/auto-quorum': 'disabled', 

2691 'DrbdOptions/Resource/quorum': 'majority' 

2692 }) 

2693 error_str = cls._get_error_str(result) 

2694 if error_str: 

2695 raise LinstorVolumeManagerError( 

2696 'Could not activate quorum on database volume: {}' 

2697 .format(error_str) 

2698 ) 

2699 

2700 # Create database and ensure path exists locally and 

2701 # on replicated devices. 

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

2703 

2704 # Ensure diskless paths exist on other hosts. Otherwise PBDs can't be 

2705 # plugged. 

2706 for node_name in node_names: 

2707 cls._activate_device_path(lin, node_name, DATABASE_VOLUME_NAME) 

2708 

2709 # We use realpath here to get the /dev/drbd<id> path instead of 

2710 # /dev/drbd/by-res/<resource_name>. 

2711 expected_device_path = cls.build_device_path(DATABASE_VOLUME_NAME) 

2712 util.wait_for_path(expected_device_path, 5) 

2713 

2714 device_realpath = os.path.realpath(expected_device_path) 

2715 if current_device_path != device_realpath: 

2716 raise LinstorVolumeManagerError( 

2717 'Invalid path, current={}, expected={} (realpath={})' 

2718 .format( 

2719 current_device_path, 

2720 expected_device_path, 

2721 device_realpath 

2722 ) 

2723 ) 

2724 

2725 try: 

2726 util.retry( 

2727 lambda: util.pread2([DATABASE_MKFS, expected_device_path]), 

2728 maxretry=5 

2729 ) 

2730 except Exception as e: 

2731 raise LinstorVolumeManagerError( 

2732 'Failed to execute {} on database volume: {}' 

2733 .format(DATABASE_MKFS, e) 

2734 ) 

2735 

2736 return expected_device_path 

2737 

2738 @classmethod 

2739 def _destroy_database_volume(cls, lin, group_name): 

2740 error_str = cls._get_error_str( 

2741 lin.resource_dfn_delete(DATABASE_VOLUME_NAME) 

2742 ) 

2743 if error_str: 

2744 raise LinstorVolumeManagerError( 

2745 'Could not destroy resource `{}` from SR `{}`: {}' 

2746 .format(DATABASE_VOLUME_NAME, group_name, error_str) 

2747 ) 

2748 

2749 @classmethod 

2750 def _mount_database_volume(cls, volume_path, mount=True, force=False): 

2751 try: 

2752 # 1. Create a backup config folder. 

2753 database_not_empty = bool(os.listdir(DATABASE_PATH)) 

2754 backup_path = cls._create_database_backup_path() 

2755 

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

2757 if database_not_empty: 

2758 cls._move_files(DATABASE_PATH, backup_path) 

2759 

2760 cls._mount_volume(volume_path, DATABASE_PATH, mount) 

2761 

2762 if database_not_empty: 

2763 cls._move_files(backup_path, DATABASE_PATH, force) 

2764 

2765 # 3. Remove useless backup directory. 

2766 try: 

2767 os.rmdir(backup_path) 

2768 except Exception as e: 

2769 raise LinstorVolumeManagerError( 

2770 'Failed to remove backup path {} of LINSTOR config: {}' 

2771 .format(backup_path, e) 

2772 ) 

2773 except Exception as e: 

2774 def force_exec(fn): 

2775 try: 

2776 fn() 

2777 except Exception: 

2778 pass 

2779 

2780 if mount == cls._is_mounted(DATABASE_PATH): 

2781 force_exec(lambda: cls._move_files( 

2782 DATABASE_PATH, backup_path 

2783 )) 

2784 force_exec(lambda: cls._mount_volume( 

2785 volume_path, DATABASE_PATH, not mount 

2786 )) 

2787 

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

2789 force_exec(lambda: cls._move_files( 

2790 backup_path, DATABASE_PATH 

2791 )) 

2792 

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

2794 raise e 

2795 

2796 @classmethod 

2797 def _force_destroy_database_volume(cls, lin, group_name): 

2798 try: 

2799 cls._destroy_database_volume(lin, group_name) 

2800 except Exception: 

2801 pass 

2802 

2803 @classmethod 

2804 def _destroy_storage_pool(cls, lin, group_name, node_name): 

2805 def destroy(): 

2806 result = lin.storage_pool_delete(node_name, group_name) 

2807 errors = cls._filter_errors(result) 

2808 if cls._check_errors(errors, [ 

2809 linstor.consts.FAIL_NOT_FOUND_STOR_POOL, 

2810 linstor.consts.FAIL_NOT_FOUND_STOR_POOL_DFN 

2811 ]): 

2812 return 

2813 

2814 if errors: 

2815 raise LinstorVolumeManagerError( 

2816 'Failed to destroy SP `{}` on node `{}`: {}'.format( 

2817 group_name, 

2818 node_name, 

2819 cls._get_error_str(errors) 

2820 ) 

2821 ) 

2822 

2823 # We must retry to avoid errors like: 

2824 # "can not be deleted as volumes / snapshot-volumes are still using it" 

2825 # after LINSTOR database volume destruction. 

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

2827 

2828 @classmethod 

2829 def _destroy_resource_group(cls, lin, group_name): 

2830 def destroy(): 

2831 result = lin.resource_group_delete(group_name) 

2832 errors = cls._filter_errors(result) 

2833 if cls._check_errors(errors, [ 

2834 linstor.consts.FAIL_NOT_FOUND_RSC_GRP 

2835 ]): 

2836 return 

2837 

2838 if errors: 

2839 raise LinstorVolumeManagerError( 

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

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

2842 ) 

2843 

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

2845 

2846 @classmethod 

2847 def _build_group_name(cls, base_name): 

2848 # If thin provisioning is used we have a path like this: 

2849 # `VG/LV`. "/" is not accepted by LINSTOR. 

2850 return '{}{}'.format(cls.PREFIX_SR, base_name.replace('/', '_')) 

2851 

2852 @classmethod 

2853 def _check_volume_creation_errors(cls, result, volume_uuid, group_name): 

2854 errors = cls._filter_errors(result) 

2855 if cls._check_errors(errors, [ 

2856 linstor.consts.FAIL_EXISTS_RSC, linstor.consts.FAIL_EXISTS_RSC_DFN 

2857 ]): 

2858 raise LinstorVolumeManagerError( 

2859 'Failed to create volume `{}` from SR `{}`, it already exists' 

2860 .format(volume_uuid, group_name), 

2861 LinstorVolumeManagerError.ERR_VOLUME_EXISTS 

2862 ) 

2863 

2864 if errors: 

2865 raise LinstorVolumeManagerError( 

2866 'Failed to create volume `{}` from SR `{}`: {}'.format( 

2867 volume_uuid, 

2868 group_name, 

2869 cls._get_error_str(errors) 

2870 ) 

2871 ) 

2872 

2873 @classmethod 

2874 def _move_files(cls, src_dir, dest_dir, force=False): 

2875 def listdir(dir): 

2876 ignored = ['lost+found'] 

2877 return [file for file in os.listdir(dir) if file not in ignored] 

2878 

2879 try: 

2880 if not force: 

2881 files = listdir(dest_dir) 

2882 if files: 

2883 raise LinstorVolumeManagerError( 

2884 'Cannot move files from {} to {} because destination ' 

2885 'contains: {}'.format(src_dir, dest_dir, files) 

2886 ) 

2887 except LinstorVolumeManagerError: 

2888 raise 

2889 except Exception as e: 

2890 raise LinstorVolumeManagerError( 

2891 'Cannot list dir {}: {}'.format(dest_dir, e) 

2892 ) 

2893 

2894 try: 

2895 for file in listdir(src_dir): 

2896 try: 

2897 dest_file = os.path.join(dest_dir, file) 

2898 if not force and os.path.exists(dest_file): 

2899 raise LinstorVolumeManagerError( 

2900 'Cannot move {} because it already exists in the ' 

2901 'destination'.format(file) 

2902 ) 

2903 shutil.move(os.path.join(src_dir, file), dest_file) 

2904 except LinstorVolumeManagerError: 

2905 raise 

2906 except Exception as e: 

2907 raise LinstorVolumeManagerError( 

2908 'Cannot move {}: {}'.format(file, e) 

2909 ) 

2910 except Exception as e: 

2911 if not force: 

2912 try: 

2913 cls._move_files(dest_dir, src_dir, force=True) 

2914 except Exception: 

2915 pass 

2916 

2917 raise LinstorVolumeManagerError( 

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

2919 src_dir, dest_dir, e 

2920 ) 

2921 ) 

2922 

2923 @staticmethod 

2924 def _create_database_backup_path(): 

2925 path = DATABASE_PATH + '-' + str(uuid.uuid4()) 

2926 try: 

2927 os.mkdir(path) 

2928 return path 

2929 except Exception as e: 

2930 raise LinstorVolumeManagerError( 

2931 'Failed to create backup path {} of LINSTOR config: {}' 

2932 .format(path, e) 

2933 ) 

2934 

2935 @staticmethod 

2936 def _get_filtered_properties(properties): 

2937 return dict(properties.items()) 

2938 

2939 @staticmethod 

2940 def _filter_errors(result): 

2941 return [ 

2942 err for err in result 

2943 if hasattr(err, 'is_error') and err.is_error() 

2944 ] 

2945 

2946 @staticmethod 

2947 def _check_errors(result, codes): 

2948 for err in result: 

2949 for code in codes: 

2950 if err.is_error(code): 

2951 return True 

2952 return False 

2953 

2954 @classmethod 

2955 def _controller_is_running(cls): 

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

2957 

2958 @classmethod 

2959 def _start_controller(cls, start=True): 

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

2961 

2962 @staticmethod 

2963 def _start_service(name, start=True): 

2964 action = 'start' if start else 'stop' 

2965 (ret, out, err) = util.doexec([ 

2966 'systemctl', action, name 

2967 ]) 

2968 if ret != 0: 

2969 raise LinstorVolumeManagerError( 

2970 'Failed to {} {}: {} {}' 

2971 .format(action, name, out, err) 

2972 ) 

2973 

2974 @staticmethod 

2975 def _service_is_running(name): 

2976 (ret, out, err) = util.doexec([ 

2977 'systemctl', 'is-active', '--quiet', name 

2978 ]) 

2979 return not ret 

2980 

2981 @staticmethod 

2982 def _is_mounted(mountpoint): 

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

2984 return ret == 0 

2985 

2986 @classmethod 

2987 def _mount_volume(cls, volume_path, mountpoint, mount=True): 

2988 if mount: 

2989 try: 

2990 util.pread(['mount', volume_path, mountpoint]) 

2991 except Exception as e: 

2992 raise LinstorVolumeManagerError( 

2993 'Failed to mount volume {} on {}: {}' 

2994 .format(volume_path, mountpoint, e) 

2995 ) 

2996 else: 

2997 try: 

2998 if cls._is_mounted(mountpoint): 

2999 util.pread(['umount', mountpoint]) 

3000 except Exception as e: 

3001 raise LinstorVolumeManagerError( 

3002 'Failed to umount volume {} on {}: {}' 

3003 .format(volume_path, mountpoint, e) 

3004 ) 

3005 

3006 

3007# ============================================================================== 

3008 

3009# Check if a path is a DRBD resource and log the process name/pid 

3010# that opened it. 

3011def log_drbd_openers(path): 

3012 # Ignore if it's not a symlink to DRBD resource. 

3013 if not path.startswith(DRBD_BY_RES_PATH): 

3014 return 

3015 

3016 # Compute resource name. 

3017 res_name_end = path.find('/', len(DRBD_BY_RES_PATH)) 

3018 if res_name_end == -1: 

3019 return 

3020 res_name = path[len(DRBD_BY_RES_PATH):res_name_end] 

3021 

3022 volume_end = path.rfind('/') 

3023 if volume_end == res_name_end: 

3024 return 

3025 volume = path[volume_end + 1:] 

3026 

3027 try: 

3028 # Ensure path is a DRBD. 

3029 drbd_path = os.path.realpath(path) 

3030 stats = os.stat(drbd_path) 

3031 if not stat.S_ISBLK(stats.st_mode) or os.major(stats.st_rdev) != 147: 

3032 return 

3033 

3034 # Find where the device is open. 

3035 (ret, stdout, stderr) = util.doexec(['drbdadm', 'status', res_name]) 

3036 if ret != 0: 

3037 util.SMlog('Failed to execute `drbdadm status` on `{}`: {}'.format( 

3038 res_name, stderr 

3039 )) 

3040 return 

3041 

3042 # Is it a local device? 

3043 if stdout.startswith('{} role:Primary'.format(res_name)): 

3044 util.SMlog( 

3045 'DRBD resource `{}` is open on local host: {}' 

3046 .format(path, get_local_volume_openers(res_name, volume)) 

3047 ) 

3048 return 

3049 

3050 # Is it a remote device? 

3051 util.SMlog( 

3052 'DRBD resource `{}` is open on hosts: {}' 

3053 .format(path, get_all_volume_openers(res_name, volume)) 

3054 ) 

3055 except Exception as e: 

3056 util.SMlog( 

3057 'Got exception while trying to determine where DRBD resource ' + 

3058 '`{}` is open: {}'.format(path, e) 

3059 )