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 

17from sm_typing import Any, Optional, override, Literal 

18 

19from constants import CBTLOG_TAG, LINSTOR_AUTO_BACKUP_DELAY 

20 

21try: 

22 from linstorcowutil import LinstorCowUtil, MultiLinstorCowUtil 

23 from linstorjournaler import LinstorJournaler 

24 from linstorvolumemanager import get_controller_uri 

25 from linstorvolumemanager import get_controller_node_name 

26 from linstorvolumemanager import LinstorVolumeManager 

27 from linstorvolumemanager import LinstorVolumeManagerError 

28 from linstorvolumemanager import DATABASE_VOLUME_NAME 

29 from linstorvolumemanager import PERSISTENT_PREFIX 

30 

31 LINSTOR_AVAILABLE = True 

32except ImportError: 

33 PERSISTENT_PREFIX = 'unknown' 

34 

35 LINSTOR_AVAILABLE = False 

36 

37import blktap2 

38import cleanup 

39import errno 

40import functools 

41import lock 

42import lvutil 

43import os 

44import re 

45import scsiutil 

46import signal 

47import socket 

48import SR 

49import SRCommand 

50import subprocess 

51import sys 

52import time 

53import traceback 

54import util 

55import VDI 

56import xml.etree.ElementTree as xml_parser 

57import xmlrpc.client 

58import xs_errors 

59 

60from cowutil import CowUtil, ImageFormat, getImageStringFromVdiType 

61from srmetadata import \ 

62 NAME_LABEL_TAG, NAME_DESCRIPTION_TAG, IS_A_SNAPSHOT_TAG, SNAPSHOT_OF_TAG, \ 

63 TYPE_TAG, VDI_TYPE_TAG, READ_ONLY_TAG, SNAPSHOT_TIME_TAG, \ 

64 METADATA_OF_POOL_TAG 

65from vditype import VdiType 

66 

67HIDDEN_TAG = 'hidden' 

68 

69XHA_CONFIG_PATH = '/etc/xensource/xhad.conf' 

70 

71FORK_LOG_DAEMON = '/opt/xensource/libexec/fork-log-daemon' 

72 

73# This flag can be disabled to debug the DRBD layer. 

74# When this config var is False, the HA can only be used under 

75# specific conditions: 

76# - Only one heartbeat diskless VDI is present in the pool. 

77# - The other hearbeat volumes must be diskful and limited to a maximum of 3. 

78USE_HTTP_NBD_SERVERS = True 

79 

80# Useful flag to trace calls using cProfile. 

81TRACE_PERFS = False 

82 

83# Enable/Disable COW key hash support. 

84USE_KEY_HASH = False 

85 

86# Special volumes. 

87HA_VOLUME_NAME = PERSISTENT_PREFIX + 'ha-statefile' 

88REDO_LOG_VOLUME_NAME = PERSISTENT_PREFIX + 'redo-log' 

89 

90# ============================================================================== 

91 

92# TODO: Supports 'VDI_INTRODUCE', 'VDI_RESET_ON_BOOT/2', 'SR_TRIM', 

93# 'VDI_CONFIG_CBT', 'SR_PROBE' 

94 

95CAPABILITIES = [ 

96 'ATOMIC_PAUSE', 

97 'SR_UPDATE', 

98 'VDI_CREATE', 

99 'VDI_DELETE', 

100 'VDI_UPDATE', 

101 'VDI_ATTACH', 

102 'VDI_DETACH', 

103 'VDI_ACTIVATE', 

104 'VDI_DEACTIVATE', 

105 'VDI_CLONE', 

106 'VDI_MIRROR', 

107 'VDI_RESIZE', 

108 'VDI_SNAPSHOT', 

109 'VDI_GENERATE_CONFIG' 

110] 

111 

112CONFIGURATION = [ 

113 ['group-name', 'LVM group name'], 

114 ['redundancy', 'replication count'], 

115 ['provisioning', '"thin" or "thick" are accepted (optional, defaults to thin)'], 

116 ['monitor-db-quorum', 'disable controller when only one host is online (optional, defaults to true)'] 

117] 

118 

119DRIVER_INFO = { 

120 'name': 'LINSTOR resources on XCP-ng', 

121 'description': 'SR plugin which uses Linstor to manage VDIs', 

122 'vendor': 'Vates', 

123 'copyright': '(C) 2020 Vates', 

124 'driver_version': '1.0', 

125 'required_api_version': '1.0', 

126 'capabilities': CAPABILITIES, 

127 'configuration': CONFIGURATION 

128} 

129 

130DRIVER_CONFIG = {'ATTACH_FROM_CONFIG_WITH_TAPDISK': False} 

131 

132OPS_EXCLUSIVE = [ 

133 'sr_create', 'sr_delete', 'sr_attach', 'sr_detach', 'sr_scan', 

134 'sr_update', 'sr_probe', 'vdi_init', 'vdi_create', 'vdi_delete', 

135 'vdi_attach', 'vdi_detach', 'vdi_clone', 'vdi_snapshot', 

136] 

137 

138# ============================================================================== 

139# Misc helpers used by LinstorSR and linstor-thin plugin. 

140# ============================================================================== 

141 

142 

143def attach_thin(session, journaler, linstor, sr_uuid, vdi_uuid): 

144 volume_metadata = linstor.get_volume_metadata(vdi_uuid) 

145 vdi_type = volume_metadata.get(VDI_TYPE_TAG) 

146 if not VdiType.isCowImage(vdi_type): 

147 return 

148 

149 device_path = linstor.get_device_path(vdi_uuid) 

150 

151 linstorcowutil = LinstorCowUtil(session, linstor, vdi_type) 

152 

153 # If the virtual COW size is lower than the LINSTOR volume size, 

154 # there is nothing to do. 

155 cow_size = linstorcowutil.compute_volume_size( 

156 linstorcowutil.get_size_virt(vdi_uuid) 

157 ) 

158 

159 volume_info = linstor.get_volume_info(vdi_uuid) 

160 volume_size = volume_info.virtual_size 

161 

162 if cow_size > volume_size: 

163 linstorcowutil.inflate(journaler, vdi_uuid, device_path, cow_size, volume_size) 

164 

165 

166def detach_thin_impl(session, linstor, sr_uuid, vdi_uuid): 

167 volume_metadata = linstor.get_volume_metadata(vdi_uuid) 

168 vdi_type = volume_metadata.get(VDI_TYPE_TAG) 

169 if not VdiType.isCowImage(vdi_type): 

170 return 

171 

172 def check_vbd_count(): 

173 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid) 

174 vbds = session.xenapi.VBD.get_all_records_where( 

175 'field "VDI" = "{}"'.format(vdi_ref) 

176 ) 

177 

178 num_plugged = 0 

179 for vbd_rec in vbds.values(): 

180 if vbd_rec['currently_attached']: 

181 num_plugged += 1 

182 if num_plugged > 1: 

183 raise xs_errors.XenError( 

184 'VDIUnavailable', 

185 opterr='Cannot deflate VDI {}, already used by ' 

186 'at least 2 VBDs'.format(vdi_uuid) 

187 ) 

188 

189 # We can have multiple VBDs attached to a VDI during a VM-template clone. 

190 # So we use a timeout to ensure that we can detach the volume properly. 

191 util.retry(check_vbd_count, maxretry=10, period=1) 

192 

193 device_path = linstor.get_device_path(vdi_uuid) 

194 linstorcowutil = LinstorCowUtil(session, linstor, vdi_type) 

195 new_volume_size = LinstorVolumeManager.round_up_volume_size( 

196 linstorcowutil.get_size_phys(vdi_uuid) 

197 ) 

198 

199 volume_info = linstor.get_volume_info(vdi_uuid) 

200 old_volume_size = volume_info.virtual_size 

201 linstorcowutil.deflate(device_path, new_volume_size, old_volume_size) 

202 

203 

204def detach_thin(session, linstor, sr_uuid, vdi_uuid): 

205 # This function must always return without errors. 

206 # Otherwise it could cause errors in the XAPI regarding the state of the VDI. 

207 # It's why we use this `try` block. 

208 try: 

209 detach_thin_impl(session, linstor, sr_uuid, vdi_uuid) 

210 except Exception as e: 

211 util.SMlog('Failed to detach properly VDI {}: {}'.format(vdi_uuid, e)) 

212 

213 

214def get_ips_from_xha_config_file(): 

215 ips = dict() 

216 host_id = None 

217 try: 

218 # Ensure there is no dirty read problem. 

219 # For example if the HA is reloaded. 

220 tree = util.retry( 

221 lambda: xml_parser.parse(XHA_CONFIG_PATH), 

222 maxretry=10, 

223 period=1 

224 ) 

225 except: 

226 return (None, ips) 

227 

228 def parse_host_nodes(ips, node): 

229 current_id = None 

230 current_ip = None 

231 

232 for sub_node in node: 

233 if sub_node.tag == 'IPaddress': 

234 current_ip = sub_node.text 

235 elif sub_node.tag == 'HostID': 

236 current_id = sub_node.text 

237 else: 

238 continue 

239 

240 if current_id and current_ip: 

241 ips[current_id] = current_ip 

242 return 

243 util.SMlog('Ill-formed XHA file, missing IPaddress or/and HostID') 

244 

245 def parse_common_config(ips, node): 

246 for sub_node in node: 

247 if sub_node.tag == 'host': 

248 parse_host_nodes(ips, sub_node) 

249 

250 def parse_local_config(ips, node): 

251 for sub_node in node: 

252 if sub_node.tag == 'localhost': 

253 for host_node in sub_node: 

254 if host_node.tag == 'HostID': 

255 return host_node.text 

256 

257 for node in tree.getroot(): 

258 if node.tag == 'common-config': 

259 parse_common_config(ips, node) 

260 elif node.tag == 'local-config': 

261 host_id = parse_local_config(ips, node) 

262 else: 

263 continue 

264 

265 if ips and host_id: 

266 break 

267 

268 return (host_id and ips.get(host_id), ips) 

269 

270 

271def activate_lvm_group(group_name): 

272 path = group_name.split('/') 

273 assert path and len(path) <= 2 

274 try: 

275 lvutil.setActiveVG(path[0], True) 

276 except Exception as e: 

277 util.SMlog('Cannot active VG `{}`: {}'.format(path[0], e)) 

278 

279# ============================================================================== 

280 

281# Usage example: 

282# xe sr-create type=linstor name-label=linstor-sr 

283# host-uuid=d2deba7a-c5ad-4de1-9a20-5c8df3343e93 

284# device-config:group-name=vg_loop device-config:redundancy=2 

285 

286 

287class LinstorSR(SR.SR): 

288 DRIVER_TYPE = 'linstor' 

289 

290 PROVISIONING_TYPES = ['thin', 'thick'] 

291 PROVISIONING_DEFAULT = 'thin' 

292 

293 MANAGER_PLUGIN = 'linstor-manager' 

294 

295 INIT_STATUS_NOT_SET = 0 

296 INIT_STATUS_IN_PROGRESS = 1 

297 INIT_STATUS_OK = 2 

298 INIT_STATUS_FAIL = 3 

299 

300 # -------------------------------------------------------------------------- 

301 # SR methods. 

302 # -------------------------------------------------------------------------- 

303 

304 _linstor: Optional["LinstorVolumeManager"] = None 

305 

306 @override 

307 @staticmethod 

308 def handles(type) -> bool: 

309 return type == LinstorSR.DRIVER_TYPE 

310 

311 def __init__(self, srcmd, sr_uuid): 

312 SR.SR.__init__(self, srcmd, sr_uuid) 

313 self._init_image_formats( 

314 preferred_image_formats=[ImageFormat.VHD], 

315 supported_image_formats=[ImageFormat.RAW, ImageFormat.VHD] 

316 ) 

317 

318 @override 

319 def load(self, sr_uuid) -> None: 

320 if not LINSTOR_AVAILABLE: 

321 raise util.SMException( 

322 'Can\'t load LinstorSR: LINSTOR libraries are missing' 

323 ) 

324 

325 # Check parameters. 

326 if 'group-name' not in self.dconf or not self.dconf['group-name']: 

327 raise xs_errors.XenError('LinstorConfigGroupNameMissing') 

328 if 'redundancy' not in self.dconf or not self.dconf['redundancy']: 

329 raise xs_errors.XenError('LinstorConfigRedundancyMissing') 

330 

331 self.driver_config = DRIVER_CONFIG 

332 

333 # Check provisioning config. 

334 provisioning = self.dconf.get('provisioning') 

335 if provisioning: 

336 if provisioning in self.PROVISIONING_TYPES: 

337 self._provisioning = provisioning 

338 else: 

339 raise xs_errors.XenError( 

340 'InvalidArg', 

341 opterr='Provisioning parameter must be one of {}'.format( 

342 self.PROVISIONING_TYPES 

343 ) 

344 ) 

345 else: 

346 self._provisioning = self.PROVISIONING_DEFAULT 

347 

348 monitor_db_quorum = self.dconf.get('monitor-db-quorum') 

349 self._monitor_db_quorum = (monitor_db_quorum is None) or \ 

350 util.strtobool(monitor_db_quorum) 

351 

352 # Note: We don't have access to the session field if the 

353 # 'vdi_attach_from_config' command is executed. 

354 self._has_session = self.sr_ref and self.session is not None 

355 if self._has_session: 

356 self.sm_config = self.session.xenapi.SR.get_sm_config(self.sr_ref) 

357 else: 

358 self.sm_config = self.srcmd.params.get('sr_sm_config') or {} 

359 

360 provisioning = self.sm_config.get('provisioning') 

361 if provisioning in self.PROVISIONING_TYPES: 

362 self._provisioning = provisioning 

363 

364 # Define properties for SR parent class. 

365 self.ops_exclusive = OPS_EXCLUSIVE 

366 self.path = LinstorVolumeManager.DEV_ROOT_PATH 

367 self.lock = lock.Lock(lock.LOCK_TYPE_SR, self.uuid) 

368 self.sr_vditype = SR.DEFAULT_TAP 

369 

370 if self.cmd == 'sr_create': 

371 self._redundancy = int(self.dconf['redundancy']) or 1 

372 self._linstor = None # Ensure that LINSTOR attribute exists. 

373 self._journaler = None 

374 

375 # Used to handle reconnect calls on LINSTOR object attached to the SR. 

376 class LinstorProxy: 

377 def __init__(self, sr: LinstorSR) -> None: 

378 self.sr = sr 

379 

380 def __getattr__(self, attr: str) -> Any: 

381 assert self.sr, "Cannot use `LinstorProxy` without valid `LinstorVolumeManager` instance" 

382 return getattr(self.sr._linstor, attr) 

383 

384 self._linstor_proxy = LinstorProxy(self) 

385 

386 self._group_name = self.dconf['group-name'] 

387 

388 self._vdi_shared_time = 0 

389 

390 self._init_status = self.INIT_STATUS_NOT_SET 

391 

392 self._vdis_loaded = False 

393 self._all_volume_info_cache = None 

394 self._all_volume_metadata_cache = None 

395 self._multi_cowutil = None 

396 

397 # To remove in python 3.10. 

398 # Use directly @staticmethod instead. 

399 @util.conditional_decorator(staticmethod, sys.version_info >= (3, 10, 0)) 

400 def _locked_load(method): 

401 def wrapped_method(self, *args, **kwargs): 

402 self._init_status = self.INIT_STATUS_OK 

403 return method(self, *args, **kwargs) 

404 

405 def load(self, *args, **kwargs): 

406 # Activate all LVMs to make drbd-reactor happy. 

407 if self.srcmd.cmd in ('sr_attach', 'vdi_attach_from_config'): 

408 activate_lvm_group(self._group_name) 

409 

410 if not self._has_session: 

411 if self.srcmd.cmd in ( 

412 'vdi_attach_from_config', 

413 'vdi_detach_from_config', 

414 # When on-slave (is_open) is executed we have an 

415 # empty command. 

416 None 

417 ): 

418 def create_linstor(uri, attempt_count=30): 

419 self._linstor = LinstorVolumeManager( 

420 uri, 

421 self._group_name, 

422 logger=util.SMlog, 

423 attempt_count=attempt_count 

424 ) 

425 

426 controller_uri = get_controller_uri() 

427 if controller_uri: 

428 create_linstor(controller_uri) 

429 else: 

430 def connect(): 

431 # We must have a valid LINSTOR instance here without using 

432 # the XAPI. Fallback with the HA config file. 

433 for ip in get_ips_from_xha_config_file()[1].values(): 

434 controller_uri = 'linstor://' + ip 

435 try: 

436 util.SMlog('Connecting from config to LINSTOR controller using: {}'.format(ip)) 

437 create_linstor(controller_uri, attempt_count=0) 

438 return controller_uri 

439 except: 

440 pass 

441 

442 controller_uri = util.retry(connect, maxretry=30, period=1) 

443 if not controller_uri: 

444 raise xs_errors.XenError( 

445 'SRUnavailable', 

446 opterr='No valid controller URI to attach/detach from config' 

447 ) 

448 

449 return wrapped_method(self, *args, **kwargs) 

450 

451 if not self.is_master(): 

452 if self.cmd in [ 

453 'sr_create', 'sr_delete', 'sr_update', 'sr_probe', 

454 'sr_scan', 'vdi_create', 'vdi_delete', 'vdi_resize', 

455 'vdi_snapshot', 'vdi_clone' 

456 ]: 

457 util.SMlog('{} blocked for non-master'.format(self.cmd)) 

458 raise xs_errors.XenError('LinstorMaster') 

459 

460 # Because the LINSTOR KV objects cache all values, we must lock 

461 # the VDI before the LinstorJournaler/LinstorVolumeManager 

462 # instantiation and before any action on the master to avoid a 

463 # bad read. The lock is also necessary to avoid strange 

464 # behaviors if the GC is executed during an action on a slave. 

465 if self.cmd.startswith('vdi_'): 

466 self._shared_lock_vdi(self.srcmd.params['vdi_uuid']) 

467 self._vdi_shared_time = time.time() 

468 

469 if self.srcmd.cmd != 'sr_create' and self.srcmd.cmd != 'sr_detach': 

470 try: 

471 self._reconnect() 

472 except Exception as e: 

473 raise xs_errors.XenError('SRUnavailable', opterr=str(e)) 

474 

475 if self._linstor: 

476 try: 

477 hosts = self._linstor.disconnected_hosts 

478 except Exception as e: 

479 raise xs_errors.XenError('SRUnavailable', opterr=str(e)) 

480 

481 if hosts: 

482 util.SMlog('Failed to join node(s): {}'.format(hosts)) 

483 

484 # Ensure we use a non-locked volume when cowutil is called. 

485 if ( 

486 self.is_master() and self.cmd.startswith('vdi_') and 

487 self.cmd != 'vdi_create' 

488 ): 

489 self._linstor.ensure_volume_is_not_locked( 

490 self.srcmd.params['vdi_uuid'] 

491 ) 

492 

493 try: 

494 # If the command is a SR scan command on the master, 

495 # we must load all VDIs and clean journal transactions. 

496 # We must load the VDIs in the snapshot case too only if 

497 # there is at least one entry in the journal. 

498 # 

499 # If the command is a SR command we want at least to remove 

500 # resourceless volumes. 

501 if self.is_master() and self.cmd not in [ 

502 'vdi_attach', 'vdi_detach', 

503 'vdi_activate', 'vdi_deactivate', 

504 'vdi_epoch_begin', 'vdi_epoch_end', 

505 'vdi_update', 'vdi_destroy', 

506 'nop' # Deal with `SR.from_uuid` that emits a fake `nop` command. 

507 ]: 

508 journaler = self._get_journaler() 

509 load_vdis = ( 

510 self.cmd == 'sr_scan' or 

511 self.cmd == 'sr_attach' 

512 ) or len( 

513 journaler.get_all(LinstorJournaler.INFLATE) 

514 ) or len( 

515 journaler.get_all(LinstorJournaler.CLONE) 

516 ) 

517 

518 if load_vdis: 

519 self._load_vdis() 

520 

521 self._linstor.remove_resourceless_volumes() 

522 

523 self._synchronize_metadata() 

524 except Exception as e: 

525 if self.cmd == 'sr_scan' or self.cmd == 'sr_attach': 

526 # Always raise, we don't want to remove VDIs 

527 # from the XAPI database otherwise. 

528 raise e 

529 util.SMlog( 

530 'Ignoring exception in LinstorSR.load: {}'.format(e) 

531 ) 

532 util.SMlog(traceback.format_exc()) 

533 

534 return wrapped_method(self, *args, **kwargs) 

535 

536 @functools.wraps(wrapped_method) 

537 def wrap(self, *args, **kwargs): 

538 if self._init_status in \ 

539 (self.INIT_STATUS_OK, self.INIT_STATUS_IN_PROGRESS): 

540 return wrapped_method(self, *args, **kwargs) 

541 if self._init_status == self.INIT_STATUS_FAIL: 

542 util.SMlog( 

543 'Can\'t call method {} because initialization failed' 

544 .format(method) 

545 ) 

546 else: 

547 try: 

548 self._init_status = self.INIT_STATUS_IN_PROGRESS 

549 return load(self, *args, **kwargs) 

550 except Exception: 

551 if self._init_status != self.INIT_STATUS_OK: 

552 self._init_status = self.INIT_STATUS_FAIL 

553 raise 

554 

555 return wrap 

556 

557 @override 

558 def cleanup(self) -> None: 

559 if self._vdi_shared_time: 

560 self._shared_lock_vdi(self.srcmd.params['vdi_uuid'], locked=False) 

561 

562 @override 

563 @_locked_load 

564 def create(self, uuid, size) -> None: 

565 util.SMlog('LinstorSR.create for {}'.format(self.uuid)) 

566 

567 host_adresses = util.get_host_addresses(self.session) 

568 if self._redundancy > len(host_adresses): 

569 raise xs_errors.XenError( 

570 'LinstorSRCreate', 

571 opterr='Redundancy greater than host count' 

572 ) 

573 

574 srs = util.get_linstor_srs_uuid(self.session) 

575 try: 

576 srs.pop(self.uuid) 

577 except KeyError: 

578 # We cannot guarantee that the new SR key will be there even it should be the case. 

579 pass 

580 

581 pbd_ref = util.find_pbd_ref_from_dconf_value( 

582 self.session, srs, "group-name", self._group_name, LinstorVolumeManager.build_group_name 

583 ) 

584 if pbd_ref: 

585 raise xs_errors.XenError( 

586 'LinstorSRCreate', 

587 opterr=f"group name must be unique, already used by PBD {self.session.xenapi.PBD.get_uuid(pbd_ref)}" 

588 ) 

589 

590 if srs: 

591 raise xs_errors.XenError( 

592 'LinstorSRCreate', 

593 opterr='LINSTOR SR must be unique in a pool' 

594 ) 

595 

596 online_hosts = util.get_enabled_hosts(self.session) 

597 if len(online_hosts) < len(host_adresses): 

598 raise xs_errors.XenError( 

599 'LinstorSRCreate', 

600 opterr='Not enough online hosts' 

601 ) 

602 

603 ips = {} 

604 for host_ref in online_hosts: 

605 record = self.session.xenapi.host.get_record(host_ref) 

606 hostname = record['hostname'] 

607 ips[hostname] = record['address'] 

608 

609 if len(ips) != len(online_hosts): 

610 raise xs_errors.XenError( 

611 'LinstorSRCreate', 

612 opterr='Multiple hosts with same hostname' 

613 ) 

614 

615 # Ensure ports are opened and LINSTOR satellites 

616 # are activated. In the same time the drbd-reactor instances 

617 # must be stopped. 

618 self._prepare_sr_on_all_hosts(self._group_name, enabled=True) 

619 

620 # Create SR. 

621 # Throw if the SR already exists. 

622 try: 

623 self._linstor = LinstorVolumeManager.create_sr( 

624 self._group_name, 

625 ips, 

626 self._redundancy, 

627 thin_provisioning=self._provisioning == 'thin', 

628 logger=util.SMlog 

629 ) 

630 

631 util.SMlog( 

632 "Finishing SR creation, enable drbd-reactor on all hosts..." 

633 ) 

634 self._update_drbd_reactor_on_all_hosts(enabled=True) 

635 except Exception as e: 

636 if not self._linstor: 

637 util.SMlog('Failed to create LINSTOR SR: {}'.format(e)) 

638 raise xs_errors.XenError('LinstorSRCreate', opterr=str(e)) 

639 

640 try: 

641 self._linstor.destroy() 

642 except Exception as e2: 

643 util.SMlog( 

644 'Failed to destroy LINSTOR SR after creation fail: {}' 

645 .format(e2) 

646 ) 

647 raise e 

648 

649 @override 

650 @_locked_load 

651 def delete(self, uuid) -> None: 

652 util.SMlog('LinstorSR.delete for {}'.format(self.uuid)) 

653 cleanup.gc_force(self.session, self.uuid) 

654 

655 assert self._linstor 

656 if self.vdis or self._linstor._volumes: 

657 raise xs_errors.XenError('SRNotEmpty') 

658 

659 node_name = get_controller_node_name() 

660 if not node_name: 

661 raise xs_errors.XenError( 

662 'LinstorSRDelete', 

663 opterr='Cannot get controller node name' 

664 ) 

665 

666 host_ref = None 

667 if node_name == 'localhost': 

668 host_ref = util.get_this_host_ref(self.session) 

669 else: 

670 for slave in util.get_all_slaves(self.session): 

671 r_name = self.session.xenapi.host.get_record(slave)['hostname'] 

672 if r_name == node_name: 

673 host_ref = slave 

674 break 

675 

676 if not host_ref: 

677 raise xs_errors.XenError( 

678 'LinstorSRDelete', 

679 opterr='Failed to find host with hostname: {}'.format( 

680 node_name 

681 ) 

682 ) 

683 

684 try: 

685 if self._monitor_db_quorum: 

686 self._linstor.set_drbd_ha_properties(DATABASE_VOLUME_NAME, enabled=False) 

687 self._update_drbd_reactor_on_all_hosts( 

688 controller_node_name=node_name, enabled=False 

689 ) 

690 

691 args = { 

692 'groupName': self._group_name, 

693 } 

694 self._exec_manager_command( 

695 host_ref, 'destroy', args, 'LinstorSRDelete' 

696 ) 

697 except Exception as e: 

698 try: 

699 self._update_drbd_reactor_on_all_hosts( 

700 controller_node_name=node_name, enabled=True 

701 ) 

702 if self._monitor_db_quorum: 

703 self._linstor.set_drbd_ha_properties(DATABASE_VOLUME_NAME, enabled=True) 

704 except Exception as e2: 

705 util.SMlog( 

706 'Failed to restart drbd-reactor after destroy fail: {}' 

707 .format(e2) 

708 ) 

709 util.SMlog('Failed to delete LINSTOR SR: {}'.format(e)) 

710 raise xs_errors.XenError( 

711 'LinstorSRDelete', 

712 opterr=str(e) 

713 ) 

714 

715 lock.Lock.cleanupAll(self.uuid) 

716 

717 @override 

718 @_locked_load 

719 def update(self, uuid) -> None: 

720 util.SMlog('LinstorSR.update for {}'.format(self.uuid)) 

721 

722 # Well, how can we update a SR if it doesn't exist? :thinking: 

723 if not self._linstor: 

724 raise xs_errors.XenError( 

725 'SRUnavailable', 

726 opterr='no such volume group: {}'.format(self._group_name) 

727 ) 

728 

729 self._update_stats(0) 

730 

731 # Update the SR name and description only in LINSTOR metadata. 

732 xenapi = self.session.xenapi 

733 self._linstor.metadata = { 

734 NAME_LABEL_TAG: util.to_plain_string( 

735 xenapi.SR.get_name_label(self.sr_ref) 

736 ), 

737 NAME_DESCRIPTION_TAG: util.to_plain_string( 

738 xenapi.SR.get_name_description(self.sr_ref) 

739 ) 

740 } 

741 

742 @override 

743 @_locked_load 

744 def attach(self, uuid) -> None: 

745 util.SMlog('LinstorSR.attach for {}'.format(self.uuid)) 

746 

747 if not self._linstor: 

748 raise xs_errors.XenError( 

749 'SRUnavailable', 

750 opterr='no such group: {}'.format(self._group_name) 

751 ) 

752 

753 if self._monitor_db_quorum and self.is_master(): 

754 self._linstor.set_drbd_ha_properties(DATABASE_VOLUME_NAME) 

755 

756 @override 

757 @_locked_load 

758 def detach(self, uuid) -> None: 

759 util.SMlog('LinstorSR.detach for {}'.format(self.uuid)) 

760 cleanup.abort(self.uuid) 

761 

762 @override 

763 @_locked_load 

764 def probe(self) -> str: 

765 util.SMlog('LinstorSR.probe for {}'.format(self.uuid)) 

766 # TODO 

767 return '' 

768 

769 @override 

770 @_locked_load 

771 def scan(self, uuid) -> None: 

772 if self._init_status == self.INIT_STATUS_FAIL: 

773 return 

774 

775 util.SMlog('LinstorSR.scan for {}'.format(self.uuid)) 

776 if not self._linstor: 

777 raise xs_errors.XenError( 

778 'SRUnavailable', 

779 opterr='no such volume group: {}'.format(self._group_name) 

780 ) 

781 

782 # Note: `scan` can be called outside this module, so ensure the VDIs 

783 # are loaded. 

784 self._load_vdis() 

785 self._update_physical_size() 

786 

787 for vdi_uuid in list(self.vdis.keys()): 

788 if self.vdis[vdi_uuid].deleted: 

789 del self.vdis[vdi_uuid] 

790 

791 # Security to prevent VDIs from being forgotten if the controller 

792 # is started without a shared and mounted /var/lib/linstor path. 

793 try: 

794 self._linstor.get_database_path() 

795 except Exception as e: 

796 # Failed to get database path, ensure we don't have 

797 # VDIs in the XAPI database... 

798 if self.session.xenapi.SR.get_VDIs( 

799 self.session.xenapi.SR.get_by_uuid(self.uuid) 

800 ): 

801 raise xs_errors.XenError( 

802 'SRUnavailable', 

803 opterr='Database is not mounted or node name is invalid ({})'.format(e) 

804 ) 

805 

806 # Update the database before the restart of the GC to avoid 

807 # bad sync in the process if new VDIs have been introduced. 

808 super(LinstorSR, self).scan(self.uuid) 

809 self._kick_gc() 

810 

811 def is_master(self): 

812 if not hasattr(self, '_is_master'): 

813 if 'SRmaster' not in self.dconf: 

814 self._is_master = self.session is not None and util.is_master(self.session) 

815 else: 

816 self._is_master = self.dconf['SRmaster'] == 'true' 

817 

818 return self._is_master 

819 

820 @override 

821 def check_sr(self, sr_uuid) -> None: 

822 # Note: check_sr is called on all hosts by the health check mechanism 

823 # not by regular xapi calls such as scans. 

824 # Applied only on the Linstor Controller, for reasons -> listed below. 

825 if not LinstorVolumeManager.is_controller(): 

826 return 

827 # Validate and clean previous backups if necessary. 

828 # -> Needs access to backup files, available only on the Controller. 

829 LinstorVolumeManager.database_backup_validate_and_prune() 

830 # check_sr is launched on *all* hosts, but it turns out that 

831 # we do not want all of them to blindly generate concurrencing backups. 

832 # Hence we must choose one, either one is good, but there must be only one. 

833 # Apply throttling: only backup if last one is >1h old. 

834 # -> Needs access to backup files, available only on the Controller. 

835 if LinstorVolumeManager.get_database_backup_age() > LINSTOR_AUTO_BACKUP_DELAY: 

836 self.database_backup("auto") 

837 

838 @override 

839 @_locked_load 

840 def vdi(self, uuid) -> VDI.VDI: 

841 return LinstorVDI(self, uuid) 

842 

843 # To remove in python 3.10 

844 # See: https://stackoverflow.com/questions/12718187/python-version-3-9-calling-class-staticmethod-within-the-class-body 

845 _locked_load = staticmethod(_locked_load) 

846 

847 # -------------------------------------------------------------------------- 

848 # Lock. 

849 # -------------------------------------------------------------------------- 

850 

851 def _shared_lock_vdi(self, vdi_uuid, locked=True): 

852 master = util.get_master_ref(self.session) 

853 

854 command = 'lockVdi' 

855 args = { 

856 'groupName': self._group_name, 

857 'srUuid': self.uuid, 

858 'vdiUuid': vdi_uuid, 

859 'locked': str(locked) 

860 } 

861 

862 # Note: We must avoid to unlock the volume if the timeout is reached 

863 # because during volume unlock, the SR lock is not used. Otherwise 

864 # we could destroy a valid lock acquired from another host... 

865 # 

866 # This code is not very clean, the ideal solution would be to acquire 

867 # the SR lock during volume unlock (like lock) but it's not easy 

868 # to implement without impacting performance. 

869 if not locked: 

870 elapsed_time = time.time() - self._vdi_shared_time 

871 timeout = LinstorVolumeManager.LOCKED_EXPIRATION_DELAY * 0.7 

872 if elapsed_time >= timeout: 

873 util.SMlog( 

874 'Avoid unlock call of {} because timeout has been reached' 

875 .format(vdi_uuid) 

876 ) 

877 return 

878 

879 self._exec_manager_command(master, command, args, 'VDIUnavailable') 

880 

881 # -------------------------------------------------------------------------- 

882 # Network. 

883 # -------------------------------------------------------------------------- 

884 

885 def _exec_manager_command(self, host_ref, command, args, error): 

886 host_rec = self.session.xenapi.host.get_record(host_ref) 

887 host_uuid = host_rec['uuid'] 

888 

889 try: 

890 ret = self.session.xenapi.host.call_plugin( 

891 host_ref, self.MANAGER_PLUGIN, command, args 

892 ) 

893 except Exception as e: 

894 util.SMlog( 

895 'call-plugin on {} ({}:{} with {}) raised'.format( 

896 host_uuid, self.MANAGER_PLUGIN, command, args 

897 ) 

898 ) 

899 raise e 

900 

901 util.SMlog( 

902 'call-plugin on {} ({}:{} with {}) returned: {}'.format( 

903 host_uuid, self.MANAGER_PLUGIN, command, args, ret 

904 ) 

905 ) 

906 if ret == 'False': 

907 raise xs_errors.XenError( 

908 error, 

909 opterr='Plugin {} failed'.format(self.MANAGER_PLUGIN) 

910 ) 

911 

912 def _prepare_sr(self, host, group_name, enabled): 

913 self._exec_manager_command( 

914 host, 

915 'prepareSr' if enabled else 'releaseSr', 

916 {'groupName': group_name}, 

917 'SRUnavailable' 

918 ) 

919 

920 def _prepare_sr_on_all_hosts(self, group_name, enabled): 

921 master = util.get_master_ref(self.session) 

922 self._prepare_sr(master, group_name, enabled) 

923 

924 for slave in util.get_all_slaves(self.session): 

925 self._prepare_sr(slave, group_name, enabled) 

926 

927 def _update_drbd_reactor(self, host, enabled): 

928 self._exec_manager_command( 

929 host, 

930 'updateDrbdReactor', 

931 {'enabled': str(enabled)}, 

932 'SRUnavailable' 

933 ) 

934 

935 def _update_drbd_reactor_on_all_hosts( 

936 self, enabled, controller_node_name=None 

937 ): 

938 if controller_node_name == 'localhost': 

939 controller_node_name = self.session.xenapi.host.get_record( 

940 util.get_this_host_ref(self.session) 

941 )['hostname'] 

942 assert controller_node_name 

943 assert controller_node_name != 'localhost' 

944 

945 controller_host = None 

946 secondary_hosts = [] 

947 

948 hosts = self.session.xenapi.host.get_all_records() 

949 for host_ref, host_rec in hosts.items(): 

950 hostname = host_rec['hostname'] 

951 if controller_node_name == hostname: 

952 controller_host = host_ref 

953 else: 

954 secondary_hosts.append((host_ref, hostname)) 

955 

956 action_name = 'Starting' if enabled else 'Stopping' 

957 if controller_node_name and not controller_host: 

958 util.SMlog('Failed to find controller host: `{}`'.format( 

959 controller_node_name 

960 )) 

961 

962 if enabled and controller_host: 

963 util.SMlog('{} drbd-reactor on controller host `{}`...'.format( 

964 action_name, controller_node_name 

965 )) 

966 # If enabled is true, we try to start the controller on the desired 

967 # node name first. 

968 self._update_drbd_reactor(controller_host, enabled) 

969 

970 for host_ref, hostname in secondary_hosts: 

971 util.SMlog('{} drbd-reactor on host {}...'.format( 

972 action_name, hostname 

973 )) 

974 self._update_drbd_reactor(host_ref, enabled) 

975 

976 if not enabled and controller_host: 

977 util.SMlog('{} drbd-reactor on controller host `{}`...'.format( 

978 action_name, controller_node_name 

979 )) 

980 # If enabled is false, we disable the drbd-reactor service of 

981 # the controller host last. Why? Otherwise the linstor-controller 

982 # of other nodes can be started, and we don't want that. 

983 self._update_drbd_reactor(controller_host, enabled) 

984 

985 # -------------------------------------------------------------------------- 

986 # Metadata. 

987 # -------------------------------------------------------------------------- 

988 

989 def _synchronize_metadata_and_xapi(self): 

990 try: 

991 # First synch SR parameters. 

992 self.update(self.uuid) 

993 

994 # Now update the VDI information in the metadata if required. 

995 xenapi = self.session.xenapi 

996 volumes_metadata = self._linstor.get_volumes_with_metadata() 

997 for vdi_uuid, volume_metadata in volumes_metadata.items(): 

998 try: 

999 vdi_ref = xenapi.VDI.get_by_uuid(vdi_uuid) 

1000 except Exception: 

1001 # May be the VDI is not in XAPI yet dont bother. 

1002 continue 

1003 

1004 label = util.to_plain_string( 

1005 xenapi.VDI.get_name_label(vdi_ref) 

1006 ) 

1007 description = util.to_plain_string( 

1008 xenapi.VDI.get_name_description(vdi_ref) 

1009 ) 

1010 

1011 if ( 

1012 volume_metadata.get(NAME_LABEL_TAG) != label or 

1013 volume_metadata.get(NAME_DESCRIPTION_TAG) != description 

1014 ): 

1015 self._linstor.update_volume_metadata(vdi_uuid, { 

1016 NAME_LABEL_TAG: label, 

1017 NAME_DESCRIPTION_TAG: description 

1018 }) 

1019 except Exception as e: 

1020 raise xs_errors.XenError( 

1021 'MetadataError', 

1022 opterr='Error synching SR Metadata and XAPI: {}'.format(e) 

1023 ) 

1024 

1025 def _synchronize_metadata(self): 

1026 if not self.is_master(): 

1027 return 

1028 

1029 util.SMlog('Synchronize metadata...') 

1030 if self.cmd == 'sr_attach': 

1031 try: 

1032 util.SMlog( 

1033 'Synchronize SR metadata and the state on the storage.' 

1034 ) 

1035 self._synchronize_metadata_and_xapi() 

1036 except Exception as e: 

1037 util.SMlog('Failed to synchronize metadata: {}'.format(e)) 

1038 

1039 # -------------------------------------------------------------------------- 

1040 # Stats. 

1041 # -------------------------------------------------------------------------- 

1042 

1043 def _update_stats(self, virt_alloc_delta): 

1044 valloc = int(self.session.xenapi.SR.get_virtual_allocation( 

1045 self.sr_ref 

1046 )) 

1047 

1048 # Update size attributes of the SR parent class. 

1049 self.virtual_allocation = valloc + virt_alloc_delta 

1050 

1051 self._update_physical_size() 

1052 

1053 # Notify SR parent class. 

1054 self._db_update() 

1055 

1056 def _update_physical_size(self): 

1057 # We use the size of the smallest disk, this is an approximation that 

1058 # ensures the displayed physical size is reachable by the user. 

1059 (min_physical_size, pool_count) = self._linstor.get_min_physical_size() 

1060 self.physical_size = min_physical_size * pool_count // \ 

1061 self._linstor.redundancy 

1062 

1063 self.physical_utilisation = self._linstor.allocated_volume_size 

1064 

1065 # -------------------------------------------------------------------------- 

1066 # VDIs. 

1067 # -------------------------------------------------------------------------- 

1068 

1069 def _load_vdis(self): 

1070 if self._vdis_loaded: 

1071 return 

1072 

1073 assert self.is_master() 

1074 

1075 # We use a cache to avoid repeated JSON parsing. 

1076 # The performance gain is not big but we can still 

1077 # enjoy it with a few lines. 

1078 self._create_linstor_cache() 

1079 self._load_vdis_ex() 

1080 self._destroy_linstor_cache() 

1081 

1082 # We must mark VDIs as loaded only if the load is a success. 

1083 self._vdis_loaded = True 

1084 

1085 self._undo_all_journal_transactions() 

1086 

1087 def _load_vdis_ex(self): 

1088 # 1. Get existing VDIs in XAPI. 

1089 xenapi = self.session.xenapi 

1090 xapi_vdi_uuids = set() 

1091 for vdi in xenapi.SR.get_VDIs(self.sr_ref): 

1092 xapi_vdi_uuids.add(xenapi.VDI.get_uuid(vdi)) 

1093 

1094 # 2. Get volumes info. 

1095 all_volume_info = self._all_volume_info_cache 

1096 volumes_metadata = self._all_volume_metadata_cache 

1097 

1098 # 3. Get CBT vdis. 

1099 # See: https://support.citrix.com/article/CTX230619 

1100 cbt_vdis = set() 

1101 for volume_metadata in volumes_metadata.values(): 

1102 cbt_uuid = volume_metadata.get(CBTLOG_TAG) 

1103 if cbt_uuid: 

1104 cbt_vdis.add(cbt_uuid) 

1105 

1106 introduce = False 

1107 

1108 # Try to introduce VDIs only during scan/attach. 

1109 if self.cmd == 'sr_scan' or self.cmd == 'sr_attach': 

1110 has_clone_entries = list(self._get_journaler().get_all( 

1111 LinstorJournaler.CLONE 

1112 ).items()) 

1113 

1114 if has_clone_entries: 

1115 util.SMlog( 

1116 'Cannot introduce VDIs during scan because it exists ' 

1117 'CLONE entries in journaler on SR {}'.format(self.uuid) 

1118 ) 

1119 else: 

1120 introduce = True 

1121 

1122 # 4. Now process all volume info. 

1123 vdi_to_snaps = {} 

1124 vdi_uuids = [] 

1125 

1126 for vdi_uuid, volume_info in all_volume_info.items(): 

1127 if vdi_uuid.startswith(cleanup.SR.TMP_RENAME_PREFIX): 

1128 continue 

1129 

1130 # 4.a. Check if the VDI in LINSTOR is in XAPI VDIs. 

1131 if vdi_uuid not in xapi_vdi_uuids: 

1132 if not introduce: 

1133 continue 

1134 

1135 if vdi_uuid.startswith('DELETED_'): 

1136 continue 

1137 

1138 volume_metadata = volumes_metadata.get(vdi_uuid) 

1139 if not volume_metadata: 

1140 util.SMlog( 

1141 'Skipping volume {} because no metadata could be found' 

1142 .format(vdi_uuid) 

1143 ) 

1144 continue 

1145 

1146 util.SMlog( 

1147 'Trying to introduce VDI {} as it is present in ' 

1148 'LINSTOR and not in XAPI...' 

1149 .format(vdi_uuid) 

1150 ) 

1151 

1152 try: 

1153 self._linstor.get_device_path(vdi_uuid) 

1154 except Exception as e: 

1155 util.SMlog( 

1156 'Cannot introduce {}, unable to get path: {}' 

1157 .format(vdi_uuid, e) 

1158 ) 

1159 continue 

1160 

1161 name_label = volume_metadata.get(NAME_LABEL_TAG) or '' 

1162 type = volume_metadata.get(TYPE_TAG) or 'user' 

1163 vdi_type = volume_metadata.get(VDI_TYPE_TAG) 

1164 

1165 if not vdi_type: 

1166 util.SMlog( 

1167 'Cannot introduce {} '.format(vdi_uuid) + 

1168 'without vdi_type' 

1169 ) 

1170 continue 

1171 

1172 sm_config = { 

1173 'vdi_type': vdi_type 

1174 } 

1175 

1176 if not VdiType.isCowImage(vdi_type): 

1177 managed = not volume_metadata.get(HIDDEN_TAG) 

1178 else: 

1179 image_info = LinstorCowUtil(self.session, self._linstor, vdi_type).get_info(vdi_uuid) 

1180 managed = not image_info.hidden 

1181 if image_info.parentUuid: 

1182 sm_config['vhd-parent'] = image_info.parentUuid 

1183 

1184 util.SMlog( 

1185 'Introducing VDI {} '.format(vdi_uuid) + 

1186 ' (name={}, virtual_size={}, allocated_size={})'.format( 

1187 name_label, 

1188 volume_info.virtual_size, 

1189 volume_info.allocated_size 

1190 ) 

1191 ) 

1192 

1193 vdi_ref = xenapi.VDI.db_introduce( 

1194 vdi_uuid, 

1195 name_label, 

1196 volume_metadata.get(NAME_DESCRIPTION_TAG) or '', 

1197 self.sr_ref, 

1198 type, 

1199 False, # sharable 

1200 bool(volume_metadata.get(READ_ONLY_TAG)), 

1201 {}, # other_config 

1202 vdi_uuid, # location 

1203 {}, # xenstore_data 

1204 sm_config, 

1205 managed, 

1206 str(volume_info.virtual_size), 

1207 str(volume_info.allocated_size) 

1208 ) 

1209 

1210 is_a_snapshot = volume_metadata.get(IS_A_SNAPSHOT_TAG) 

1211 xenapi.VDI.set_is_a_snapshot(vdi_ref, bool(is_a_snapshot)) 

1212 if is_a_snapshot: 

1213 xenapi.VDI.set_snapshot_time( 

1214 vdi_ref, 

1215 xmlrpc.client.DateTime( 

1216 volume_metadata[SNAPSHOT_TIME_TAG] or 

1217 '19700101T00:00:00Z' 

1218 ) 

1219 ) 

1220 

1221 snap_uuid = volume_metadata[SNAPSHOT_OF_TAG] 

1222 if snap_uuid in vdi_to_snaps: 

1223 vdi_to_snaps[snap_uuid].append(vdi_uuid) 

1224 else: 

1225 vdi_to_snaps[snap_uuid] = [vdi_uuid] 

1226 

1227 # 4.b. Add the VDI in the list. 

1228 vdi_uuids.append(vdi_uuid) 

1229 

1230 # 5. Create VDIs. 

1231 self._multi_cowutil = MultiLinstorCowUtil(self._linstor.uri, self._group_name) 

1232 

1233 def load_vdi(vdi_uuid, multi_cowutil): 

1234 vdi = self.vdi(vdi_uuid) 

1235 

1236 if USE_KEY_HASH and VdiType.isCowImage(vdi.vdi_type): 

1237 cowutil_instance = multi_cowutil.get_local_cowutil(vdi.vdi_type) 

1238 vdi.sm_config_override['key_hash'] = cowutil_instance.get_key_hash(vdi_uuid) 

1239 

1240 return vdi 

1241 

1242 try: 

1243 self.vdis = {vdi.uuid: vdi for vdi in self._multi_cowutil.run(load_vdi, vdi_uuids)} 

1244 finally: 

1245 multi_cowutil = self._multi_cowutil 

1246 self._multi_cowutil = None 

1247 del multi_cowutil 

1248 

1249 # 6. Update CBT status of disks either just added 

1250 # or already in XAPI. 

1251 for vdi in self.vdis.values(): 

1252 volume_metadata = volumes_metadata.get(vdi.uuid) 

1253 cbt_uuid = volume_metadata.get(CBTLOG_TAG) 

1254 if cbt_uuid in cbt_vdis: 

1255 vdi_ref = xenapi.VDI.get_by_uuid(vdi_uuid) 

1256 xenapi.VDI.set_cbt_enabled(vdi_ref, True) 

1257 # For existing VDIs, update local state too. 

1258 # Scan in base class SR updates existing VDIs 

1259 # again based on local states. 

1260 self.vdis[vdi_uuid].cbt_enabled = True 

1261 cbt_vdis.remove(cbt_uuid) 

1262 

1263 # 7. Now set the snapshot statuses correctly in XAPI. 

1264 for src_uuid in vdi_to_snaps: 

1265 try: 

1266 src_ref = xenapi.VDI.get_by_uuid(src_uuid) 

1267 except Exception: 

1268 # The source VDI no longer exists, continue. 

1269 continue 

1270 

1271 for snap_uuid in vdi_to_snaps[src_uuid]: 

1272 try: 

1273 # This might fail in cases where its already set. 

1274 snap_ref = xenapi.VDI.get_by_uuid(snap_uuid) 

1275 xenapi.VDI.set_snapshot_of(snap_ref, src_ref) 

1276 except Exception as e: 

1277 util.SMlog('Setting snapshot failed: {}'.format(e)) 

1278 

1279 # TODO: Check correctly how to use CBT. 

1280 # Update cbt_enabled on the right VDI, check LVM/FileSR code. 

1281 

1282 # 8. If we have items remaining in this list, 

1283 # they are cbt_metadata VDI that XAPI doesn't know about. 

1284 # Add them to self.vdis and they'll get added to the DB. 

1285 for cbt_uuid in cbt_vdis: 

1286 new_vdi = self.vdi(cbt_uuid) 

1287 new_vdi.ty = 'cbt_metadata' 

1288 new_vdi.cbt_enabled = True 

1289 self.vdis[cbt_uuid] = new_vdi 

1290 

1291 # 9. Update virtual allocation, build geneology and remove useless VDIs 

1292 self.virtual_allocation = 0 

1293 

1294 # 10. Build geneology. 

1295 geneology = {} 

1296 

1297 for vdi_uuid, vdi in self.vdis.items(): 

1298 if vdi.parent: 

1299 if vdi.parent in self.vdis: 

1300 self.vdis[vdi.parent].read_only = True 

1301 if vdi.parent in geneology: 

1302 geneology[vdi.parent].append(vdi_uuid) 

1303 else: 

1304 geneology[vdi.parent] = [vdi_uuid] 

1305 if not vdi.hidden: 

1306 self.virtual_allocation += vdi.size 

1307 

1308 # 11. Remove all hidden leaf nodes to avoid introducing records that 

1309 # will be GC'ed. 

1310 for vdi_uuid in list(self.vdis.keys()): 

1311 if vdi_uuid not in geneology and self.vdis[vdi_uuid].hidden: 

1312 util.SMlog( 

1313 'Scan found hidden leaf ({}), ignoring'.format(vdi_uuid) 

1314 ) 

1315 del self.vdis[vdi_uuid] 

1316 

1317 # -------------------------------------------------------------------------- 

1318 # Journals. 

1319 # -------------------------------------------------------------------------- 

1320 

1321 def _get_journaler(self): 

1322 if not self._journaler: 

1323 self._journaler = LinstorJournaler( 

1324 self._group_name, 

1325 native_client=self._linstor.native_client, 

1326 logger=util.SMlog 

1327 ) 

1328 return self._journaler 

1329 

1330 def _get_vdi_path_and_parent(self, vdi_uuid, volume_name): 

1331 try: 

1332 device_path = self._linstor.build_device_path(volume_name) 

1333 if not util.pathexists(device_path): 

1334 return (None, None) 

1335 

1336 # If it's a RAW VDI, there is no parent. 

1337 volume_metadata = self._linstor.get_volume_metadata(vdi_uuid) 

1338 vdi_type = volume_metadata[VDI_TYPE_TAG] 

1339 if not VdiType.isCowImage(vdi_type): 

1340 return (device_path, None) 

1341 

1342 # Otherwise it's a COW and a parent can exist. 

1343 linstorcowutil = LinstorCowUtil(self.session, self._linstor, vdi_type) 

1344 if linstorcowutil.check(vdi_uuid) != CowUtil.CheckResult.Success: 

1345 return (None, None) 

1346 

1347 image_info = linstorcowutil.get_info(vdi_uuid) 

1348 if image_info: 

1349 return (device_path, image_info.parentUuid) 

1350 except Exception as e: 

1351 util.SMlog( 

1352 'Failed to get VDI path and parent, ignoring: {}' 

1353 .format(e) 

1354 ) 

1355 return (None, None) 

1356 

1357 def _undo_all_journal_transactions(self): 

1358 util.SMlog('Undoing all journal transactions...') 

1359 self.lock.acquire() 

1360 try: 

1361 # Ensure journaler cache is clean. We MUST not rollback from invalid cache. 

1362 self._journaler = None 

1363 journaler = self._get_journaler() 

1364 self._handle_interrupted_inflate_ops(journaler) 

1365 self._handle_interrupted_clone_ops(journaler) 

1366 finally: 

1367 self.lock.release() 

1368 

1369 def _handle_interrupted_inflate_ops(self, journaler): 

1370 transactions = journaler.get_all(LinstorJournaler.INFLATE) 

1371 for vdi_uuid, old_size in transactions.items(): 

1372 self._handle_interrupted_inflate(vdi_uuid, old_size) 

1373 journaler.remove(LinstorJournaler.INFLATE, vdi_uuid) 

1374 

1375 def _handle_interrupted_clone_ops(self, journaler): 

1376 transactions = journaler.get_all(LinstorJournaler.CLONE) 

1377 for vdi_uuid, old_size in transactions.items(): 

1378 self._handle_interrupted_clone(vdi_uuid, old_size) 

1379 journaler.remove(LinstorJournaler.CLONE, vdi_uuid) 

1380 

1381 def _handle_interrupted_inflate(self, vdi_uuid, old_size): 

1382 util.SMlog( 

1383 '*** INTERRUPTED INFLATE OP: for {} ({})' 

1384 .format(vdi_uuid, old_size) 

1385 ) 

1386 

1387 vdi = self.vdis.get(vdi_uuid) 

1388 if not vdi: 

1389 util.SMlog('Cannot deflate missing VDI {}'.format(vdi_uuid)) 

1390 return 

1391 

1392 assert not self._all_volume_info_cache 

1393 volume_info = self._linstor.get_volume_info(vdi_uuid) 

1394 

1395 current_size = volume_info.virtual_size 

1396 assert current_size > 0 

1397 vdi.linstorcowutil.force_deflate(vdi.path, old_size, current_size, zeroize=True) 

1398 

1399 def _handle_interrupted_clone( 

1400 self, vdi_uuid, clone_info, force_undo=False 

1401 ): 

1402 util.SMlog( 

1403 '*** INTERRUPTED CLONE OP: for {} ({})' 

1404 .format(vdi_uuid, clone_info) 

1405 ) 

1406 

1407 base_uuid, snap_uuid = clone_info.split('_') 

1408 

1409 # Use LINSTOR data because new VDIs may not be in the XAPI. 

1410 volume_names = self._linstor.get_volumes_with_name() 

1411 

1412 # Check if we don't have a base VDI. (If clone failed at startup.) 

1413 if base_uuid not in volume_names: 

1414 if vdi_uuid in volume_names: 

1415 util.SMlog('*** INTERRUPTED CLONE OP: nothing to do') 

1416 return 

1417 raise util.SMException( 

1418 'Base copy {} not present, but no original {} found' 

1419 .format(base_uuid, vdi_uuid) 

1420 ) 

1421 

1422 if force_undo: 

1423 util.SMlog('Explicit revert') 

1424 self._undo_clone( 

1425 volume_names, vdi_uuid, base_uuid, snap_uuid 

1426 ) 

1427 return 

1428 

1429 # If VDI or snap uuid is missing... 

1430 if vdi_uuid not in volume_names or \ 

1431 (snap_uuid and snap_uuid not in volume_names): 

1432 util.SMlog('One or both leaves missing => revert') 

1433 self._undo_clone(volume_names, vdi_uuid, base_uuid, snap_uuid) 

1434 return 

1435 

1436 vdi_path, vdi_parent_uuid = self._get_vdi_path_and_parent( 

1437 vdi_uuid, volume_names[vdi_uuid] 

1438 ) 

1439 snap_path, snap_parent_uuid = self._get_vdi_path_and_parent( 

1440 snap_uuid, volume_names[snap_uuid] 

1441 ) 

1442 

1443 if not vdi_path or (snap_uuid and not snap_path): 

1444 util.SMlog('One or both leaves invalid (and path(s)) => revert') 

1445 self._undo_clone(volume_names, vdi_uuid, base_uuid, snap_uuid) 

1446 return 

1447 

1448 util.SMlog('Leaves valid but => revert') 

1449 self._undo_clone(volume_names, vdi_uuid, base_uuid, snap_uuid) 

1450 

1451 def _undo_clone(self, volume_names, vdi_uuid, base_uuid, snap_uuid): 

1452 base_path = self._linstor.build_device_path(volume_names[base_uuid]) 

1453 base_metadata = self._linstor.get_volume_metadata(base_uuid) 

1454 base_type = base_metadata[VDI_TYPE_TAG] 

1455 

1456 if not util.pathexists(base_path): 

1457 util.SMlog('Base not found! Exit...') 

1458 util.SMlog('*** INTERRUPTED CLONE OP: rollback fail') 

1459 return 

1460 

1461 linstorcowutil = LinstorCowUtil(self.session, self._linstor, base_type) 

1462 

1463 # Un-hide the parent. 

1464 self._linstor.update_volume_metadata(base_uuid, {READ_ONLY_TAG: False}) 

1465 if VdiType.isCowImage(base_type): 

1466 image_info = linstorcowutil.get_info(base_uuid, False) 

1467 if image_info.hidden: 

1468 linstorcowutil.set_hidden(base_path, False) 

1469 elif base_metadata.get(HIDDEN_TAG): 

1470 self._linstor.update_volume_metadata( 

1471 base_uuid, {HIDDEN_TAG: False} 

1472 ) 

1473 

1474 # Remove the child nodes. 

1475 if snap_uuid and snap_uuid in volume_names: 

1476 util.SMlog('Destroying snap {}...'.format(snap_uuid)) 

1477 

1478 try: 

1479 self._linstor.destroy_volume(snap_uuid) 

1480 except Exception as e: 

1481 util.SMlog( 

1482 'Cannot destroy snap {} during undo clone: {}' 

1483 .format(snap_uuid, e) 

1484 ) 

1485 

1486 if vdi_uuid in volume_names: 

1487 try: 

1488 util.SMlog('Destroying {}...'.format(vdi_uuid)) 

1489 self._linstor.destroy_volume(vdi_uuid) 

1490 except Exception as e: 

1491 util.SMlog( 

1492 'Cannot destroy VDI {} during undo clone: {}' 

1493 .format(vdi_uuid, e) 

1494 ) 

1495 # We can get an exception like this: 

1496 # "Shutdown of the DRBD resource 'XXX failed", so the 

1497 # volume info remains... The problem is we can't rename 

1498 # properly the base VDI below this line, so we must change the 

1499 # UUID of this bad VDI before. 

1500 self._linstor.update_volume_uuid( 

1501 vdi_uuid, 'DELETED_' + vdi_uuid, force=True 

1502 ) 

1503 

1504 # Rename! 

1505 self._linstor.update_volume_uuid(base_uuid, vdi_uuid) 

1506 

1507 # Inflate to the right size. 

1508 if VdiType.isCowImage(base_type): 

1509 vdi = self.vdi(vdi_uuid) 

1510 linstorcowutil = LinstorCowUtil(self.session, self._linstor, vdi.vdi_type) 

1511 volume_size = linstorcowutil.compute_volume_size(vdi.size) 

1512 linstorcowutil.inflate( 

1513 self._get_journaler(), vdi_uuid, vdi.path, 

1514 volume_size, vdi.capacity 

1515 ) 

1516 self.vdis[vdi_uuid] = vdi 

1517 

1518 # At this stage, tapdisk and SM vdi will be in paused state. Remove 

1519 # flag to facilitate vm deactivate. 

1520 vdi_ref = self.session.xenapi.VDI.get_by_uuid(vdi_uuid) 

1521 self.session.xenapi.VDI.remove_from_sm_config(vdi_ref, 'paused') 

1522 

1523 util.SMlog('*** INTERRUPTED CLONE OP: rollback success') 

1524 

1525 # -------------------------------------------------------------------------- 

1526 # Cache. 

1527 # -------------------------------------------------------------------------- 

1528 

1529 def _create_linstor_cache(self): 

1530 reconnect = False 

1531 

1532 def create_cache(): 

1533 nonlocal reconnect 

1534 try: 

1535 if reconnect: 

1536 self._reconnect() 

1537 return self._linstor.get_volumes_with_info() 

1538 except Exception as e: 

1539 reconnect = True 

1540 raise e 

1541 

1542 self._all_volume_metadata_cache = \ 

1543 self._linstor.get_volumes_with_metadata() 

1544 self._all_volume_info_cache = util.retry( 

1545 create_cache, 

1546 maxretry=10, 

1547 period=3 

1548 ) 

1549 

1550 def _destroy_linstor_cache(self): 

1551 self._all_volume_info_cache = None 

1552 self._all_volume_metadata_cache = None 

1553 

1554 # -------------------------------------------------------------------------- 

1555 # Misc. 

1556 # -------------------------------------------------------------------------- 

1557 

1558 def _reconnect(self): 

1559 controller_uri = get_controller_uri() 

1560 

1561 # Try to open SR if exists. 

1562 # We can repair only if we are on the master AND if 

1563 # we are trying to execute an exclusive operation. 

1564 # Otherwise we could try to delete a VDI being created or 

1565 # during a snapshot. An exclusive op is the guarantee that 

1566 # the SR is locked. 

1567 self._linstor = LinstorVolumeManager( 

1568 controller_uri, 

1569 self._group_name, 

1570 repair=( 

1571 self.is_master() and 

1572 self.srcmd.cmd in self.ops_exclusive 

1573 ), 

1574 logger=util.SMlog 

1575 ) 

1576 

1577 def _ensure_space_available(self, amount_needed): 

1578 space_available = self._linstor.max_volume_size_allowed 

1579 if (space_available < amount_needed): 

1580 util.SMlog( 

1581 'Not enough space! Free space: {}, need: {}'.format( 

1582 space_available, amount_needed 

1583 ) 

1584 ) 

1585 raise xs_errors.XenError('SRNoSpace') 

1586 

1587 def _kick_gc(self): 

1588 util.SMlog('Kicking GC') 

1589 cleanup.start_gc_service(self.uuid) 

1590 

1591 def database_backup(self, name: Literal["auto", "create", "delete", "snapshot"]): 

1592 """ 

1593 Generate a new database backup file. 

1594 This operation should not prevent the underlying action to be successful. 

1595 Hence all Exceptions are caught and logged. 

1596 """ 

1597 if not self._linstor: 

1598 self._reconnect() 

1599 try: 

1600 assert self._linstor 

1601 self._linstor.database_backup(name) 

1602 except Exception as e: 

1603 util.SMlog( 

1604 f"[database_backup] Error during creation: {e}", 

1605 priority=util.LOG_ERR, 

1606 ) 

1607 

1608# ============================================================================== 

1609# LinstorSr VDI 

1610# ============================================================================== 

1611 

1612 

1613class LinstorVDI(VDI.VDI): 

1614 # -------------------------------------------------------------------------- 

1615 # VDI methods. 

1616 # -------------------------------------------------------------------------- 

1617 

1618 @override 

1619 def load(self, vdi_uuid) -> None: 

1620 self._lock = self.sr.lock 

1621 self._exists = True 

1622 self._linstor = self.sr._linstor 

1623 

1624 # Update hidden parent property. 

1625 self.hidden = False 

1626 

1627 def raise_bad_load(e): 

1628 util.SMlog( 

1629 'Got exception in LinstorVDI.load: {}'.format(e) 

1630 ) 

1631 util.SMlog(traceback.format_exc()) 

1632 raise xs_errors.XenError( 

1633 'VDIUnavailable', 

1634 opterr='Could not load {} because: {}'.format(self.uuid, e) 

1635 ) 

1636 

1637 # Try to load VDI. 

1638 try: 

1639 if ( 

1640 self.sr.srcmd.cmd == 'vdi_attach_from_config' or 

1641 self.sr.srcmd.cmd == 'vdi_detach_from_config' 

1642 ): 

1643 self._set_type(VdiType.RAW) 

1644 self.path = self.sr.srcmd.params['vdi_path'] 

1645 else: 

1646 self._determine_type_and_path() 

1647 self._load_this() 

1648 

1649 util.SMlog('VDI {} loaded! (path={}, hidden={})'.format( 

1650 self.uuid, self.path, self.hidden 

1651 )) 

1652 except LinstorVolumeManagerError as e: 

1653 # 1. It may be a VDI deletion. 

1654 if e.code == LinstorVolumeManagerError.ERR_VOLUME_NOT_EXISTS: 

1655 if self.sr.srcmd.cmd == 'vdi_delete': 

1656 self.deleted = True 

1657 return 

1658 

1659 # 2. Or maybe a creation. 

1660 if self.sr.srcmd.cmd == 'vdi_create': 

1661 image_format = None 

1662 self._key_hash = None # Only used in create. 

1663 

1664 self._exists = False 

1665 vdi_sm_config = self.sr.srcmd.params.get('vdi_sm_config') 

1666 if vdi_sm_config: 

1667 image_format = self.sr.read_config_image_format(vdi_sm_config) 

1668 

1669 if not image_format: 

1670 image_format = self.sr.preferred_image_formats[0] 

1671 self._set_type(self.sr._resolve_vdi_type_from_image_format(image_format)) 

1672 

1673 if VdiType.isCowImage(self.vdi_type): 

1674 self._key_hash = vdi_sm_config.get('key_hash') 

1675 

1676 # For the moment we don't have a path. 

1677 self._update_device_name(None) 

1678 return 

1679 raise_bad_load(e) 

1680 except Exception as e: 

1681 raise_bad_load(e) 

1682 

1683 @override 

1684 def create(self, sr_uuid, vdi_uuid, size) -> str: 

1685 # Usage example: 

1686 # xe vdi-create sr-uuid=39a5826b-5a90-73eb-dd09-51e3a116f937 

1687 # name-label="linstor-vdi-1" virtual-size=4096MiB sm-config:type=vhd 

1688 

1689 # 1. Check if we are on the master and if the VDI doesn't exist. 

1690 util.SMlog('LinstorVDI.create for {}'.format(self.uuid)) 

1691 if self._exists: 

1692 raise xs_errors.XenError('VDIExists') 

1693 

1694 assert self.uuid 

1695 assert self.ty 

1696 assert self.vdi_type 

1697 

1698 # 2. Compute size and check space available. 

1699 size = self.linstorcowutil.cowutil.validateAndRoundImageSize(int(size)) 

1700 volume_size = self.linstorcowutil.compute_volume_size(size) 

1701 util.SMlog( 

1702 'LinstorVDI.create: type={}, cow-size={}, volume-size={}' 

1703 .format(self.vdi_type, size, volume_size) 

1704 ) 

1705 self.sr._ensure_space_available(volume_size) 

1706 

1707 # 3. Set sm_config attribute of VDI parent class. 

1708 self.sm_config = self.sr.srcmd.params['vdi_sm_config'] 

1709 

1710 # 4. Create! 

1711 failed = False 

1712 try: 

1713 volume_name = None 

1714 if self.ty == 'ha_statefile': 

1715 volume_name = HA_VOLUME_NAME 

1716 elif self.ty == 'redo_log': 

1717 volume_name = REDO_LOG_VOLUME_NAME 

1718 

1719 self._linstor.create_volume( 

1720 self.uuid, 

1721 volume_size, 

1722 persistent=False, 

1723 volume_name=volume_name, 

1724 high_availability=volume_name is not None 

1725 ) 

1726 volume_info = self._linstor.get_volume_info(self.uuid) 

1727 

1728 self._update_device_name(volume_info.name) 

1729 

1730 if not VdiType.isCowImage(self.vdi_type): 

1731 self.size = volume_info.virtual_size 

1732 else: 

1733 self.linstorcowutil.create( 

1734 self.path, size, False, self.linstorcowutil.cowutil.getDefaultPreallocationSizeVirt() 

1735 ) 

1736 self.size = self.linstorcowutil.get_size_virt(self.uuid) 

1737 

1738 if self._key_hash: 

1739 self.linstorcowutil.set_key(self.path, self._key_hash) 

1740 

1741 # Because cowutil commands modify the volume data, 

1742 # we must retrieve a new time the utilization size. 

1743 volume_info = self._linstor.get_volume_info(self.uuid) 

1744 

1745 volume_metadata = { 

1746 NAME_LABEL_TAG: util.to_plain_string(self.label), 

1747 NAME_DESCRIPTION_TAG: util.to_plain_string(self.description), 

1748 IS_A_SNAPSHOT_TAG: False, 

1749 SNAPSHOT_OF_TAG: '', 

1750 SNAPSHOT_TIME_TAG: '', 

1751 TYPE_TAG: self.ty, 

1752 VDI_TYPE_TAG: self.vdi_type, 

1753 READ_ONLY_TAG: bool(self.read_only), 

1754 METADATA_OF_POOL_TAG: '' 

1755 } 

1756 self._linstor.set_volume_metadata(self.uuid, volume_metadata) 

1757 

1758 # Set the open timeout to 1min to reduce CPU usage 

1759 # in http-disk-server when a secondary server tries to open 

1760 # an already opened volume. 

1761 if self.ty == 'ha_statefile' or self.ty == 'redo_log': 

1762 self._linstor.set_auto_promote_timeout(self.uuid, 600) 

1763 

1764 self._linstor.mark_volume_as_persistent(self.uuid) 

1765 except util.CommandException as e: 

1766 failed = True 

1767 raise xs_errors.XenError( 

1768 'VDICreate', opterr='error {}'.format(e.code) 

1769 ) 

1770 except Exception as e: 

1771 failed = True 

1772 raise xs_errors.XenError('VDICreate', opterr='error {}'.format(e)) 

1773 finally: 

1774 if failed: 

1775 util.SMlog('Unable to create VDI {}'.format(self.uuid)) 

1776 try: 

1777 self._linstor.destroy_volume(self.uuid) 

1778 except Exception as e: 

1779 util.SMlog( 

1780 'Ignoring exception after fail in LinstorVDI.create: ' 

1781 '{}'.format(e) 

1782 ) 

1783 

1784 self.utilisation = volume_info.allocated_size 

1785 self.sm_config['vdi_type'] = self.vdi_type 

1786 self.sm_config['image-format'] = getImageStringFromVdiType(self.vdi_type) 

1787 

1788 self.ref = self._db_introduce() 

1789 self.sr._update_stats(self.size) 

1790 

1791 self.sr.database_backup("create") 

1792 

1793 return VDI.VDI.get_params(self) 

1794 

1795 @override 

1796 def delete(self, sr_uuid, vdi_uuid, data_only=False) -> None: 

1797 util.SMlog('LinstorVDI.delete for {}'.format(self.uuid)) 

1798 if self.attached: 

1799 raise xs_errors.XenError('VDIInUse') 

1800 

1801 if self.deleted: 

1802 return super(LinstorVDI, self).delete( 

1803 sr_uuid, vdi_uuid, data_only 

1804 ) 

1805 

1806 vdi_ref = self.sr.srcmd.params['vdi_ref'] 

1807 if not self.session.xenapi.VDI.get_managed(vdi_ref): 

1808 raise xs_errors.XenError( 

1809 'VDIDelete', 

1810 opterr='Deleting non-leaf node not permitted' 

1811 ) 

1812 

1813 try: 

1814 # Remove from XAPI and delete from LINSTOR. 

1815 self._linstor.destroy_volume(self.uuid) 

1816 if not data_only: 

1817 self._db_forget() 

1818 

1819 self.sr.lock.cleanupAll(vdi_uuid) 

1820 except Exception as e: 

1821 util.SMlog( 

1822 'Failed to remove the volume (maybe is leaf coalescing) ' 

1823 'for {} err: {}'.format(self.uuid, e) 

1824 ) 

1825 

1826 try: 

1827 raise e 

1828 except LinstorVolumeManagerError as e: 

1829 if e.code != LinstorVolumeManagerError.ERR_VOLUME_DESTROY: 

1830 raise xs_errors.XenError('VDIDelete', opterr=str(e)) 

1831 

1832 return 

1833 

1834 if self.uuid in self.sr.vdis: 

1835 del self.sr.vdis[self.uuid] 

1836 

1837 # TODO: Check size after delete. 

1838 self.sr._update_stats(-self.size) 

1839 self.sr._kick_gc() 

1840 super(LinstorVDI, self).delete(sr_uuid, vdi_uuid, data_only) 

1841 self.sr.database_backup("delete") 

1842 

1843 @override 

1844 def attach(self, sr_uuid, vdi_uuid) -> str: 

1845 util.SMlog('LinstorVDI.attach for {}'.format(self.uuid)) 

1846 attach_from_config = self.sr.srcmd.cmd == 'vdi_attach_from_config' 

1847 if ( 

1848 not attach_from_config or 

1849 self.sr.srcmd.params['vdi_uuid'] != self.uuid 

1850 ) and self.sr._get_journaler().has_entries(self.uuid): 

1851 raise xs_errors.XenError( 

1852 'VDIUnavailable', 

1853 opterr='Interrupted operation detected on this VDI, ' 

1854 'scan SR first to trigger auto-repair' 

1855 ) 

1856 

1857 writable = 'args' not in self.sr.srcmd.params or \ 

1858 self.sr.srcmd.params['args'][0] == 'true' 

1859 

1860 if not attach_from_config or self.sr.is_master(): 

1861 # We need to inflate the volume if we don't have enough place 

1862 # to mount the COW image. I.e. the volume capacity must be greater 

1863 # than the COW size + bitmap size. 

1864 need_inflate = True 

1865 if ( 

1866 not VdiType.isCowImage(self.vdi_type) or 

1867 not writable or 

1868 self.capacity >= self.linstorcowutil.compute_volume_size(self.size) 

1869 ): 

1870 need_inflate = False 

1871 

1872 if need_inflate: 

1873 try: 

1874 self._prepare_thin(True) 

1875 except Exception as e: 

1876 raise xs_errors.XenError( 

1877 'VDIUnavailable', 

1878 opterr='Failed to attach VDI during "prepare thin": {}' 

1879 .format(e) 

1880 ) 

1881 

1882 if not hasattr(self, 'xenstore_data'): 

1883 self.xenstore_data = {} 

1884 self.xenstore_data['storage-type'] = LinstorSR.DRIVER_TYPE 

1885 

1886 if ( 

1887 USE_HTTP_NBD_SERVERS and 

1888 attach_from_config and 

1889 self.path.startswith('/dev/http-nbd/') 

1890 ): 

1891 return self._attach_using_http_nbd() 

1892 

1893 # Ensure we have a path... 

1894 chain = self.linstorcowutil.create_chain_paths( 

1895 self.uuid, 

1896 readonly=not writable, 

1897 cb_openers=cleanup.LinstorSR.abort_gc_from_openers_vdi 

1898 ) 

1899 chain.close() 

1900 

1901 self.attached = True 

1902 return VDI.VDI.attach(self, self.sr.uuid, self.uuid) 

1903 

1904 @override 

1905 def detach(self, sr_uuid, vdi_uuid) -> None: 

1906 util.SMlog('LinstorVDI.detach for {}'.format(self.uuid)) 

1907 detach_from_config = self.sr.srcmd.cmd == 'vdi_detach_from_config' 

1908 self.attached = False 

1909 

1910 if detach_from_config and self.path.startswith('/dev/http-nbd/'): 

1911 return self._detach_using_http_nbd() 

1912 

1913 if not VdiType.isCowImage(self.vdi_type): 

1914 return 

1915 

1916 # The VDI is already deflated if the COW image size + metadata is 

1917 # equal to the LINSTOR volume size. 

1918 volume_size = self.linstorcowutil.compute_volume_size(self.size) 

1919 already_deflated = self.capacity <= volume_size 

1920 

1921 if already_deflated: 

1922 util.SMlog( 

1923 'VDI {} already deflated (old volume size={}, volume size={})' 

1924 .format(self.uuid, self.capacity, volume_size) 

1925 ) 

1926 

1927 need_deflate = True 

1928 if already_deflated: 

1929 need_deflate = False 

1930 elif self.sr._provisioning == 'thick': 

1931 need_deflate = False 

1932 

1933 vdi_ref = self.sr.srcmd.params['vdi_ref'] 

1934 if self.session.xenapi.VDI.get_is_a_snapshot(vdi_ref): 

1935 need_deflate = True 

1936 

1937 if need_deflate: 

1938 try: 

1939 self._prepare_thin(False) 

1940 except Exception as e: 

1941 raise xs_errors.XenError( 

1942 'VDIUnavailable', 

1943 opterr='Failed to detach VDI during "prepare thin": {}' 

1944 .format(e) 

1945 ) 

1946 

1947 # We remove only on slaves because the volume can be used by the GC. 

1948 if self.sr.is_master(): 

1949 return 

1950 

1951 while vdi_uuid: 

1952 try: 

1953 path = self._linstor.build_device_path(self._linstor.get_volume_name(vdi_uuid)) 

1954 parent_vdi_uuid = self.linstorcowutil.get_info(vdi_uuid).parentUuid 

1955 except Exception: 

1956 break 

1957 

1958 if util.pathexists(path): 

1959 try: 

1960 self._linstor.remove_volume_if_diskless(vdi_uuid) 

1961 except Exception as e: 

1962 # Ensure we can always detach properly. 

1963 # I don't want to corrupt the XAPI info. 

1964 util.SMlog('Failed to clean VDI {} during detach: {}'.format(vdi_uuid, e)) 

1965 vdi_uuid = parent_vdi_uuid 

1966 

1967 @override 

1968 def resize(self, sr_uuid, vdi_uuid, size) -> str: 

1969 util.SMlog('LinstorVDI.resize for {}'.format(self.uuid)) 

1970 if not self.sr.is_master(): 

1971 raise xs_errors.XenError( 

1972 'VDISize', 

1973 opterr='resize on slave not allowed' 

1974 ) 

1975 

1976 if self.hidden: 

1977 raise xs_errors.XenError('VDIUnavailable', opterr='hidden VDI') 

1978 

1979 # Compute the virtual COW and DRBD volume size. 

1980 size = self.linstorcowutil.cowutil.validateAndRoundImageSize(int(size)) 

1981 volume_size = self.linstorcowutil.compute_volume_size(size) 

1982 util.SMlog( 

1983 'LinstorVDI.resize: type={}, cow-size={}, volume-size={}' 

1984 .format(self.vdi_type, size, volume_size) 

1985 ) 

1986 

1987 if size < self.size: 

1988 util.SMlog( 

1989 'vdi_resize: shrinking not supported: ' 

1990 '(current size: {}, new size: {})'.format(self.size, size) 

1991 ) 

1992 raise xs_errors.XenError('VDISize', opterr='shrinking not allowed') 

1993 

1994 if size == self.size: 

1995 return VDI.VDI.get_params(self) # No change needed 

1996 

1997 # Compute VDI sizes 

1998 if not VdiType.isCowImage(self.vdi_type): 

1999 old_volume_size = self.size 

2000 new_volume_size = LinstorVolumeManager.round_up_volume_size(size) 

2001 else: 

2002 old_volume_size = self.utilisation 

2003 if self.sr._provisioning == 'thin': 

2004 # VDI is currently deflated, so keep it deflated. 

2005 new_volume_size = old_volume_size 

2006 else: 

2007 new_volume_size = self.linstorcowutil.compute_volume_size(size) 

2008 assert new_volume_size >= old_volume_size 

2009 

2010 space_needed = new_volume_size - old_volume_size 

2011 self.sr._ensure_space_available(space_needed) 

2012 

2013 old_size = self.size 

2014 if not VdiType.isCowImage(self.vdi_type): 

2015 self._linstor.resize_volume(self.uuid, new_volume_size) 

2016 else: 

2017 if new_volume_size != old_volume_size: 

2018 self.linstorcowutil.inflate( 

2019 self.sr._get_journaler(), self.uuid, self.path, 

2020 new_volume_size, old_volume_size 

2021 ) 

2022 self.linstorcowutil.set_size_virt_fast(self.path, size) 

2023 

2024 # Reload size attributes. 

2025 self._load_this() 

2026 

2027 # Update metadata 

2028 vdi_ref = self.sr.srcmd.params['vdi_ref'] 

2029 self.session.xenapi.VDI.set_virtual_size(vdi_ref, str(self.size)) 

2030 self.session.xenapi.VDI.set_physical_utilisation( 

2031 vdi_ref, str(self.utilisation) 

2032 ) 

2033 self.sr._update_stats(self.size - old_size) 

2034 return VDI.VDI.get_params(self) 

2035 

2036 @override 

2037 def clone(self, sr_uuid, vdi_uuid) -> str: 

2038 return self._do_snapshot(sr_uuid, vdi_uuid, VDI.SNAPSHOT_DOUBLE) 

2039 

2040 @override 

2041 def compose(self, sr_uuid, vdi1, vdi2) -> None: 

2042 util.SMlog('VDI.compose for {} -> {}'.format(vdi2, vdi1)) 

2043 if not VdiType.isCowImage(self.vdi_type): 

2044 raise xs_errors.XenError('Unimplemented') 

2045 

2046 parent_uuid = vdi1 

2047 parent_path = self._linstor.get_device_path(parent_uuid) 

2048 

2049 # We must pause tapdisk to correctly change the parent. Otherwise we 

2050 # have a readonly error. 

2051 # See: https://github.com/xapi-project/xen-api/blob/b3169a16d36dae0654881b336801910811a399d9/ocaml/xapi/storage_migrate.ml#L928-L929 

2052 # and: https://github.com/xapi-project/xen-api/blob/b3169a16d36dae0654881b336801910811a399d9/ocaml/xapi/storage_migrate.ml#L775 

2053 

2054 if not blktap2.VDI.tap_pause(self.session, self.sr.uuid, self.uuid): 

2055 raise util.SMException('Failed to pause VDI {}'.format(self.uuid)) 

2056 try: 

2057 self.linstorcowutil.set_parent(self.path, parent_path, False) 

2058 self.linstorcowutil.set_hidden(parent_path) 

2059 self.sr.session.xenapi.VDI.set_managed( 

2060 self.sr.srcmd.params['args'][0], False 

2061 ) 

2062 finally: 

2063 blktap2.VDI.tap_unpause(self.session, self.sr.uuid, self.uuid) 

2064 

2065 if not blktap2.VDI.tap_refresh(self.session, self.sr.uuid, self.uuid): 

2066 raise util.SMException( 

2067 'Failed to refresh VDI {}'.format(self.uuid) 

2068 ) 

2069 

2070 util.SMlog('Compose done') 

2071 

2072 @override 

2073 def generate_config(self, sr_uuid, vdi_uuid) -> str: 

2074 """ 

2075 Generate the XML config required to attach and activate 

2076 a VDI for use when XAPI is not running. Attach and 

2077 activation is handled by vdi_attach_from_config below. 

2078 """ 

2079 

2080 util.SMlog('LinstorVDI.generate_config for {}'.format(self.uuid)) 

2081 

2082 resp = {} 

2083 resp['device_config'] = self.sr.dconf 

2084 resp['sr_uuid'] = sr_uuid 

2085 resp['vdi_uuid'] = self.uuid 

2086 resp['sr_sm_config'] = self.sr.sm_config 

2087 resp['command'] = 'vdi_attach_from_config' 

2088 

2089 # By default, we generate a normal config. 

2090 # But if the disk is persistent, we must use a HTTP/NBD 

2091 # server to ensure we can always write or read data. 

2092 # Why? DRBD is unsafe when used with more than 4 hosts: 

2093 # We are limited to use 1 diskless and 3 full. 

2094 # We can't increase this limitation, so we use a NBD/HTTP device 

2095 # instead. 

2096 volume_name = self._linstor.get_volume_name(self.uuid) 

2097 if not USE_HTTP_NBD_SERVERS or volume_name not in [ 

2098 HA_VOLUME_NAME, REDO_LOG_VOLUME_NAME 

2099 ]: 

2100 if not self.path or not util.pathexists(self.path): 

2101 available = False 

2102 # Try to refresh symlink path... 

2103 try: 

2104 self.path = self._linstor.get_device_path(vdi_uuid) 

2105 available = util.pathexists(self.path) 

2106 except Exception: 

2107 pass 

2108 if not available: 

2109 raise xs_errors.XenError('VDIUnavailable') 

2110 

2111 resp['vdi_path'] = self.path 

2112 else: 

2113 # Axiom: DRBD device is present on at least one host. 

2114 resp['vdi_path'] = '/dev/http-nbd/' + volume_name 

2115 

2116 config = xmlrpc.client.dumps(tuple([resp]), 'vdi_attach_from_config') 

2117 return xmlrpc.client.dumps((config,), "", True) 

2118 

2119 @override 

2120 def attach_from_config(self, sr_uuid, vdi_uuid) -> str: 

2121 """ 

2122 Attach and activate a VDI using config generated by 

2123 vdi_generate_config above. This is used for cases such as 

2124 the HA state-file and the redo-log. 

2125 """ 

2126 

2127 util.SMlog('LinstorVDI.attach_from_config for {}'.format(vdi_uuid)) 

2128 

2129 try: 

2130 if not util.pathexists(self.sr.path): 

2131 self.sr.attach(sr_uuid) 

2132 

2133 if not DRIVER_CONFIG['ATTACH_FROM_CONFIG_WITH_TAPDISK']: 

2134 return self.attach(sr_uuid, vdi_uuid) 

2135 except Exception: 

2136 util.logException('LinstorVDI.attach_from_config') 

2137 raise xs_errors.XenError( 

2138 'SRUnavailable', 

2139 opterr='Unable to attach from config' 

2140 ) 

2141 return '' 

2142 

2143 def reset_leaf(self, sr_uuid, vdi_uuid): 

2144 if not VdiType.isCowImage(self.vdi_type): 

2145 raise xs_errors.XenError('Unimplemented') 

2146 

2147 if not self.linstorcowutil.has_parent(self.uuid): 

2148 raise util.SMException( 

2149 'ERROR: VDI {} has no parent, will not reset contents' 

2150 .format(self.uuid) 

2151 ) 

2152 

2153 self.linstorcowutil.kill_data(self.path) 

2154 

2155 def _load_this(self): 

2156 volume_metadata = None 

2157 if self.sr._all_volume_metadata_cache: 

2158 volume_metadata = self.sr._all_volume_metadata_cache.get(self.uuid) 

2159 assert volume_metadata 

2160 else: 

2161 volume_metadata = self._linstor.get_volume_metadata(self.uuid) 

2162 

2163 volume_info = None 

2164 if self.sr._all_volume_info_cache: 

2165 volume_info = self.sr._all_volume_info_cache.get(self.uuid) 

2166 assert volume_info 

2167 else: 

2168 volume_info = self._linstor.get_volume_info(self.uuid) 

2169 

2170 # Contains the max physical size used on a disk. 

2171 # When LINSTOR LVM driver is used, the size should be similar to 

2172 # virtual size (i.e. the LINSTOR max volume size). 

2173 # When LINSTOR Thin LVM driver is used, the used physical size should 

2174 # be lower than virtual size at creation. 

2175 # The physical size increases after each write in a new block. 

2176 self.utilisation = volume_info.allocated_size 

2177 self.capacity = volume_info.virtual_size 

2178 

2179 if not VdiType.isCowImage(self.vdi_type): 

2180 self.hidden = int(volume_metadata.get(HIDDEN_TAG) or 0) 

2181 self.size = volume_info.virtual_size 

2182 self.parent = '' 

2183 else: 

2184 if self.sr._multi_cowutil: 

2185 cowutil_instance = self.sr._multi_cowutil.get_local_cowutil(self.vdi_type) 

2186 else: 

2187 cowutil_instance = self.linstorcowutil 

2188 

2189 image_info = cowutil_instance.get_info(self.uuid) 

2190 self.hidden = image_info.hidden 

2191 self.size = image_info.sizeVirt 

2192 self.parent = image_info.parentUuid 

2193 

2194 if self.hidden: 

2195 self.managed = False 

2196 

2197 self.label = volume_metadata.get(NAME_LABEL_TAG) or '' 

2198 self.description = volume_metadata.get(NAME_DESCRIPTION_TAG) or '' 

2199 

2200 # Update sm_config_override of VDI parent class. 

2201 self.sm_config_override = {'vhd-parent': self.parent or None} 

2202 

2203 def _mark_hidden(self, hidden=True): 

2204 if self.hidden == hidden: 

2205 return 

2206 

2207 if VdiType.isCowImage(self.vdi_type): 

2208 self.linstorcowutil.set_hidden(self.path, hidden) 

2209 else: 

2210 self._linstor.update_volume_metadata(self.uuid, { 

2211 HIDDEN_TAG: hidden 

2212 }) 

2213 self.hidden = hidden 

2214 

2215 @override 

2216 def update(self, sr_uuid, vdi_uuid) -> None: 

2217 xenapi = self.session.xenapi 

2218 vdi_ref = xenapi.VDI.get_by_uuid(self.uuid) 

2219 

2220 volume_metadata = { 

2221 NAME_LABEL_TAG: util.to_plain_string( 

2222 xenapi.VDI.get_name_label(vdi_ref) 

2223 ), 

2224 NAME_DESCRIPTION_TAG: util.to_plain_string( 

2225 xenapi.VDI.get_name_description(vdi_ref) 

2226 ) 

2227 } 

2228 

2229 try: 

2230 self._linstor.update_volume_metadata(self.uuid, volume_metadata) 

2231 except LinstorVolumeManagerError as e: 

2232 if e.code == LinstorVolumeManagerError.ERR_VOLUME_NOT_EXISTS: 

2233 raise xs_errors.XenError( 

2234 'VDIUnavailable', 

2235 opterr='LINSTOR volume {} not found'.format(self.uuid) 

2236 ) 

2237 raise xs_errors.XenError('VDIUnavailable', opterr=str(e)) 

2238 

2239 # -------------------------------------------------------------------------- 

2240 # Thin provisioning. 

2241 # -------------------------------------------------------------------------- 

2242 

2243 def _prepare_thin(self, attach): 

2244 if self.sr.is_master(): 

2245 if attach: 

2246 attach_thin( 

2247 self.session, self.sr._get_journaler(), self._linstor, 

2248 self.sr.uuid, self.uuid 

2249 ) 

2250 else: 

2251 detach_thin( 

2252 self.session, self._linstor, self.sr.uuid, self.uuid 

2253 ) 

2254 else: 

2255 fn = 'attach' if attach else 'detach' 

2256 

2257 master = util.get_master_ref(self.session) 

2258 

2259 args = { 

2260 'groupName': self.sr._group_name, 

2261 'srUuid': self.sr.uuid, 

2262 'vdiUuid': self.uuid 

2263 } 

2264 

2265 try: 

2266 self.sr._exec_manager_command(master, fn, args, 'VDIUnavailable') 

2267 except Exception: 

2268 if fn != 'detach': 

2269 raise 

2270 

2271 # Reload size attrs after inflate or deflate! 

2272 self._load_this() 

2273 self.sr._update_physical_size() 

2274 

2275 vdi_ref = self.sr.srcmd.params['vdi_ref'] 

2276 self.session.xenapi.VDI.set_physical_utilisation( 

2277 vdi_ref, str(self.utilisation) 

2278 ) 

2279 

2280 self.session.xenapi.SR.set_physical_utilisation( 

2281 self.sr.sr_ref, str(self.sr.physical_utilisation) 

2282 ) 

2283 

2284 # -------------------------------------------------------------------------- 

2285 # Generic helpers. 

2286 # -------------------------------------------------------------------------- 

2287 

2288 def _set_type(self, vdi_type: str) -> None: 

2289 self.vdi_type = vdi_type 

2290 self.linstorcowutil = LinstorCowUtil(self.session, self.sr._linstor_proxy, self.vdi_type) 

2291 

2292 def _determine_type_and_path(self): 

2293 """ 

2294 Determine whether this is a RAW or a COW VDI. 

2295 """ 

2296 

2297 if self.sr._all_volume_metadata_cache: 

2298 # We are currently loading all volumes. 

2299 volume_metadata = self.sr._all_volume_metadata_cache.get(self.uuid) 

2300 if not volume_metadata: 

2301 raise xs_errors.XenError( 

2302 'VDIUnavailable', 

2303 opterr='failed to get metadata' 

2304 ) 

2305 else: 

2306 # Simple load. 

2307 volume_metadata = self._linstor.get_volume_metadata(self.uuid) 

2308 

2309 # Set type and path. 

2310 vdi_type = volume_metadata.get(VDI_TYPE_TAG) 

2311 if not vdi_type: 

2312 raise xs_errors.XenError( 

2313 'VDIUnavailable', 

2314 opterr='failed to get vdi_type in metadata' 

2315 ) 

2316 self._set_type(vdi_type) 

2317 

2318 self._update_device_name(self._linstor.get_volume_name(self.uuid)) 

2319 

2320 def _update_device_name(self, device_name): 

2321 self._device_name = device_name 

2322 

2323 # Mark path of VDI parent class. 

2324 if device_name: 

2325 self.path = self._linstor.build_device_path(self._device_name) 

2326 else: 

2327 self.path = None 

2328 

2329 def _create_snapshot(self, snap_vdi_type, snap_uuid, snap_of_uuid=None): 

2330 """ 

2331 Snapshot self and return the snapshot VDI object. 

2332 """ 

2333 

2334 # 1. Create a new LINSTOR volume with the same size than self. 

2335 snap_path = self._linstor.shallow_clone_volume( 

2336 self.uuid, snap_uuid, persistent=False 

2337 ) 

2338 

2339 # 2. Write the snapshot content. 

2340 is_raw = (self.vdi_type == VdiType.RAW) 

2341 self.linstorcowutil.snapshot( 

2342 snap_path, self.path, is_raw, max(self.size, self.linstorcowutil.cowutil.getDefaultPreallocationSizeVirt()) 

2343 ) 

2344 

2345 # 3. Get snapshot parent. 

2346 snap_parent = self.linstorcowutil.get_parent(snap_uuid) 

2347 

2348 # 4. Update metadata. 

2349 util.SMlog('Set VDI {} metadata of snapshot'.format(snap_uuid)) 

2350 volume_metadata = { 

2351 NAME_LABEL_TAG: util.to_plain_string(self.label), 

2352 NAME_DESCRIPTION_TAG: util.to_plain_string(self.description), 

2353 IS_A_SNAPSHOT_TAG: bool(snap_of_uuid), 

2354 SNAPSHOT_OF_TAG: snap_of_uuid, 

2355 SNAPSHOT_TIME_TAG: '', 

2356 TYPE_TAG: self.ty, 

2357 VDI_TYPE_TAG: snap_vdi_type, 

2358 READ_ONLY_TAG: False, 

2359 METADATA_OF_POOL_TAG: '' 

2360 } 

2361 self._linstor.set_volume_metadata(snap_uuid, volume_metadata) 

2362 

2363 # 5. Set size. 

2364 snap_vdi = LinstorVDI(self.sr, snap_uuid) 

2365 if not snap_vdi._exists: 

2366 raise xs_errors.XenError('VDISnapshot') 

2367 

2368 volume_info = self._linstor.get_volume_info(snap_uuid) 

2369 

2370 snap_vdi.size = self.linstorcowutil.get_size_virt(snap_uuid) 

2371 snap_vdi.utilisation = volume_info.allocated_size 

2372 

2373 # 6. Update sm config. 

2374 snap_vdi.sm_config = {} 

2375 snap_vdi.sm_config['vdi_type'] = snap_vdi.vdi_type 

2376 if snap_parent: 

2377 snap_vdi.sm_config['vhd-parent'] = snap_parent 

2378 snap_vdi.parent = snap_parent 

2379 

2380 snap_vdi.label = self.label 

2381 snap_vdi.description = self.description 

2382 

2383 self._linstor.mark_volume_as_persistent(snap_uuid) 

2384 

2385 return snap_vdi 

2386 

2387 # -------------------------------------------------------------------------- 

2388 # Implement specific SR methods. 

2389 # -------------------------------------------------------------------------- 

2390 

2391 @override 

2392 def _rename(self, oldpath, newpath) -> None: 

2393 # TODO: I'm not sure... Used by CBT. 

2394 volume_uuid = self._linstor.get_volume_uuid_from_device_path(oldpath) 

2395 self._linstor.update_volume_name(volume_uuid, newpath) 

2396 

2397 @override 

2398 def _do_snapshot(self, sr_uuid, vdi_uuid, snapType, 

2399 cloneOp=False, secondary=None, cbtlog=None, is_mirror_destination=False) -> str: 

2400 # If cbt enabled, save file consistency state. 

2401 if cbtlog is not None: 

2402 if blktap2.VDI.tap_status(self.session, vdi_uuid): 

2403 consistency_state = False 

2404 else: 

2405 consistency_state = True 

2406 util.SMlog( 

2407 'Saving log consistency state of {} for vdi: {}' 

2408 .format(consistency_state, vdi_uuid) 

2409 ) 

2410 else: 

2411 consistency_state = None 

2412 

2413 if not VdiType.isCowImage(self.vdi_type): 

2414 raise xs_errors.XenError('Unimplemented') 

2415 

2416 if not blktap2.VDI.tap_pause(self.session, sr_uuid, vdi_uuid): 

2417 raise util.SMException('Failed to pause VDI {}'.format(vdi_uuid)) 

2418 try: 

2419 return self._snapshot(snapType, cbtlog, consistency_state) 

2420 finally: 

2421 self.disable_leaf_on_secondary(vdi_uuid, secondary=secondary) 

2422 blktap2.VDI.tap_unpause(self.session, sr_uuid, vdi_uuid, secondary) 

2423 self.sr.database_backup("snapshot") 

2424 

2425 def _snapshot(self, snap_type, cbtlog=None, cbt_consistency=None): 

2426 util.SMlog( 

2427 'LinstorVDI._snapshot for {} (type {})' 

2428 .format(self.uuid, snap_type) 

2429 ) 

2430 

2431 # 1. Checks... 

2432 if self.hidden: 

2433 raise xs_errors.XenError('VDIClone', opterr='hidden VDI') 

2434 

2435 snap_vdi_type = self.sr._get_snap_vdi_type(self.vdi_type, self.size) 

2436 

2437 depth = self.linstorcowutil.get_depth(self.uuid) 

2438 if depth == -1: 

2439 raise xs_errors.XenError( 

2440 'VDIUnavailable', 

2441 opterr='failed to get COW depth' 

2442 ) 

2443 elif depth >= self.linstorcowutil.cowutil.getMaxChainLength(): 

2444 raise xs_errors.XenError('SnapshotChainTooLong') 

2445 

2446 # Ensure we have a valid path if we don't have a local diskful. 

2447 chain = self.linstorcowutil.create_chain_paths( 

2448 self.uuid, 

2449 readonly=True, 

2450 cb_openers=cleanup.LinstorSR.abort_gc_from_openers_vdi 

2451 ) 

2452 chain.close() 

2453 

2454 volume_path = self.path 

2455 if not util.pathexists(volume_path): 

2456 raise xs_errors.XenError( 

2457 'EIO', 

2458 opterr='IO error checking path {}'.format(volume_path) 

2459 ) 

2460 

2461 # 2. Create base and snap uuid (if required) and a journal entry. 

2462 base_uuid = util.gen_uuid() 

2463 snap_uuid = None 

2464 

2465 if snap_type == VDI.SNAPSHOT_DOUBLE: 

2466 snap_uuid = util.gen_uuid() 

2467 

2468 clone_info = '{}_{}'.format(base_uuid, snap_uuid) 

2469 

2470 active_uuid = self.uuid 

2471 

2472 journaler = self.sr._get_journaler() 

2473 journaler.create( 

2474 LinstorJournaler.CLONE, active_uuid, clone_info 

2475 ) 

2476 

2477 try: 

2478 # 3. Self becomes the new base. 

2479 # The device path remains the same. 

2480 self._linstor.update_volume_uuid(self.uuid, base_uuid) 

2481 self.uuid = base_uuid 

2482 self.location = self.uuid 

2483 self.read_only = True 

2484 self.managed = False 

2485 

2486 # 4. Create snapshots (new active and snap). 

2487 active_vdi = self._create_snapshot(snap_vdi_type, active_uuid) 

2488 

2489 snap_vdi = None 

2490 if snap_type == VDI.SNAPSHOT_DOUBLE: 

2491 snap_vdi = self._create_snapshot(snap_vdi_type, snap_uuid, active_uuid) 

2492 

2493 self.label = 'base copy' 

2494 self.description = '' 

2495 

2496 # 5. Mark the base VDI as hidden so that it does not show up 

2497 # in subsequent scans. 

2498 self._mark_hidden() 

2499 self._linstor.update_volume_metadata( 

2500 self.uuid, {READ_ONLY_TAG: True} 

2501 ) 

2502 

2503 # 6. We must update the new active VDI with the "paused" and 

2504 # "host_" properties. Why? Because the original VDI has been 

2505 # paused and we we must unpause it after the snapshot. 

2506 # See: `tap_unpause` in `blktap2.py`. 

2507 vdi_ref = self.session.xenapi.VDI.get_by_uuid(active_uuid) 

2508 sm_config = self.session.xenapi.VDI.get_sm_config(vdi_ref) 

2509 for key in [x for x in sm_config.keys() if x == 'paused' or x.startswith('host_')]: 

2510 active_vdi.sm_config[key] = sm_config[key] 

2511 

2512 # 7. Verify parent locator field of both children and 

2513 # delete base if unused. 

2514 introduce_parent = True 

2515 try: 

2516 snap_parent = None 

2517 if snap_vdi: 

2518 snap_parent = snap_vdi.parent 

2519 

2520 if active_vdi.parent != self.uuid and ( 

2521 snap_type == VDI.SNAPSHOT_SINGLE or 

2522 snap_type == VDI.SNAPSHOT_INTERNAL or 

2523 snap_parent != self.uuid 

2524 ): 

2525 util.SMlog( 

2526 'Destroy unused base volume: {} (path={})' 

2527 .format(self.uuid, self.path) 

2528 ) 

2529 introduce_parent = False 

2530 self._linstor.destroy_volume(self.uuid) 

2531 except Exception as e: 

2532 util.SMlog('Ignoring exception: {}'.format(e)) 

2533 pass 

2534 

2535 # 8. Introduce the new VDI records. 

2536 if snap_vdi: 

2537 # If the parent is encrypted set the key_hash for the 

2538 # new snapshot disk. 

2539 vdi_ref = self.sr.srcmd.params['vdi_ref'] 

2540 sm_config = self.session.xenapi.VDI.get_sm_config(vdi_ref) 

2541 # TODO: Maybe remove key_hash support. 

2542 if 'key_hash' in sm_config: 

2543 snap_vdi.sm_config['key_hash'] = sm_config['key_hash'] 

2544 # If we have CBT enabled on the VDI, 

2545 # set CBT status for the new snapshot disk. 

2546 if cbtlog: 

2547 snap_vdi.cbt_enabled = True 

2548 

2549 if snap_vdi: 

2550 snap_vdi_ref = snap_vdi._db_introduce() 

2551 util.SMlog( 

2552 'vdi_clone: introduced VDI: {} ({})' 

2553 .format(snap_vdi_ref, snap_vdi.uuid) 

2554 ) 

2555 if introduce_parent: 

2556 base_vdi_ref = self._db_introduce() 

2557 self.session.xenapi.VDI.set_managed(base_vdi_ref, False) 

2558 util.SMlog( 

2559 'vdi_clone: introduced VDI: {} ({})' 

2560 .format(base_vdi_ref, self.uuid) 

2561 ) 

2562 self._linstor.update_volume_metadata(self.uuid, { 

2563 NAME_LABEL_TAG: util.to_plain_string(self.label), 

2564 NAME_DESCRIPTION_TAG: util.to_plain_string( 

2565 self.description 

2566 ), 

2567 READ_ONLY_TAG: True, 

2568 METADATA_OF_POOL_TAG: '' 

2569 }) 

2570 

2571 # 9. Update cbt files if user created snapshot (SNAPSHOT_DOUBLE) 

2572 if snap_type == VDI.SNAPSHOT_DOUBLE and cbtlog: 

2573 try: 

2574 self._cbt_snapshot(snap_uuid, cbt_consistency) 

2575 except Exception: 

2576 # CBT operation failed. 

2577 # TODO: Implement me. 

2578 raise 

2579 

2580 if snap_type != VDI.SNAPSHOT_INTERNAL: 

2581 self.sr._update_stats(self.size) 

2582 

2583 # 10. Return info on the new user-visible leaf VDI. 

2584 ret_vdi = snap_vdi 

2585 if not ret_vdi: 

2586 ret_vdi = self 

2587 if not ret_vdi: 

2588 ret_vdi = active_vdi 

2589 

2590 vdi_ref = self.sr.srcmd.params['vdi_ref'] 

2591 self.session.xenapi.VDI.set_sm_config( 

2592 vdi_ref, active_vdi.sm_config 

2593 ) 

2594 except Exception as e: 

2595 util.logException('Failed to snapshot!') 

2596 try: 

2597 self.sr._handle_interrupted_clone( 

2598 active_uuid, clone_info, force_undo=True 

2599 ) 

2600 journaler.remove(LinstorJournaler.CLONE, active_uuid) 

2601 except Exception as clean_error: 

2602 util.SMlog( 

2603 'WARNING: Failed to clean up failed snapshot: {}' 

2604 .format(clean_error) 

2605 ) 

2606 raise xs_errors.XenError('VDIClone', opterr=str(e)) 

2607 

2608 journaler.remove(LinstorJournaler.CLONE, active_uuid) 

2609 

2610 return ret_vdi.get_params() 

2611 

2612 @staticmethod 

2613 def _start_persistent_http_server(volume_name): 

2614 pid_path = None 

2615 http_server = None 

2616 

2617 try: 

2618 if volume_name == HA_VOLUME_NAME: 

2619 port = '8076' 

2620 else: 

2621 port = '8077' 

2622 

2623 try: 

2624 # Use a timeout call because XAPI may be unusable on startup 

2625 # or if the host has been ejected. So in this case the call can 

2626 # block indefinitely. 

2627 api_session = util.timeout(5, util.ApiSession, "SM-ha-linstor-http-server") 

2628 session = api_session.session 

2629 host_ip = util.get_this_host_address(session) 

2630 except: 

2631 # Fallback using the XHA file if session not available. 

2632 host_ip, _ = get_ips_from_xha_config_file() 

2633 if not host_ip: 

2634 raise Exception( 

2635 'Cannot start persistent HTTP server: no XAPI session, nor XHA config file' 

2636 ) 

2637 finally: 

2638 api_session.logout() 

2639 

2640 arguments = [ 

2641 'http-disk-server', 

2642 '--disk', 

2643 '/dev/drbd/by-res/{}/0'.format(volume_name), 

2644 '--ip', 

2645 host_ip, 

2646 '--port', 

2647 port 

2648 ] 

2649 

2650 util.SMlog('Starting {} on port {}...'.format(arguments[0], port)) 

2651 http_server = subprocess.Popen( 

2652 [FORK_LOG_DAEMON] + arguments, 

2653 stdout=subprocess.PIPE, 

2654 stderr=subprocess.STDOUT, 

2655 universal_newlines=True, 

2656 # Ensure we use another group id to kill this process without 

2657 # touch the current one. 

2658 preexec_fn=os.setsid 

2659 ) 

2660 

2661 pid_path = '/run/http-server-{}.pid'.format(volume_name) 

2662 with open(pid_path, 'w') as pid_file: 

2663 pid_file.write(str(http_server.pid)) 

2664 

2665 reg_server_ready = re.compile("Server ready!$") 

2666 def is_ready(): 

2667 while http_server.poll() is None: 

2668 line = http_server.stdout.readline() 

2669 if reg_server_ready.search(line): 

2670 return True 

2671 return False 

2672 try: 

2673 if not util.timeout(10, is_ready): 

2674 raise Exception('Failed to wait HTTP server startup, bad output') 

2675 except util.TimeoutException: 

2676 raise Exception('Failed to wait for HTTP server startup during given delay') 

2677 except Exception as e: 

2678 if pid_path: 

2679 try: 

2680 os.remove(pid_path) 

2681 except Exception: 

2682 pass 

2683 

2684 if http_server: 

2685 # Kill process and children in this case... 

2686 try: 

2687 os.killpg(os.getpgid(http_server.pid), signal.SIGTERM) 

2688 except: 

2689 pass 

2690 

2691 raise xs_errors.XenError( 

2692 'VDIUnavailable', 

2693 opterr='Failed to start http-server: {}'.format(e) 

2694 ) 

2695 

2696 def _start_persistent_nbd_server(self, volume_name): 

2697 pid_path = None 

2698 nbd_path = None 

2699 nbd_server = None 

2700 

2701 try: 

2702 # We use a precomputed device size. 

2703 # So if the XAPI is modified, we must update these values! 

2704 if volume_name == HA_VOLUME_NAME: 

2705 # See: https://github.com/xapi-project/xen-api/blob/703479fa448a8d7141954bb6e8964d8e25c4ac2e/ocaml/xapi/xha_statefile.ml#L32-L37 

2706 port = '8076' 

2707 device_size = 4 * 1024 * 1024 

2708 else: 

2709 # See: https://github.com/xapi-project/xen-api/blob/703479fa448a8d7141954bb6e8964d8e25c4ac2e/ocaml/database/redo_log.ml#L41-L44 

2710 port = '8077' 

2711 device_size = 256 * 1024 * 1024 

2712 

2713 try: 

2714 api_session = util.timeout(5, util.ApiSession, "SM-ha-linstor-nbd-server") 

2715 session = api_session.session 

2716 ips = util.get_host_addresses(session) 

2717 except Exception as e: 

2718 _, ips = get_ips_from_xha_config_file() 

2719 if not ips: 

2720 raise Exception( 

2721 'Cannot start persistent NBD server: no XAPI session, nor XHA config file ({})'.format(e) 

2722 ) 

2723 ips = ips.values() 

2724 finally: 

2725 api_session.logout() 

2726 

2727 arguments = [ 

2728 'nbd-http-server', 

2729 '--socket-path', 

2730 '/run/{}.socket'.format(volume_name), 

2731 '--nbd-name', 

2732 volume_name, 

2733 '--urls', 

2734 ','.join(['http://' + ip + ':' + port for ip in ips]), 

2735 '--device-size', 

2736 str(device_size) 

2737 ] 

2738 

2739 util.SMlog('Starting {} using port {}...'.format(arguments[0], port)) 

2740 nbd_server = subprocess.Popen( 

2741 [FORK_LOG_DAEMON] + arguments, 

2742 stdout=subprocess.PIPE, 

2743 stderr=subprocess.STDOUT, 

2744 universal_newlines=True, 

2745 # Ensure we use another group id to kill this process without 

2746 # touch the current one. 

2747 preexec_fn=os.setsid 

2748 ) 

2749 

2750 pid_path = '/run/nbd-server-{}.pid'.format(volume_name) 

2751 with open(pid_path, 'w') as pid_file: 

2752 pid_file.write(str(nbd_server.pid)) 

2753 

2754 reg_nbd_path = re.compile("NBD `(/dev/nbd[0-9]+)` is now attached.$") 

2755 def get_nbd_path(): 

2756 while nbd_server.poll() is None: 

2757 line = nbd_server.stdout.readline() 

2758 match = reg_nbd_path.search(line) 

2759 if match: 

2760 return match.group(1) 

2761 # Use a timeout to never block the smapi if there is a problem. 

2762 try: 

2763 nbd_path = util.timeout(10, get_nbd_path) 

2764 if nbd_path is None: 

2765 raise Exception('Empty NBD path (NBD server is probably dead)') 

2766 except util.TimeoutException: 

2767 raise Exception('Unable to read NBD path') 

2768 

2769 util.SMlog('Create symlink: {} -> {}'.format(self.path, nbd_path)) 

2770 os.symlink(nbd_path, self.path) 

2771 except Exception as e: 

2772 if pid_path: 

2773 try: 

2774 os.remove(pid_path) 

2775 except Exception: 

2776 pass 

2777 

2778 if nbd_path: 

2779 try: 

2780 os.remove(nbd_path) 

2781 except Exception: 

2782 pass 

2783 

2784 if nbd_server: 

2785 # Kill process and children in this case... 

2786 try: 

2787 os.killpg(os.getpgid(nbd_server.pid), signal.SIGTERM) 

2788 except: 

2789 pass 

2790 

2791 raise xs_errors.XenError( 

2792 'VDIUnavailable', 

2793 opterr='Failed to start nbd-server: {}'.format(e) 

2794 ) 

2795 

2796 @classmethod 

2797 def _kill_persistent_server(self, type, volume_name, sig): 

2798 try: 

2799 path = '/run/{}-server-{}.pid'.format(type, volume_name) 

2800 if not os.path.exists(path): 

2801 return 

2802 

2803 pid = None 

2804 with open(path, 'r') as pid_file: 

2805 try: 

2806 pid = int(pid_file.read()) 

2807 except Exception: 

2808 pass 

2809 

2810 if pid is not None and util.check_pid_exists(pid): 

2811 util.SMlog('Kill {} server {} (pid={})'.format(type, path, pid)) 

2812 try: 

2813 os.killpg(os.getpgid(pid), sig) 

2814 except Exception as e: 

2815 util.SMlog('Failed to kill {} server: {}'.format(type, e)) 

2816 

2817 os.remove(path) 

2818 except: 

2819 pass 

2820 

2821 @classmethod 

2822 def _kill_persistent_http_server(self, volume_name, sig=signal.SIGTERM): 

2823 return self._kill_persistent_server('nbd', volume_name, sig) 

2824 

2825 @classmethod 

2826 def _kill_persistent_nbd_server(self, volume_name, sig=signal.SIGTERM): 

2827 return self._kill_persistent_server('http', volume_name, sig) 

2828 

2829 def _check_http_nbd_volume_name(self): 

2830 volume_name = self.path[14:] 

2831 if volume_name not in [ 

2832 HA_VOLUME_NAME, REDO_LOG_VOLUME_NAME 

2833 ]: 

2834 raise xs_errors.XenError( 

2835 'VDIUnavailable', 

2836 opterr='Unsupported path: {}'.format(self.path) 

2837 ) 

2838 return volume_name 

2839 

2840 def _attach_using_http_nbd(self): 

2841 volume_name = self._check_http_nbd_volume_name() 

2842 

2843 # Ensure there is no NBD and HTTP server running. 

2844 self._kill_persistent_nbd_server(volume_name) 

2845 self._kill_persistent_http_server(volume_name) 

2846 

2847 # 0. Fetch drbd path. 

2848 must_get_device_path = True 

2849 if not self.sr.is_master(): 

2850 # We are on a slave, we must try to find a diskful locally. 

2851 try: 

2852 volume_info = self._linstor.get_volume_info(self.uuid) 

2853 except Exception as e: 

2854 raise xs_errors.XenError( 

2855 'VDIUnavailable', 

2856 opterr='Cannot get volume info of {}: {}' 

2857 .format(self.uuid, e) 

2858 ) 

2859 

2860 hostname = socket.gethostname() 

2861 must_get_device_path = hostname in volume_info.diskful 

2862 

2863 drbd_path = None 

2864 if must_get_device_path or self.sr.is_master(): 

2865 # If we are master, we must ensure we have a diskless 

2866 # or diskful available to init HA. 

2867 # It also avoid this error in xensource.log 

2868 # (/usr/libexec/xapi/cluster-stack/xhad/ha_set_pool_state): 

2869 # init exited with code 8 [stdout = ''; stderr = 'SF: failed to write in State-File \x10 (fd 4208696). (sys 28)\x0A'] 

2870 # init returned MTC_EXIT_CAN_NOT_ACCESS_STATEFILE (State-File is inaccessible) 

2871 available = False 

2872 try: 

2873 drbd_path = self._linstor.get_device_path(self.uuid) 

2874 available = util.pathexists(drbd_path) 

2875 except Exception: 

2876 pass 

2877 

2878 if not available: 

2879 raise xs_errors.XenError( 

2880 'VDIUnavailable', 

2881 opterr='Cannot get device path of {}'.format(self.uuid) 

2882 ) 

2883 

2884 # 1. Prepare http-nbd folder. 

2885 try: 

2886 if not os.path.exists('/dev/http-nbd/'): 

2887 os.makedirs('/dev/http-nbd/') 

2888 elif os.path.islink(self.path): 

2889 os.remove(self.path) 

2890 except OSError as e: 

2891 if e.errno != errno.EEXIST: 

2892 raise xs_errors.XenError( 

2893 'VDIUnavailable', 

2894 opterr='Cannot prepare http-nbd: {}'.format(e) 

2895 ) 

2896 

2897 # 2. Start HTTP service if we have a diskful or if we are master. 

2898 http_service = None 

2899 if drbd_path: 

2900 assert(drbd_path in ( 

2901 '/dev/drbd/by-res/{}/0'.format(HA_VOLUME_NAME), 

2902 '/dev/drbd/by-res/{}/0'.format(REDO_LOG_VOLUME_NAME) 

2903 )) 

2904 self._start_persistent_http_server(volume_name) 

2905 

2906 # 3. Start NBD server in all cases. 

2907 try: 

2908 self._start_persistent_nbd_server(volume_name) 

2909 except Exception as e: 

2910 if drbd_path: 

2911 self._kill_persistent_http_server(volume_name) 

2912 raise 

2913 

2914 self.attached = True 

2915 return VDI.VDI.attach(self, self.sr.uuid, self.uuid) 

2916 

2917 def _detach_using_http_nbd(self): 

2918 volume_name = self._check_http_nbd_volume_name() 

2919 self._kill_persistent_nbd_server(volume_name) 

2920 self._kill_persistent_http_server(volume_name) 

2921 

2922# ------------------------------------------------------------------------------ 

2923 

2924 

2925if __name__ == '__main__': 2925 ↛ 2926line 2925 didn't jump to line 2926, because the condition on line 2925 was never true

2926 def run(): 

2927 SRCommand.run(LinstorSR, DRIVER_INFO) 

2928 

2929 if not TRACE_PERFS: 

2930 run() 

2931 else: 

2932 util.make_profile('LinstorSR', run) 

2933else: 

2934 SR.registerSR(LinstorSR)