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 

18 

19from constants import CBTLOG_TAG 

20 

21try: 

22 from linstorcowutil import LinstorCowUtil 

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, getImageStringFromVdiType, getVdiTypeFromImageFormat 

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# TODO: Simplify with File SR and LVM SR 

91# Warning: Not the same values than VdiType.*. 

92# These values represents the types given on the command line. 

93CREATE_PARAM_TYPES = { 

94 "raw": VdiType.RAW, 

95 "vhd": VdiType.VHD, 

96 "qcow2": VdiType.QCOW2 

97} 

98 

99# ============================================================================== 

100 

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

102# 'VDI_CONFIG_CBT', 'SR_PROBE' 

103 

104CAPABILITIES = [ 

105 'ATOMIC_PAUSE', 

106 'SR_UPDATE', 

107 'VDI_CREATE', 

108 'VDI_DELETE', 

109 'VDI_UPDATE', 

110 'VDI_ATTACH', 

111 'VDI_DETACH', 

112 'VDI_ACTIVATE', 

113 'VDI_DEACTIVATE', 

114 'VDI_CLONE', 

115 'VDI_MIRROR', 

116 'VDI_RESIZE', 

117 'VDI_SNAPSHOT', 

118 'VDI_GENERATE_CONFIG' 

119] 

120 

121CONFIGURATION = [ 

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

123 ['redundancy', 'replication count'], 

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

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

126] 

127 

128DRIVER_INFO = { 

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

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

131 'vendor': 'Vates', 

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

133 'driver_version': '1.0', 

134 'required_api_version': '1.0', 

135 'capabilities': CAPABILITIES, 

136 'configuration': CONFIGURATION 

137} 

138 

139DRIVER_CONFIG = {'ATTACH_FROM_CONFIG_WITH_TAPDISK': False} 

140 

141OPS_EXCLUSIVE = [ 

142 'sr_create', 'sr_delete', 'sr_attach', 'sr_detach', 'sr_scan', 

143 'sr_update', 'sr_probe', 'vdi_init', 'vdi_create', 'vdi_delete', 

144 'vdi_attach', 'vdi_detach', 'vdi_clone', 'vdi_snapshot', 

145] 

146 

147# ============================================================================== 

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

149# ============================================================================== 

150 

151 

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

153 volume_metadata = linstor.get_volume_metadata(vdi_uuid) 

154 vdi_type = volume_metadata.get(VDI_TYPE_TAG) 

155 if not VdiType.isCowImage(vdi_type): 

156 return 

157 

158 device_path = linstor.get_device_path(vdi_uuid) 

159 

160 linstorcowutil = LinstorCowUtil(session, linstor, vdi_type) 

161 

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

163 # there is nothing to do. 

164 cow_size = linstorcowutil.compute_volume_size( 

165 linstorcowutil.get_size_virt(vdi_uuid) 

166 ) 

167 

168 volume_info = linstor.get_volume_info(vdi_uuid) 

169 volume_size = volume_info.virtual_size 

170 

171 if cow_size > volume_size: 

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

173 

174 

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

176 volume_metadata = linstor.get_volume_metadata(vdi_uuid) 

177 vdi_type = volume_metadata.get(VDI_TYPE_TAG) 

178 if not VdiType.isCowImage(vdi_type): 

179 return 

180 

181 def check_vbd_count(): 

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

183 vbds = session.xenapi.VBD.get_all_records_where( 

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

185 ) 

186 

187 num_plugged = 0 

188 for vbd_rec in vbds.values(): 

189 if vbd_rec['currently_attached']: 

190 num_plugged += 1 

191 if num_plugged > 1: 

192 raise xs_errors.XenError( 

193 'VDIUnavailable', 

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

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

196 ) 

197 

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

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

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

201 

202 device_path = linstor.get_device_path(vdi_uuid) 

203 linstorcowutil = LinstorCowUtil(session, linstor, vdi_type) 

204 new_volume_size = LinstorVolumeManager.round_up_volume_size( 

205 linstorcowutil.get_size_phys(vdi_uuid) 

206 ) 

207 

208 volume_info = linstor.get_volume_info(vdi_uuid) 

209 old_volume_size = volume_info.virtual_size 

210 linstorcowutil.deflate(device_path, new_volume_size, old_volume_size) 

211 

212 

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

214 # This function must always return without errors. 

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

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

217 try: 

218 detach_thin_impl(session, linstor, sr_uuid, vdi_uuid) 

219 except Exception as e: 

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

221 

222 

223def get_ips_from_xha_config_file(): 

224 ips = dict() 

225 host_id = None 

226 try: 

227 # Ensure there is no dirty read problem. 

228 # For example if the HA is reloaded. 

229 tree = util.retry( 

230 lambda: xml_parser.parse(XHA_CONFIG_PATH), 

231 maxretry=10, 

232 period=1 

233 ) 

234 except: 

235 return (None, ips) 

236 

237 def parse_host_nodes(ips, node): 

238 current_id = None 

239 current_ip = None 

240 

241 for sub_node in node: 

242 if sub_node.tag == 'IPaddress': 

243 current_ip = sub_node.text 

244 elif sub_node.tag == 'HostID': 

245 current_id = sub_node.text 

246 else: 

247 continue 

248 

249 if current_id and current_ip: 

250 ips[current_id] = current_ip 

251 return 

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

253 

254 def parse_common_config(ips, node): 

255 for sub_node in node: 

256 if sub_node.tag == 'host': 

257 parse_host_nodes(ips, sub_node) 

258 

259 def parse_local_config(ips, node): 

260 for sub_node in node: 

261 if sub_node.tag == 'localhost': 

262 for host_node in sub_node: 

263 if host_node.tag == 'HostID': 

264 return host_node.text 

265 

266 for node in tree.getroot(): 

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

268 parse_common_config(ips, node) 

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

270 host_id = parse_local_config(ips, node) 

271 else: 

272 continue 

273 

274 if ips and host_id: 

275 break 

276 

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

278 

279 

280def activate_lvm_group(group_name): 

281 path = group_name.split('/') 

282 assert path and len(path) <= 2 

283 try: 

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

285 except Exception as e: 

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

287 

288# ============================================================================== 

289 

290# Usage example: 

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

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

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

294 

295 

296class LinstorSR(SR.SR): 

297 DRIVER_TYPE = 'linstor' 

298 

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

300 PROVISIONING_DEFAULT = 'thin' 

301 

302 MANAGER_PLUGIN = 'linstor-manager' 

303 

304 INIT_STATUS_NOT_SET = 0 

305 INIT_STATUS_IN_PROGRESS = 1 

306 INIT_STATUS_OK = 2 

307 INIT_STATUS_FAIL = 3 

308 

309 # -------------------------------------------------------------------------- 

310 # SR methods. 

311 # -------------------------------------------------------------------------- 

312 

313 _linstor: Optional[LinstorVolumeManager] = None 

314 

315 @override 

316 @staticmethod 

317 def handles(type) -> bool: 

318 return type == LinstorSR.DRIVER_TYPE 

319 

320 def __init__(self, srcmd, sr_uuid): 

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

322 self._init_preferred_image_formats() 

323 

324 @override 

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

326 if not LINSTOR_AVAILABLE: 

327 raise util.SMException( 

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

329 ) 

330 

331 # Check parameters. 

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

333 raise xs_errors.XenError('LinstorConfigGroupNameMissing') 

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

335 raise xs_errors.XenError('LinstorConfigRedundancyMissing') 

336 

337 self.driver_config = DRIVER_CONFIG 

338 

339 # Check provisioning config. 

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

341 if provisioning: 

342 if provisioning in self.PROVISIONING_TYPES: 

343 self._provisioning = provisioning 

344 else: 

345 raise xs_errors.XenError( 

346 'InvalidArg', 

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

348 self.PROVISIONING_TYPES 

349 ) 

350 ) 

351 else: 

352 self._provisioning = self.PROVISIONING_DEFAULT 

353 

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

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

356 util.strtobool(monitor_db_quorum) 

357 

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

359 # 'vdi_attach_from_config' command is executed. 

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

361 if self._has_session: 

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

363 else: 

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

365 

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

367 if provisioning in self.PROVISIONING_TYPES: 

368 self._provisioning = provisioning 

369 

370 # Define properties for SR parent class. 

371 self.ops_exclusive = OPS_EXCLUSIVE 

372 self.path = LinstorVolumeManager.DEV_ROOT_PATH 

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

374 self.sr_vditype = SR.DEFAULT_TAP 

375 

376 if self.cmd == 'sr_create': 

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

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

379 self._journaler = None 

380 

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

382 class LinstorProxy: 

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

384 self.sr = sr 

385 

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

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

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

389 

390 self._linstor_proxy = LinstorProxy(self) 

391 

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

393 

394 self._vdi_shared_time = 0 

395 

396 self._init_status = self.INIT_STATUS_NOT_SET 

397 

398 self._vdis_loaded = False 

399 self._all_volume_info_cache = None 

400 self._all_volume_metadata_cache = None 

401 

402 # To remove in python 3.10. 

403 # Use directly @staticmethod instead. 

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

405 def _locked_load(method): 

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

407 self._init_status = self.INIT_STATUS_OK 

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

409 

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

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

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

413 activate_lvm_group(self._group_name) 

414 

415 if not self._has_session: 

416 if self.srcmd.cmd in ( 

417 'vdi_attach_from_config', 

418 'vdi_detach_from_config', 

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

420 # empty command. 

421 None 

422 ): 

423 def create_linstor(uri, attempt_count=30): 

424 self._linstor = LinstorVolumeManager( 

425 uri, 

426 self._group_name, 

427 logger=util.SMlog, 

428 attempt_count=attempt_count 

429 ) 

430 

431 controller_uri = get_controller_uri() 

432 if controller_uri: 

433 create_linstor(controller_uri) 

434 else: 

435 def connect(): 

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

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

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

439 controller_uri = 'linstor://' + ip 

440 try: 

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

442 create_linstor(controller_uri, attempt_count=0) 

443 return controller_uri 

444 except: 

445 pass 

446 

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

448 if not controller_uri: 

449 raise xs_errors.XenError( 

450 'SRUnavailable', 

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

452 ) 

453 

454 self._journaler = LinstorJournaler( 

455 controller_uri, self._group_name, logger=util.SMlog 

456 ) 

457 

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

459 

460 if not self.is_master(): 

461 if self.cmd in [ 

462 'sr_create', 'sr_delete', 'sr_update', 'sr_probe', 

463 'sr_scan', 'vdi_create', 'vdi_delete', 'vdi_resize', 

464 'vdi_snapshot', 'vdi_clone' 

465 ]: 

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

467 raise xs_errors.XenError('LinstorMaster') 

468 

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

470 # the VDI before the LinstorJournaler/LinstorVolumeManager 

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

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

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

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

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

476 self._vdi_shared_time = time.time() 

477 

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

479 try: 

480 self._reconnect() 

481 except Exception as e: 

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

483 

484 if self._linstor: 

485 try: 

486 hosts = self._linstor.disconnected_hosts 

487 except Exception as e: 

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

489 

490 if hosts: 

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

492 

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

494 if ( 

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

496 self.cmd != 'vdi_create' 

497 ): 

498 self._linstor.ensure_volume_is_not_locked( 

499 self.srcmd.params['vdi_uuid'] 

500 ) 

501 

502 try: 

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

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

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

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

507 # 

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

509 # resourceless volumes. 

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

511 'vdi_attach', 'vdi_detach', 

512 'vdi_activate', 'vdi_deactivate', 

513 'vdi_epoch_begin', 'vdi_epoch_end', 

514 'vdi_update', 'vdi_destroy' 

515 ]: 

516 load_vdis = ( 

517 self.cmd == 'sr_scan' or 

518 self.cmd == 'sr_attach' 

519 ) or len( 

520 self._journaler.get_all(LinstorJournaler.INFLATE) 

521 ) or len( 

522 self._journaler.get_all(LinstorJournaler.CLONE) 

523 ) 

524 

525 if load_vdis: 

526 self._load_vdis() 

527 

528 self._linstor.remove_resourceless_volumes() 

529 

530 self._synchronize_metadata() 

531 except Exception as e: 

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

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

534 # from the XAPI database otherwise. 

535 raise e 

536 util.SMlog( 

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

538 ) 

539 util.SMlog(traceback.format_exc()) 

540 

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

542 

543 @functools.wraps(wrapped_method) 

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

545 if self._init_status in \ 

546 (self.INIT_STATUS_OK, self.INIT_STATUS_IN_PROGRESS): 

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

548 if self._init_status == self.INIT_STATUS_FAIL: 

549 util.SMlog( 

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

551 .format(method) 

552 ) 

553 else: 

554 try: 

555 self._init_status = self.INIT_STATUS_IN_PROGRESS 

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

557 except Exception: 

558 if self._init_status != self.INIT_STATUS_OK: 

559 self._init_status = self.INIT_STATUS_FAIL 

560 raise 

561 

562 return wrap 

563 

564 @override 

565 def cleanup(self) -> None: 

566 if self._vdi_shared_time: 

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

568 

569 @override 

570 @_locked_load 

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

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

573 

574 host_adresses = util.get_host_addresses(self.session) 

575 if self._redundancy > len(host_adresses): 

576 raise xs_errors.XenError( 

577 'LinstorSRCreate', 

578 opterr='Redundancy greater than host count' 

579 ) 

580 

581 xenapi = self.session.xenapi 

582 srs = xenapi.SR.get_all_records_where( 

583 'field "type" = "{}"'.format(self.DRIVER_TYPE) 

584 ) 

585 srs = dict([e for e in srs.items() if e[1]['uuid'] != self.uuid]) 

586 

587 for sr in srs.values(): 

588 for pbd in sr['PBDs']: 

589 device_config = xenapi.PBD.get_device_config(pbd) 

590 group_name = device_config.get('group-name') 

591 if group_name and group_name == self._group_name: 

592 raise xs_errors.XenError( 

593 'LinstorSRCreate', 

594 opterr='group name must be unique, already used by PBD {}'.format( 

595 xenapi.PBD.get_uuid(pbd) 

596 ) 

597 ) 

598 

599 if srs: 

600 raise xs_errors.XenError( 

601 'LinstorSRCreate', 

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

603 ) 

604 

605 online_hosts = util.get_enabled_hosts(self.session) 

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

607 raise xs_errors.XenError( 

608 'LinstorSRCreate', 

609 opterr='Not enough online hosts' 

610 ) 

611 

612 ips = {} 

613 for host_ref in online_hosts: 

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

615 hostname = record['hostname'] 

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

617 

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

619 raise xs_errors.XenError( 

620 'LinstorSRCreate', 

621 opterr='Multiple hosts with same hostname' 

622 ) 

623 

624 # Ensure ports are opened and LINSTOR satellites 

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

626 # must be stopped. 

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

628 

629 # Create SR. 

630 # Throw if the SR already exists. 

631 try: 

632 self._linstor = LinstorVolumeManager.create_sr( 

633 self._group_name, 

634 ips, 

635 self._redundancy, 

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

637 logger=util.SMlog 

638 ) 

639 

640 util.SMlog( 

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

642 ) 

643 self._update_drbd_reactor_on_all_hosts(enabled=True) 

644 except Exception as e: 

645 if not self._linstor: 

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

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

648 

649 try: 

650 self._linstor.destroy() 

651 except Exception as e2: 

652 util.SMlog( 

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

654 .format(e2) 

655 ) 

656 raise e 

657 

658 @override 

659 @_locked_load 

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

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

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

663 

664 assert self._linstor 

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

666 raise xs_errors.XenError('SRNotEmpty') 

667 

668 node_name = get_controller_node_name() 

669 if not node_name: 

670 raise xs_errors.XenError( 

671 'LinstorSRDelete', 

672 opterr='Cannot get controller node name' 

673 ) 

674 

675 host_ref = None 

676 if node_name == 'localhost': 

677 host_ref = util.get_this_host_ref(self.session) 

678 else: 

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

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

681 if r_name == node_name: 

682 host_ref = slave 

683 break 

684 

685 if not host_ref: 

686 raise xs_errors.XenError( 

687 'LinstorSRDelete', 

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

689 node_name 

690 ) 

691 ) 

692 

693 try: 

694 if self._monitor_db_quorum: 

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

696 self._update_drbd_reactor_on_all_hosts( 

697 controller_node_name=node_name, enabled=False 

698 ) 

699 

700 args = { 

701 'groupName': self._group_name, 

702 } 

703 self._exec_manager_command( 

704 host_ref, 'destroy', args, 'LinstorSRDelete' 

705 ) 

706 except Exception as e: 

707 try: 

708 self._update_drbd_reactor_on_all_hosts( 

709 controller_node_name=node_name, enabled=True 

710 ) 

711 if self._monitor_db_quorum: 

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

713 except Exception as e2: 

714 util.SMlog( 

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

716 .format(e2) 

717 ) 

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

719 raise xs_errors.XenError( 

720 'LinstorSRDelete', 

721 opterr=str(e) 

722 ) 

723 

724 lock.Lock.cleanupAll(self.uuid) 

725 

726 @override 

727 @_locked_load 

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

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

730 

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

732 if not self._linstor: 

733 raise xs_errors.XenError( 

734 'SRUnavailable', 

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

736 ) 

737 

738 self._update_stats(0) 

739 

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

741 xenapi = self.session.xenapi 

742 self._linstor.metadata = { 

743 NAME_LABEL_TAG: util.to_plain_string( 

744 xenapi.SR.get_name_label(self.sr_ref) 

745 ), 

746 NAME_DESCRIPTION_TAG: util.to_plain_string( 

747 xenapi.SR.get_name_description(self.sr_ref) 

748 ) 

749 } 

750 

751 @override 

752 @_locked_load 

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

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

755 

756 if not self._linstor: 

757 raise xs_errors.XenError( 

758 'SRUnavailable', 

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

760 ) 

761 

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

763 self._linstor.set_drbd_ha_properties(DATABASE_VOLUME_NAME) 

764 

765 @override 

766 @_locked_load 

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

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

769 cleanup.abort(self.uuid) 

770 

771 @override 

772 @_locked_load 

773 def probe(self) -> str: 

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

775 # TODO 

776 return '' 

777 

778 @override 

779 @_locked_load 

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

781 if self._init_status == self.INIT_STATUS_FAIL: 

782 return 

783 

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

785 if not self._linstor: 

786 raise xs_errors.XenError( 

787 'SRUnavailable', 

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

789 ) 

790 

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

792 # are loaded. 

793 self._load_vdis() 

794 self._update_physical_size() 

795 

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

797 if self.vdis[vdi_uuid].deleted: 

798 del self.vdis[vdi_uuid] 

799 

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

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

802 try: 

803 self._linstor.get_database_path() 

804 except Exception as e: 

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

806 # VDIs in the XAPI database... 

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

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

809 ): 

810 raise xs_errors.XenError( 

811 'SRUnavailable', 

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

813 ) 

814 

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

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

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

818 self._kick_gc() 

819 

820 def is_master(self): 

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

822 if 'SRmaster' not in self.dconf: 

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

824 else: 

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

826 

827 return self._is_master 

828 

829 @override 

830 @_locked_load 

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

832 return LinstorVDI(self, uuid) 

833 

834 # To remove in python 3.10 

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

836 _locked_load = staticmethod(_locked_load) 

837 

838 # -------------------------------------------------------------------------- 

839 # Lock. 

840 # -------------------------------------------------------------------------- 

841 

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

843 master = util.get_master_ref(self.session) 

844 

845 command = 'lockVdi' 

846 args = { 

847 'groupName': self._group_name, 

848 'srUuid': self.uuid, 

849 'vdiUuid': vdi_uuid, 

850 'locked': str(locked) 

851 } 

852 

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

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

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

856 # 

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

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

859 # to implement without impacting performance. 

860 if not locked: 

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

862 timeout = LinstorVolumeManager.LOCKED_EXPIRATION_DELAY * 0.7 

863 if elapsed_time >= timeout: 

864 util.SMlog( 

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

866 .format(vdi_uuid) 

867 ) 

868 return 

869 

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

871 

872 # -------------------------------------------------------------------------- 

873 # Network. 

874 # -------------------------------------------------------------------------- 

875 

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

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

878 host_uuid = host_rec['uuid'] 

879 

880 try: 

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

882 host_ref, self.MANAGER_PLUGIN, command, args 

883 ) 

884 except Exception as e: 

885 util.SMlog( 

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

887 host_uuid, self.MANAGER_PLUGIN, command, args 

888 ) 

889 ) 

890 raise e 

891 

892 util.SMlog( 

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

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

895 ) 

896 ) 

897 if ret == 'False': 

898 raise xs_errors.XenError( 

899 error, 

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

901 ) 

902 

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

904 self._exec_manager_command( 

905 host, 

906 'prepareSr' if enabled else 'releaseSr', 

907 {'groupName': group_name}, 

908 'SRUnavailable' 

909 ) 

910 

911 def _prepare_sr_on_all_hosts(self, group_name, enabled): 

912 master = util.get_master_ref(self.session) 

913 self._prepare_sr(master, group_name, enabled) 

914 

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

916 self._prepare_sr(slave, group_name, enabled) 

917 

918 def _update_drbd_reactor(self, host, enabled): 

919 self._exec_manager_command( 

920 host, 

921 'updateDrbdReactor', 

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

923 'SRUnavailable' 

924 ) 

925 

926 def _update_drbd_reactor_on_all_hosts( 

927 self, enabled, controller_node_name=None 

928 ): 

929 if controller_node_name == 'localhost': 

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

931 util.get_this_host_ref(self.session) 

932 )['hostname'] 

933 assert controller_node_name 

934 assert controller_node_name != 'localhost' 

935 

936 controller_host = None 

937 secondary_hosts = [] 

938 

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

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

941 hostname = host_rec['hostname'] 

942 if controller_node_name == hostname: 

943 controller_host = host_ref 

944 else: 

945 secondary_hosts.append((host_ref, hostname)) 

946 

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

948 if controller_node_name and not controller_host: 

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

950 controller_node_name 

951 )) 

952 

953 if enabled and controller_host: 

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

955 action_name, controller_node_name 

956 )) 

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

958 # node name first. 

959 self._update_drbd_reactor(controller_host, enabled) 

960 

961 for host_ref, hostname in secondary_hosts: 

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

963 action_name, hostname 

964 )) 

965 self._update_drbd_reactor(host_ref, enabled) 

966 

967 if not enabled and controller_host: 

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

969 action_name, controller_node_name 

970 )) 

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

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

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

974 self._update_drbd_reactor(controller_host, enabled) 

975 

976 # -------------------------------------------------------------------------- 

977 # Metadata. 

978 # -------------------------------------------------------------------------- 

979 

980 def _synchronize_metadata_and_xapi(self): 

981 try: 

982 # First synch SR parameters. 

983 self.update(self.uuid) 

984 

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

986 xenapi = self.session.xenapi 

987 volumes_metadata = self._linstor.get_volumes_with_metadata() 

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

989 try: 

990 vdi_ref = xenapi.VDI.get_by_uuid(vdi_uuid) 

991 except Exception: 

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

993 continue 

994 

995 label = util.to_plain_string( 

996 xenapi.VDI.get_name_label(vdi_ref) 

997 ) 

998 description = util.to_plain_string( 

999 xenapi.VDI.get_name_description(vdi_ref) 

1000 ) 

1001 

1002 if ( 

1003 volume_metadata.get(NAME_LABEL_TAG) != label or 

1004 volume_metadata.get(NAME_DESCRIPTION_TAG) != description 

1005 ): 

1006 self._linstor.update_volume_metadata(vdi_uuid, { 

1007 NAME_LABEL_TAG: label, 

1008 NAME_DESCRIPTION_TAG: description 

1009 }) 

1010 except Exception as e: 

1011 raise xs_errors.XenError( 

1012 'MetadataError', 

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

1014 ) 

1015 

1016 def _synchronize_metadata(self): 

1017 if not self.is_master(): 

1018 return 

1019 

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

1021 if self.cmd == 'sr_attach': 

1022 try: 

1023 util.SMlog( 

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

1025 ) 

1026 self._synchronize_metadata_and_xapi() 

1027 except Exception as e: 

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

1029 

1030 # -------------------------------------------------------------------------- 

1031 # Stats. 

1032 # -------------------------------------------------------------------------- 

1033 

1034 def _update_stats(self, virt_alloc_delta): 

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

1036 self.sr_ref 

1037 )) 

1038 

1039 # Update size attributes of the SR parent class. 

1040 self.virtual_allocation = valloc + virt_alloc_delta 

1041 

1042 self._update_physical_size() 

1043 

1044 # Notify SR parent class. 

1045 self._db_update() 

1046 

1047 def _update_physical_size(self): 

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

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

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

1051 self.physical_size = min_physical_size * pool_count // \ 

1052 self._linstor.redundancy 

1053 

1054 self.physical_utilisation = self._linstor.allocated_volume_size 

1055 

1056 # -------------------------------------------------------------------------- 

1057 # VDIs. 

1058 # -------------------------------------------------------------------------- 

1059 

1060 def _load_vdis(self): 

1061 if self._vdis_loaded: 

1062 return 

1063 

1064 assert self.is_master() 

1065 

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

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

1068 # enjoy it with a few lines. 

1069 self._create_linstor_cache() 

1070 self._load_vdis_ex() 

1071 self._destroy_linstor_cache() 

1072 

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

1074 self._vdis_loaded = True 

1075 

1076 self._undo_all_journal_transactions() 

1077 

1078 def _load_vdis_ex(self): 

1079 # 1. Get existing VDIs in XAPI. 

1080 xenapi = self.session.xenapi 

1081 xapi_vdi_uuids = set() 

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

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

1084 

1085 # 2. Get volumes info. 

1086 all_volume_info = self._all_volume_info_cache 

1087 volumes_metadata = self._all_volume_metadata_cache 

1088 

1089 # 3. Get CBT vdis. 

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

1091 cbt_vdis = set() 

1092 for volume_metadata in volumes_metadata.values(): 

1093 cbt_uuid = volume_metadata.get(CBTLOG_TAG) 

1094 if cbt_uuid: 

1095 cbt_vdis.add(cbt_uuid) 

1096 

1097 introduce = False 

1098 

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

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

1101 has_clone_entries = list(self._journaler.get_all( 

1102 LinstorJournaler.CLONE 

1103 ).items()) 

1104 

1105 if has_clone_entries: 

1106 util.SMlog( 

1107 'Cannot introduce VDIs during scan because it exists ' 

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

1109 ) 

1110 else: 

1111 introduce = True 

1112 

1113 # 4. Now check all volume info. 

1114 vdi_to_snaps = {} 

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

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

1117 continue 

1118 

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

1120 if vdi_uuid not in xapi_vdi_uuids: 

1121 if not introduce: 

1122 continue 

1123 

1124 if vdi_uuid.startswith('DELETED_'): 

1125 continue 

1126 

1127 volume_metadata = volumes_metadata.get(vdi_uuid) 

1128 if not volume_metadata: 

1129 util.SMlog( 

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

1131 .format(vdi_uuid) 

1132 ) 

1133 continue 

1134 

1135 util.SMlog( 

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

1137 'LINSTOR and not in XAPI...' 

1138 .format(vdi_uuid) 

1139 ) 

1140 

1141 try: 

1142 self._linstor.get_device_path(vdi_uuid) 

1143 except Exception as e: 

1144 util.SMlog( 

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

1146 .format(vdi_uuid, e) 

1147 ) 

1148 continue 

1149 

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

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

1152 vdi_type = volume_metadata.get(VDI_TYPE_TAG) 

1153 

1154 if not vdi_type: 

1155 util.SMlog( 

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

1157 'without vdi_type' 

1158 ) 

1159 continue 

1160 

1161 sm_config = { 

1162 'vdi_type': vdi_type 

1163 } 

1164 

1165 if not VdiType.isCowImage(vdi_type): 

1166 managed = not volume_metadata.get(HIDDEN_TAG) 

1167 else: 

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

1169 managed = not image_info.hidden 

1170 if image_info.parentUuid: 

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

1172 

1173 util.SMlog( 

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

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

1176 name_label, 

1177 volume_info.virtual_size, 

1178 volume_info.allocated_size 

1179 ) 

1180 ) 

1181 

1182 vdi_ref = xenapi.VDI.db_introduce( 

1183 vdi_uuid, 

1184 name_label, 

1185 volume_metadata.get(NAME_DESCRIPTION_TAG) or '', 

1186 self.sr_ref, 

1187 type, 

1188 False, # sharable 

1189 bool(volume_metadata.get(READ_ONLY_TAG)), 

1190 {}, # other_config 

1191 vdi_uuid, # location 

1192 {}, # xenstore_data 

1193 sm_config, 

1194 managed, 

1195 str(volume_info.virtual_size), 

1196 str(volume_info.allocated_size) 

1197 ) 

1198 

1199 is_a_snapshot = volume_metadata.get(IS_A_SNAPSHOT_TAG) 

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

1201 if is_a_snapshot: 

1202 xenapi.VDI.set_snapshot_time( 

1203 vdi_ref, 

1204 xmlrpc.client.DateTime( 

1205 volume_metadata[SNAPSHOT_TIME_TAG] or 

1206 '19700101T00:00:00Z' 

1207 ) 

1208 ) 

1209 

1210 snap_uuid = volume_metadata[SNAPSHOT_OF_TAG] 

1211 if snap_uuid in vdi_to_snaps: 

1212 vdi_to_snaps[snap_uuid].append(vdi_uuid) 

1213 else: 

1214 vdi_to_snaps[snap_uuid] = [vdi_uuid] 

1215 

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

1217 vdi = self.vdi(vdi_uuid) 

1218 self.vdis[vdi_uuid] = vdi 

1219 

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

1221 vdi.sm_config_override['key_hash'] = vdi.linstorcowutil.get_key_hash(vdi_uuid) 

1222 

1223 # 4.c. Update CBT status of disks either just added 

1224 # or already in XAPI. 

1225 cbt_uuid = volume_metadata.get(CBTLOG_TAG) 

1226 if cbt_uuid in cbt_vdis: 

1227 vdi_ref = xenapi.VDI.get_by_uuid(vdi_uuid) 

1228 xenapi.VDI.set_cbt_enabled(vdi_ref, True) 

1229 # For existing VDIs, update local state too. 

1230 # Scan in base class SR updates existing VDIs 

1231 # again based on local states. 

1232 self.vdis[vdi_uuid].cbt_enabled = True 

1233 cbt_vdis.remove(cbt_uuid) 

1234 

1235 # 5. Now set the snapshot statuses correctly in XAPI. 

1236 for src_uuid in vdi_to_snaps: 

1237 try: 

1238 src_ref = xenapi.VDI.get_by_uuid(src_uuid) 

1239 except Exception: 

1240 # The source VDI no longer exists, continue. 

1241 continue 

1242 

1243 for snap_uuid in vdi_to_snaps[src_uuid]: 

1244 try: 

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

1246 snap_ref = xenapi.VDI.get_by_uuid(snap_uuid) 

1247 xenapi.VDI.set_snapshot_of(snap_ref, src_ref) 

1248 except Exception as e: 

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

1250 

1251 # TODO: Check correctly how to use CBT. 

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

1253 

1254 # 6. If we have items remaining in this list, 

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

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

1257 for cbt_uuid in cbt_vdis: 

1258 new_vdi = self.vdi(cbt_uuid) 

1259 new_vdi.ty = 'cbt_metadata' 

1260 new_vdi.cbt_enabled = True 

1261 self.vdis[cbt_uuid] = new_vdi 

1262 

1263 # 7. Update virtual allocation, build geneology and remove useless VDIs 

1264 self.virtual_allocation = 0 

1265 

1266 # 8. Build geneology. 

1267 geneology = {} 

1268 

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

1270 if vdi.parent: 

1271 if vdi.parent in self.vdis: 

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

1273 if vdi.parent in geneology: 

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

1275 else: 

1276 geneology[vdi.parent] = [vdi_uuid] 

1277 if not vdi.hidden: 

1278 self.virtual_allocation += vdi.size 

1279 

1280 # 9. Remove all hidden leaf nodes to avoid introducing records that 

1281 # will be GC'ed. 

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

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

1284 util.SMlog( 

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

1286 ) 

1287 del self.vdis[vdi_uuid] 

1288 

1289 # -------------------------------------------------------------------------- 

1290 # Journals. 

1291 # -------------------------------------------------------------------------- 

1292 

1293 def _get_vdi_path_and_parent(self, vdi_uuid, volume_name): 

1294 try: 

1295 device_path = self._linstor.build_device_path(volume_name) 

1296 if not util.pathexists(device_path): 

1297 return (None, None) 

1298 

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

1300 volume_metadata = self._linstor.get_volume_metadata(vdi_uuid) 

1301 vdi_type = volume_metadata[VDI_TYPE_TAG] 

1302 if not VdiType.isCowImage(vdi_type): 

1303 return (device_path, None) 

1304 

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

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

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

1308 return (None, None) 

1309 

1310 image_info = linstorcowutil.get_info(vdi_uuid) 

1311 if image_info: 

1312 return (device_path, image_info.parentUuid) 

1313 except Exception as e: 

1314 util.SMlog( 

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

1316 .format(e) 

1317 ) 

1318 return (None, None) 

1319 

1320 def _undo_all_journal_transactions(self): 

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

1322 self.lock.acquire() 

1323 try: 

1324 self._handle_interrupted_inflate_ops() 

1325 self._handle_interrupted_clone_ops() 

1326 pass 

1327 finally: 

1328 self.lock.release() 

1329 

1330 def _handle_interrupted_inflate_ops(self): 

1331 transactions = self._journaler.get_all(LinstorJournaler.INFLATE) 

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

1333 self._handle_interrupted_inflate(vdi_uuid, old_size) 

1334 self._journaler.remove(LinstorJournaler.INFLATE, vdi_uuid) 

1335 

1336 def _handle_interrupted_clone_ops(self): 

1337 transactions = self._journaler.get_all(LinstorJournaler.CLONE) 

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

1339 self._handle_interrupted_clone(vdi_uuid, old_size) 

1340 self._journaler.remove(LinstorJournaler.CLONE, vdi_uuid) 

1341 

1342 def _handle_interrupted_inflate(self, vdi_uuid, old_size): 

1343 util.SMlog( 

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

1345 .format(vdi_uuid, old_size) 

1346 ) 

1347 

1348 vdi = self.vdis.get(vdi_uuid) 

1349 if not vdi: 

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

1351 return 

1352 

1353 assert not self._all_volume_info_cache 

1354 volume_info = self._linstor.get_volume_info(vdi_uuid) 

1355 

1356 current_size = volume_info.virtual_size 

1357 assert current_size > 0 

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

1359 

1360 def _handle_interrupted_clone( 

1361 self, vdi_uuid, clone_info, force_undo=False 

1362 ): 

1363 util.SMlog( 

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

1365 .format(vdi_uuid, clone_info) 

1366 ) 

1367 

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

1369 

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

1371 volume_names = self._linstor.get_volumes_with_name() 

1372 

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

1374 if base_uuid not in volume_names: 

1375 if vdi_uuid in volume_names: 

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

1377 return 

1378 raise util.SMException( 

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

1380 .format(base_uuid, vdi_uuid) 

1381 ) 

1382 

1383 if force_undo: 

1384 util.SMlog('Explicit revert') 

1385 self._undo_clone( 

1386 volume_names, vdi_uuid, base_uuid, snap_uuid 

1387 ) 

1388 return 

1389 

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

1391 if vdi_uuid not in volume_names or \ 

1392 (snap_uuid and snap_uuid not in volume_names): 

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

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

1395 return 

1396 

1397 vdi_path, vdi_parent_uuid = self._get_vdi_path_and_parent( 

1398 vdi_uuid, volume_names[vdi_uuid] 

1399 ) 

1400 snap_path, snap_parent_uuid = self._get_vdi_path_and_parent( 

1401 snap_uuid, volume_names[snap_uuid] 

1402 ) 

1403 

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

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

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

1407 return 

1408 

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

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

1411 

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

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

1414 base_metadata = self._linstor.get_volume_metadata(base_uuid) 

1415 base_type = base_metadata[VDI_TYPE_TAG] 

1416 

1417 if not util.pathexists(base_path): 

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

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

1420 return 

1421 

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

1423 

1424 # Un-hide the parent. 

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

1426 if VdiType.isCowImage(base_type): 

1427 image_info = linstorcowutil.get_info(base_uuid, False) 

1428 if image_info.hidden: 

1429 linstorcowutil.set_hidden(base_path, False) 

1430 elif base_metadata.get(HIDDEN_TAG): 

1431 self._linstor.update_volume_metadata( 

1432 base_uuid, {HIDDEN_TAG: False} 

1433 ) 

1434 

1435 # Remove the child nodes. 

1436 if snap_uuid and snap_uuid in volume_names: 

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

1438 

1439 try: 

1440 self._linstor.destroy_volume(snap_uuid) 

1441 except Exception as e: 

1442 util.SMlog( 

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

1444 .format(snap_uuid, e) 

1445 ) 

1446 

1447 if vdi_uuid in volume_names: 

1448 try: 

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

1450 self._linstor.destroy_volume(vdi_uuid) 

1451 except Exception as e: 

1452 util.SMlog( 

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

1454 .format(vdi_uuid, e) 

1455 ) 

1456 # We can get an exception like this: 

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

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

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

1460 # UUID of this bad VDI before. 

1461 self._linstor.update_volume_uuid( 

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

1463 ) 

1464 

1465 # Rename! 

1466 self._linstor.update_volume_uuid(base_uuid, vdi_uuid) 

1467 

1468 # Inflate to the right size. 

1469 if VdiType.isCowImage(base_type): 

1470 vdi = self.vdi(vdi_uuid) 

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

1472 volume_size = linstorcowutil.compute_volume_size(vdi.size) 

1473 linstorcowutil.inflate( 

1474 self._journaler, vdi_uuid, vdi.path, 

1475 volume_size, vdi.capacity 

1476 ) 

1477 self.vdis[vdi_uuid] = vdi 

1478 

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

1480 # flag to facilitate vm deactivate. 

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

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

1483 

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

1485 

1486 # -------------------------------------------------------------------------- 

1487 # Cache. 

1488 # -------------------------------------------------------------------------- 

1489 

1490 def _create_linstor_cache(self): 

1491 reconnect = False 

1492 

1493 def create_cache(): 

1494 nonlocal reconnect 

1495 try: 

1496 if reconnect: 

1497 self._reconnect() 

1498 return self._linstor.get_volumes_with_info() 

1499 except Exception as e: 

1500 reconnect = True 

1501 raise e 

1502 

1503 self._all_volume_metadata_cache = \ 

1504 self._linstor.get_volumes_with_metadata() 

1505 self._all_volume_info_cache = util.retry( 

1506 create_cache, 

1507 maxretry=10, 

1508 period=3 

1509 ) 

1510 

1511 def _destroy_linstor_cache(self): 

1512 self._all_volume_info_cache = None 

1513 self._all_volume_metadata_cache = None 

1514 

1515 # -------------------------------------------------------------------------- 

1516 # Misc. 

1517 # -------------------------------------------------------------------------- 

1518 

1519 def _reconnect(self): 

1520 controller_uri = get_controller_uri() 

1521 

1522 self._journaler = LinstorJournaler( 

1523 controller_uri, self._group_name, logger=util.SMlog 

1524 ) 

1525 

1526 # Try to open SR if exists. 

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

1528 # we are trying to execute an exclusive operation. 

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

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

1531 # the SR is locked. 

1532 self._linstor = LinstorVolumeManager( 

1533 controller_uri, 

1534 self._group_name, 

1535 repair=( 

1536 self.is_master() and 

1537 self.srcmd.cmd in self.ops_exclusive 

1538 ), 

1539 logger=util.SMlog 

1540 ) 

1541 

1542 def _ensure_space_available(self, amount_needed): 

1543 space_available = self._linstor.max_volume_size_allowed 

1544 if (space_available < amount_needed): 

1545 util.SMlog( 

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

1547 space_available, amount_needed 

1548 ) 

1549 ) 

1550 raise xs_errors.XenError('SRNoSpace') 

1551 

1552 def _kick_gc(self): 

1553 util.SMlog('Kicking GC') 

1554 cleanup.start_gc_service(self.uuid) 

1555 

1556# ============================================================================== 

1557# LinstorSr VDI 

1558# ============================================================================== 

1559 

1560 

1561class LinstorVDI(VDI.VDI): 

1562 # -------------------------------------------------------------------------- 

1563 # VDI methods. 

1564 # -------------------------------------------------------------------------- 

1565 

1566 @override 

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

1568 self._lock = self.sr.lock 

1569 self._exists = True 

1570 self._linstor = self.sr._linstor 

1571 

1572 # Update hidden parent property. 

1573 self.hidden = False 

1574 

1575 def raise_bad_load(e): 

1576 util.SMlog( 

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

1578 ) 

1579 util.SMlog(traceback.format_exc()) 

1580 raise xs_errors.XenError( 

1581 'VDIUnavailable', 

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

1583 ) 

1584 

1585 # Try to load VDI. 

1586 try: 

1587 if ( 

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

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

1590 ): 

1591 self._set_type(VdiType.RAW) 

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

1593 else: 

1594 self._determine_type_and_path() 

1595 self._load_this() 

1596 

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

1598 self.uuid, self.path, self.hidden 

1599 )) 

1600 except LinstorVolumeManagerError as e: 

1601 # 1. It may be a VDI deletion. 

1602 if e.code == LinstorVolumeManagerError.ERR_VOLUME_NOT_EXISTS: 

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

1604 self.deleted = True 

1605 return 

1606 

1607 # 2. Or maybe a creation. 

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

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

1610 

1611 self._exists = False 

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

1613 if vdi_sm_config: 

1614 image_format = vdi_sm_config.get('image-format') or vdi_sm_config.get('type') 

1615 if image_format: 

1616 try: 

1617 self._set_type(CREATE_PARAM_TYPES[image_format]) 

1618 except: 

1619 raise xs_errors.XenError('VDICreate', opterr='bad image format') 

1620 

1621 if not self.vdi_type: 

1622 self._set_type(getVdiTypeFromImageFormat(self.sr.preferred_image_formats[0])) 

1623 

1624 if VdiType.isCowImage(self.vdi_type): 

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

1626 

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

1628 self._update_device_name(None) 

1629 return 

1630 raise_bad_load(e) 

1631 except Exception as e: 

1632 raise_bad_load(e) 

1633 

1634 @override 

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

1636 # Usage example: 

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

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

1639 

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

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

1642 if self._exists: 

1643 raise xs_errors.XenError('VDIExists') 

1644 

1645 assert self.uuid 

1646 assert self.ty 

1647 assert self.vdi_type 

1648 

1649 # 2. Compute size and check space available. 

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

1651 volume_size = self.linstorcowutil.compute_volume_size(size) 

1652 util.SMlog( 

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

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

1655 ) 

1656 self.sr._ensure_space_available(volume_size) 

1657 

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

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

1660 

1661 # 4. Create! 

1662 failed = False 

1663 try: 

1664 volume_name = None 

1665 if self.ty == 'ha_statefile': 

1666 volume_name = HA_VOLUME_NAME 

1667 elif self.ty == 'redo_log': 

1668 volume_name = REDO_LOG_VOLUME_NAME 

1669 

1670 self._linstor.create_volume( 

1671 self.uuid, 

1672 volume_size, 

1673 persistent=False, 

1674 volume_name=volume_name, 

1675 high_availability=volume_name is not None 

1676 ) 

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

1678 

1679 self._update_device_name(volume_info.name) 

1680 

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

1682 self.size = volume_info.virtual_size 

1683 else: 

1684 self.linstorcowutil.create( 

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

1686 ) 

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

1688 

1689 if self._key_hash: 

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

1691 

1692 # Because cowutil commands modify the volume data, 

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

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

1695 

1696 volume_metadata = { 

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

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

1699 IS_A_SNAPSHOT_TAG: False, 

1700 SNAPSHOT_OF_TAG: '', 

1701 SNAPSHOT_TIME_TAG: '', 

1702 TYPE_TAG: self.ty, 

1703 VDI_TYPE_TAG: self.vdi_type, 

1704 READ_ONLY_TAG: bool(self.read_only), 

1705 METADATA_OF_POOL_TAG: '' 

1706 } 

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

1708 

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

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

1711 # an already opened volume. 

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

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

1714 

1715 self._linstor.mark_volume_as_persistent(self.uuid) 

1716 except util.CommandException as e: 

1717 failed = True 

1718 raise xs_errors.XenError( 

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

1720 ) 

1721 except Exception as e: 

1722 failed = True 

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

1724 finally: 

1725 if failed: 

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

1727 try: 

1728 self._linstor.destroy_volume(self.uuid) 

1729 except Exception as e: 

1730 util.SMlog( 

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

1732 '{}'.format(e) 

1733 ) 

1734 

1735 self.utilisation = volume_info.allocated_size 

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

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

1738 

1739 self.ref = self._db_introduce() 

1740 self.sr._update_stats(self.size) 

1741 

1742 return VDI.VDI.get_params(self) 

1743 

1744 @override 

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

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

1747 if self.attached: 

1748 raise xs_errors.XenError('VDIInUse') 

1749 

1750 if self.deleted: 

1751 return super(LinstorVDI, self).delete( 

1752 sr_uuid, vdi_uuid, data_only 

1753 ) 

1754 

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

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

1757 raise xs_errors.XenError( 

1758 'VDIDelete', 

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

1760 ) 

1761 

1762 try: 

1763 # Remove from XAPI and delete from LINSTOR. 

1764 self._linstor.destroy_volume(self.uuid) 

1765 if not data_only: 

1766 self._db_forget() 

1767 

1768 self.sr.lock.cleanupAll(vdi_uuid) 

1769 except Exception as e: 

1770 util.SMlog( 

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

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

1773 ) 

1774 

1775 try: 

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

1777 except LinstorVolumeManagerError as e: 

1778 if e.code != LinstorVolumeManagerError.ERR_VOLUME_DESTROY: 

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

1780 

1781 return 

1782 

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

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

1785 

1786 # TODO: Check size after delete. 

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

1788 self.sr._kick_gc() 

1789 return super(LinstorVDI, self).delete(sr_uuid, vdi_uuid, data_only) 

1790 

1791 @override 

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

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

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

1795 if ( 

1796 not attach_from_config or 

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

1798 ) and self.sr._journaler.has_entries(self.uuid): 

1799 raise xs_errors.XenError( 

1800 'VDIUnavailable', 

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

1802 'scan SR first to trigger auto-repair' 

1803 ) 

1804 

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

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

1807 

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

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

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

1811 # than the COW size + bitmap size. 

1812 need_inflate = True 

1813 if ( 

1814 not VdiType.isCowImage(self.vdi_type) or 

1815 not writable or 

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

1817 ): 

1818 need_inflate = False 

1819 

1820 if need_inflate: 

1821 try: 

1822 self._prepare_thin(True) 

1823 except Exception as e: 

1824 raise xs_errors.XenError( 

1825 'VDIUnavailable', 

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

1827 .format(e) 

1828 ) 

1829 

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

1831 self.xenstore_data = {} 

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

1833 

1834 if ( 

1835 USE_HTTP_NBD_SERVERS and 

1836 attach_from_config and 

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

1838 ): 

1839 return self._attach_using_http_nbd() 

1840 

1841 # Ensure we have a path... 

1842 self.linstorcowutil.create_chain_paths(self.uuid, readonly=not writable) 

1843 

1844 self.attached = True 

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

1846 

1847 @override 

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

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

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

1851 self.attached = False 

1852 

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

1854 return self._detach_using_http_nbd() 

1855 

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

1857 return 

1858 

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

1860 # equal to the LINSTOR volume size. 

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

1862 already_deflated = self.capacity <= volume_size 

1863 

1864 if already_deflated: 

1865 util.SMlog( 

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

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

1868 ) 

1869 

1870 need_deflate = True 

1871 if already_deflated: 

1872 need_deflate = False 

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

1874 need_deflate = False 

1875 

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

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

1878 need_deflate = True 

1879 

1880 if need_deflate: 

1881 try: 

1882 self._prepare_thin(False) 

1883 except Exception as e: 

1884 raise xs_errors.XenError( 

1885 'VDIUnavailable', 

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

1887 .format(e) 

1888 ) 

1889 

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

1891 if self.sr.is_master(): 

1892 return 

1893 

1894 while vdi_uuid: 

1895 try: 

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

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

1898 except Exception: 

1899 break 

1900 

1901 if util.pathexists(path): 

1902 try: 

1903 self._linstor.remove_volume_if_diskless(vdi_uuid) 

1904 except Exception as e: 

1905 # Ensure we can always detach properly. 

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

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

1908 vdi_uuid = parent_vdi_uuid 

1909 

1910 @override 

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

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

1913 if not self.sr.is_master(): 

1914 raise xs_errors.XenError( 

1915 'VDISize', 

1916 opterr='resize on slave not allowed' 

1917 ) 

1918 

1919 if self.hidden: 

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

1921 

1922 # Compute the virtual COW and DRBD volume size. 

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

1924 volume_size = self.linstorcowutil.compute_volume_size(size) 

1925 util.SMlog( 

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

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

1928 ) 

1929 

1930 if size < self.size: 

1931 util.SMlog( 

1932 'vdi_resize: shrinking not supported: ' 

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

1934 ) 

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

1936 

1937 if size == self.size: 

1938 return VDI.VDI.get_params(self) 

1939 

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

1941 old_volume_size = self.size 

1942 new_volume_size = LinstorVolumeManager.round_up_volume_size(size) 

1943 else: 

1944 old_volume_size = self.utilisation 

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

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

1947 new_volume_size = old_volume_size 

1948 else: 

1949 new_volume_size = self.linstorcowutil.compute_volume_size(size) 

1950 assert new_volume_size >= old_volume_size 

1951 

1952 space_needed = new_volume_size - old_volume_size 

1953 self.sr._ensure_space_available(space_needed) 

1954 

1955 old_size = self.size 

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

1957 self._linstor.resize(self.uuid, new_volume_size) 

1958 else: 

1959 if new_volume_size != old_volume_size: 

1960 self.linstorcowutil.inflate( 

1961 self.sr._journaler, self.uuid, self.path, 

1962 new_volume_size, old_volume_size 

1963 ) 

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

1965 

1966 # Reload size attributes. 

1967 self._load_this() 

1968 

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

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

1971 self.session.xenapi.VDI.set_physical_utilisation( 

1972 vdi_ref, str(self.utilisation) 

1973 ) 

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

1975 return VDI.VDI.get_params(self) 

1976 

1977 @override 

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

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

1980 

1981 @override 

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

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

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

1985 raise xs_errors.XenError('Unimplemented') 

1986 

1987 parent_uuid = vdi1 

1988 parent_path = self._linstor.get_device_path(parent_uuid) 

1989 

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

1991 # have a readonly error. 

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

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

1994 

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

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

1997 try: 

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

1999 self.linstorcowutil.set_hidden(parent_path) 

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

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

2002 ) 

2003 finally: 

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

2005 

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

2007 raise util.SMException( 

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

2009 ) 

2010 

2011 util.SMlog('Compose done') 

2012 

2013 @override 

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

2015 """ 

2016 Generate the XML config required to attach and activate 

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

2018 activation is handled by vdi_attach_from_config below. 

2019 """ 

2020 

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

2022 

2023 resp = {} 

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

2025 resp['sr_uuid'] = sr_uuid 

2026 resp['vdi_uuid'] = self.uuid 

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

2028 resp['command'] = 'vdi_attach_from_config' 

2029 

2030 # By default, we generate a normal config. 

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

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

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

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

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

2036 # instead. 

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

2038 if not USE_HTTP_NBD_SERVERS or volume_name not in [ 

2039 HA_VOLUME_NAME, REDO_LOG_VOLUME_NAME 

2040 ]: 

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

2042 available = False 

2043 # Try to refresh symlink path... 

2044 try: 

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

2046 available = util.pathexists(self.path) 

2047 except Exception: 

2048 pass 

2049 if not available: 

2050 raise xs_errors.XenError('VDIUnavailable') 

2051 

2052 resp['vdi_path'] = self.path 

2053 else: 

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

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

2056 

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

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

2059 

2060 @override 

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

2062 """ 

2063 Attach and activate a VDI using config generated by 

2064 vdi_generate_config above. This is used for cases such as 

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

2066 """ 

2067 

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

2069 

2070 try: 

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

2072 self.sr.attach(sr_uuid) 

2073 

2074 if not DRIVER_CONFIG['ATTACH_FROM_CONFIG_WITH_TAPDISK']: 

2075 return self.attach(sr_uuid, vdi_uuid) 

2076 except Exception: 

2077 util.logException('LinstorVDI.attach_from_config') 

2078 raise xs_errors.XenError( 

2079 'SRUnavailable', 

2080 opterr='Unable to attach from config' 

2081 ) 

2082 return '' 

2083 

2084 def reset_leaf(self, sr_uuid, vdi_uuid): 

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

2086 raise xs_errors.XenError('Unimplemented') 

2087 

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

2089 raise util.SMException( 

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

2091 .format(self.uuid) 

2092 ) 

2093 

2094 self.linstorcowutil.kill_data(self.path) 

2095 

2096 def _load_this(self): 

2097 volume_metadata = None 

2098 if self.sr._all_volume_metadata_cache: 

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

2100 if volume_metadata is None: 

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

2102 

2103 volume_info = None 

2104 if self.sr._all_volume_info_cache: 

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

2106 if volume_info is None: 

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

2108 

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

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

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

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

2113 # be lower than virtual size at creation. 

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

2115 self.utilisation = volume_info.allocated_size 

2116 self.capacity = volume_info.virtual_size 

2117 

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

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

2120 self.size = volume_info.virtual_size 

2121 self.parent = '' 

2122 else: 

2123 image_info = self.linstorcowutil.get_info(self.uuid) 

2124 self.hidden = image_info.hidden 

2125 self.size = image_info.sizeVirt 

2126 self.parent = image_info.parentUuid 

2127 

2128 if self.hidden: 

2129 self.managed = False 

2130 

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

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

2133 

2134 # Update sm_config_override of VDI parent class. 

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

2136 

2137 def _mark_hidden(self, hidden=True): 

2138 if self.hidden == hidden: 

2139 return 

2140 

2141 if VdiType.isCowImage(self.vdi_type): 

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

2143 else: 

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

2145 HIDDEN_TAG: hidden 

2146 }) 

2147 self.hidden = hidden 

2148 

2149 @override 

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

2151 xenapi = self.session.xenapi 

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

2153 

2154 volume_metadata = { 

2155 NAME_LABEL_TAG: util.to_plain_string( 

2156 xenapi.VDI.get_name_label(vdi_ref) 

2157 ), 

2158 NAME_DESCRIPTION_TAG: util.to_plain_string( 

2159 xenapi.VDI.get_name_description(vdi_ref) 

2160 ) 

2161 } 

2162 

2163 try: 

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

2165 except LinstorVolumeManagerError as e: 

2166 if e.code == LinstorVolumeManagerError.ERR_VOLUME_NOT_EXISTS: 

2167 raise xs_errors.XenError( 

2168 'VDIUnavailable', 

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

2170 ) 

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

2172 

2173 # -------------------------------------------------------------------------- 

2174 # Thin provisioning. 

2175 # -------------------------------------------------------------------------- 

2176 

2177 def _prepare_thin(self, attach): 

2178 if self.sr.is_master(): 

2179 if attach: 

2180 attach_thin( 

2181 self.session, self.sr._journaler, self._linstor, 

2182 self.sr.uuid, self.uuid 

2183 ) 

2184 else: 

2185 detach_thin( 

2186 self.session, self._linstor, self.sr.uuid, self.uuid 

2187 ) 

2188 else: 

2189 fn = 'attach' if attach else 'detach' 

2190 

2191 master = util.get_master_ref(self.session) 

2192 

2193 args = { 

2194 'groupName': self.sr._group_name, 

2195 'srUuid': self.sr.uuid, 

2196 'vdiUuid': self.uuid 

2197 } 

2198 

2199 try: 

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

2201 except Exception: 

2202 if fn != 'detach': 

2203 raise 

2204 

2205 # Reload size attrs after inflate or deflate! 

2206 self._load_this() 

2207 self.sr._update_physical_size() 

2208 

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

2210 self.session.xenapi.VDI.set_physical_utilisation( 

2211 vdi_ref, str(self.utilisation) 

2212 ) 

2213 

2214 self.session.xenapi.SR.set_physical_utilisation( 

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

2216 ) 

2217 

2218 # -------------------------------------------------------------------------- 

2219 # Generic helpers. 

2220 # -------------------------------------------------------------------------- 

2221 

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

2223 self.vdi_type = vdi_type 

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

2225 

2226 def _determine_type_and_path(self): 

2227 """ 

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

2229 """ 

2230 

2231 # 1. Check vdi_ref and vdi_type in config. 

2232 try: 

2233 vdi_ref = self.session.xenapi.VDI.get_by_uuid(self.uuid) 

2234 if vdi_ref: 

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

2236 vdi_type = sm_config.get('vdi_type') 

2237 if vdi_type: 

2238 # Update parent fields. 

2239 self._set_type(vdi_type) 

2240 self.sm_config_override = sm_config 

2241 self._update_device_name( 

2242 self._linstor.get_volume_name(self.uuid) 

2243 ) 

2244 return 

2245 except Exception: 

2246 pass 

2247 

2248 # 2. Otherwise use the LINSTOR volume manager directly. 

2249 # It's probably a new VDI created via snapshot. 

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

2251 self._set_type(volume_metadata.get(VDI_TYPE_TAG)) 

2252 if not self.vdi_type: 

2253 raise xs_errors.XenError( 

2254 'VDIUnavailable', 

2255 opterr='failed to get vdi_type in metadata' 

2256 ) 

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

2258 

2259 def _update_device_name(self, device_name): 

2260 self._device_name = device_name 

2261 

2262 # Mark path of VDI parent class. 

2263 if device_name: 

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

2265 else: 

2266 self.path = None 

2267 

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

2269 """ 

2270 Snapshot self and return the snapshot VDI object. 

2271 """ 

2272 

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

2274 snap_path = self._linstor.shallow_clone_volume( 

2275 self.uuid, snap_uuid, persistent=False 

2276 ) 

2277 

2278 # 2. Write the snapshot content. 

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

2280 self.linstorcowutil.snapshot( 

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

2282 ) 

2283 

2284 # 3. Get snapshot parent. 

2285 snap_parent = self.linstorcowutil.get_parent(snap_uuid) 

2286 

2287 # 4. Update metadata. 

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

2289 volume_metadata = { 

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

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

2292 IS_A_SNAPSHOT_TAG: bool(snap_of_uuid), 

2293 SNAPSHOT_OF_TAG: snap_of_uuid, 

2294 SNAPSHOT_TIME_TAG: '', 

2295 TYPE_TAG: self.ty, 

2296 VDI_TYPE_TAG: snap_vdi_type, 

2297 READ_ONLY_TAG: False, 

2298 METADATA_OF_POOL_TAG: '' 

2299 } 

2300 self._linstor.set_volume_metadata(snap_uuid, volume_metadata) 

2301 

2302 # 5. Set size. 

2303 snap_vdi = LinstorVDI(self.sr, snap_uuid) 

2304 if not snap_vdi._exists: 

2305 raise xs_errors.XenError('VDISnapshot') 

2306 

2307 volume_info = self._linstor.get_volume_info(snap_uuid) 

2308 

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

2310 snap_vdi.utilisation = volume_info.allocated_size 

2311 

2312 # 6. Update sm config. 

2313 snap_vdi.sm_config = {} 

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

2315 if snap_parent: 

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

2317 snap_vdi.parent = snap_parent 

2318 

2319 snap_vdi.label = self.label 

2320 snap_vdi.description = self.description 

2321 

2322 self._linstor.mark_volume_as_persistent(snap_uuid) 

2323 

2324 return snap_vdi 

2325 

2326 # -------------------------------------------------------------------------- 

2327 # Implement specific SR methods. 

2328 # -------------------------------------------------------------------------- 

2329 

2330 @override 

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

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

2333 volume_uuid = self._linstor.get_volume_uuid_from_device_path(oldpath) 

2334 self._linstor.update_volume_name(volume_uuid, newpath) 

2335 

2336 @override 

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

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

2339 # If cbt enabled, save file consistency state. 

2340 if cbtlog is not None: 

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

2342 consistency_state = False 

2343 else: 

2344 consistency_state = True 

2345 util.SMlog( 

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

2347 .format(consistency_state, vdi_uuid) 

2348 ) 

2349 else: 

2350 consistency_state = None 

2351 

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

2353 raise xs_errors.XenError('Unimplemented') 

2354 

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

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

2357 try: 

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

2359 finally: 

2360 self.disable_leaf_on_secondary(vdi_uuid, secondary=secondary) 

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

2362 

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

2364 util.SMlog( 

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

2366 .format(self.uuid, snap_type) 

2367 ) 

2368 

2369 # 1. Checks... 

2370 if self.hidden: 

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

2372 

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

2374 

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

2376 if depth == -1: 

2377 raise xs_errors.XenError( 

2378 'VDIUnavailable', 

2379 opterr='failed to get COW depth' 

2380 ) 

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

2382 raise xs_errors.XenError('SnapshotChainTooLong') 

2383 

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

2385 self.linstorcowutil.create_chain_paths(self.uuid, readonly=True) 

2386 

2387 volume_path = self.path 

2388 if not util.pathexists(volume_path): 

2389 raise xs_errors.XenError( 

2390 'EIO', 

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

2392 ) 

2393 

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

2395 base_uuid = util.gen_uuid() 

2396 snap_uuid = None 

2397 

2398 if snap_type == VDI.SNAPSHOT_DOUBLE: 

2399 snap_uuid = util.gen_uuid() 

2400 

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

2402 

2403 active_uuid = self.uuid 

2404 self.sr._journaler.create( 

2405 LinstorJournaler.CLONE, active_uuid, clone_info 

2406 ) 

2407 

2408 try: 

2409 # 3. Self becomes the new base. 

2410 # The device path remains the same. 

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

2412 self.uuid = base_uuid 

2413 self.location = self.uuid 

2414 self.read_only = True 

2415 self.managed = False 

2416 

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

2418 active_vdi = self._create_snapshot(snap_vdi_type, active_uuid) 

2419 

2420 snap_vdi = None 

2421 if snap_type == VDI.SNAPSHOT_DOUBLE: 

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

2423 

2424 self.label = 'base copy' 

2425 self.description = '' 

2426 

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

2428 # in subsequent scans. 

2429 self._mark_hidden() 

2430 self._linstor.update_volume_metadata( 

2431 self.uuid, {READ_ONLY_TAG: True} 

2432 ) 

2433 

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

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

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

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

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

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

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

2441 active_vdi.sm_config[key] = sm_config[key] 

2442 

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

2444 # delete base if unused. 

2445 introduce_parent = True 

2446 try: 

2447 snap_parent = None 

2448 if snap_vdi: 

2449 snap_parent = snap_vdi.parent 

2450 

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

2452 snap_type == VDI.SNAPSHOT_SINGLE or 

2453 snap_type == VDI.SNAPSHOT_INTERNAL or 

2454 snap_parent != self.uuid 

2455 ): 

2456 util.SMlog( 

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

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

2459 ) 

2460 introduce_parent = False 

2461 self._linstor.destroy_volume(self.uuid) 

2462 except Exception as e: 

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

2464 pass 

2465 

2466 # 8. Introduce the new VDI records. 

2467 if snap_vdi: 

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

2469 # new snapshot disk. 

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

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

2472 # TODO: Maybe remove key_hash support. 

2473 if 'key_hash' in sm_config: 

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

2475 # If we have CBT enabled on the VDI, 

2476 # set CBT status for the new snapshot disk. 

2477 if cbtlog: 

2478 snap_vdi.cbt_enabled = True 

2479 

2480 if snap_vdi: 

2481 snap_vdi_ref = snap_vdi._db_introduce() 

2482 util.SMlog( 

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

2484 .format(snap_vdi_ref, snap_vdi.uuid) 

2485 ) 

2486 if introduce_parent: 

2487 base_vdi_ref = self._db_introduce() 

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

2489 util.SMlog( 

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

2491 .format(base_vdi_ref, self.uuid) 

2492 ) 

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

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

2495 NAME_DESCRIPTION_TAG: util.to_plain_string( 

2496 self.description 

2497 ), 

2498 READ_ONLY_TAG: True, 

2499 METADATA_OF_POOL_TAG: '' 

2500 }) 

2501 

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

2503 if snap_type == VDI.SNAPSHOT_DOUBLE and cbtlog: 

2504 try: 

2505 self._cbt_snapshot(snap_uuid, cbt_consistency) 

2506 except Exception: 

2507 # CBT operation failed. 

2508 # TODO: Implement me. 

2509 raise 

2510 

2511 if snap_type != VDI.SNAPSHOT_INTERNAL: 

2512 self.sr._update_stats(self.size) 

2513 

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

2515 ret_vdi = snap_vdi 

2516 if not ret_vdi: 

2517 ret_vdi = self 

2518 if not ret_vdi: 

2519 ret_vdi = active_vdi 

2520 

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

2522 self.session.xenapi.VDI.set_sm_config( 

2523 vdi_ref, active_vdi.sm_config 

2524 ) 

2525 except Exception as e: 

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

2527 try: 

2528 self.sr._handle_interrupted_clone( 

2529 active_uuid, clone_info, force_undo=True 

2530 ) 

2531 self.sr._journaler.remove(LinstorJournaler.CLONE, active_uuid) 

2532 except Exception as clean_error: 

2533 util.SMlog( 

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

2535 .format(clean_error) 

2536 ) 

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

2538 

2539 self.sr._journaler.remove(LinstorJournaler.CLONE, active_uuid) 

2540 

2541 return ret_vdi.get_params() 

2542 

2543 @staticmethod 

2544 def _start_persistent_http_server(volume_name): 

2545 pid_path = None 

2546 http_server = None 

2547 

2548 try: 

2549 if volume_name == HA_VOLUME_NAME: 

2550 port = '8076' 

2551 else: 

2552 port = '8077' 

2553 

2554 try: 

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

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

2557 # block indefinitely. 

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

2559 host_ip = util.get_this_host_address(session) 

2560 except: 

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

2562 host_ip, _ = get_ips_from_xha_config_file() 

2563 if not host_ip: 

2564 raise Exception( 

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

2566 ) 

2567 

2568 arguments = [ 

2569 'http-disk-server', 

2570 '--disk', 

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

2572 '--ip', 

2573 host_ip, 

2574 '--port', 

2575 port 

2576 ] 

2577 

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

2579 http_server = subprocess.Popen( 

2580 [FORK_LOG_DAEMON] + arguments, 

2581 stdout=subprocess.PIPE, 

2582 stderr=subprocess.STDOUT, 

2583 universal_newlines=True, 

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

2585 # touch the current one. 

2586 preexec_fn=os.setsid 

2587 ) 

2588 

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

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

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

2592 

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

2594 def is_ready(): 

2595 while http_server.poll() is None: 

2596 line = http_server.stdout.readline() 

2597 if reg_server_ready.search(line): 

2598 return True 

2599 return False 

2600 try: 

2601 if not util.timeout_call(10, is_ready): 

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

2603 except util.TimeoutException: 

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

2605 except Exception as e: 

2606 if pid_path: 

2607 try: 

2608 os.remove(pid_path) 

2609 except Exception: 

2610 pass 

2611 

2612 if http_server: 

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

2614 try: 

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

2616 except: 

2617 pass 

2618 

2619 raise xs_errors.XenError( 

2620 'VDIUnavailable', 

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

2622 ) 

2623 

2624 def _start_persistent_nbd_server(self, volume_name): 

2625 pid_path = None 

2626 nbd_path = None 

2627 nbd_server = None 

2628 

2629 try: 

2630 # We use a precomputed device size. 

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

2632 if volume_name == HA_VOLUME_NAME: 

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

2634 port = '8076' 

2635 device_size = 4 * 1024 * 1024 

2636 else: 

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

2638 port = '8077' 

2639 device_size = 256 * 1024 * 1024 

2640 

2641 try: 

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

2643 ips = util.get_host_addresses(session) 

2644 except Exception as e: 

2645 _, ips = get_ips_from_xha_config_file() 

2646 if not ips: 

2647 raise Exception( 

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

2649 ) 

2650 ips = ips.values() 

2651 

2652 arguments = [ 

2653 'nbd-http-server', 

2654 '--socket-path', 

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

2656 '--nbd-name', 

2657 volume_name, 

2658 '--urls', 

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

2660 '--device-size', 

2661 str(device_size) 

2662 ] 

2663 

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

2665 nbd_server = subprocess.Popen( 

2666 [FORK_LOG_DAEMON] + arguments, 

2667 stdout=subprocess.PIPE, 

2668 stderr=subprocess.STDOUT, 

2669 universal_newlines=True, 

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

2671 # touch the current one. 

2672 preexec_fn=os.setsid 

2673 ) 

2674 

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

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

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

2678 

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

2680 def get_nbd_path(): 

2681 while nbd_server.poll() is None: 

2682 line = nbd_server.stdout.readline() 

2683 match = reg_nbd_path.search(line) 

2684 if match: 

2685 return match.group(1) 

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

2687 try: 

2688 nbd_path = util.timeout_call(10, get_nbd_path) 

2689 if nbd_path is None: 

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

2691 except util.TimeoutException: 

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

2693 

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

2695 os.symlink(nbd_path, self.path) 

2696 except Exception as e: 

2697 if pid_path: 

2698 try: 

2699 os.remove(pid_path) 

2700 except Exception: 

2701 pass 

2702 

2703 if nbd_path: 

2704 try: 

2705 os.remove(nbd_path) 

2706 except Exception: 

2707 pass 

2708 

2709 if nbd_server: 

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

2711 try: 

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

2713 except: 

2714 pass 

2715 

2716 raise xs_errors.XenError( 

2717 'VDIUnavailable', 

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

2719 ) 

2720 

2721 @classmethod 

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

2723 try: 

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

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

2726 return 

2727 

2728 pid = None 

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

2730 try: 

2731 pid = int(pid_file.read()) 

2732 except Exception: 

2733 pass 

2734 

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

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

2737 try: 

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

2739 except Exception as e: 

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

2741 

2742 os.remove(path) 

2743 except: 

2744 pass 

2745 

2746 @classmethod 

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

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

2749 

2750 @classmethod 

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

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

2753 

2754 def _check_http_nbd_volume_name(self): 

2755 volume_name = self.path[14:] 

2756 if volume_name not in [ 

2757 HA_VOLUME_NAME, REDO_LOG_VOLUME_NAME 

2758 ]: 

2759 raise xs_errors.XenError( 

2760 'VDIUnavailable', 

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

2762 ) 

2763 return volume_name 

2764 

2765 def _attach_using_http_nbd(self): 

2766 volume_name = self._check_http_nbd_volume_name() 

2767 

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

2769 self._kill_persistent_nbd_server(volume_name) 

2770 self._kill_persistent_http_server(volume_name) 

2771 

2772 # 0. Fetch drbd path. 

2773 must_get_device_path = True 

2774 if not self.sr.is_master(): 

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

2776 try: 

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

2778 except Exception as e: 

2779 raise xs_errors.XenError( 

2780 'VDIUnavailable', 

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

2782 .format(self.uuid, e) 

2783 ) 

2784 

2785 hostname = socket.gethostname() 

2786 must_get_device_path = hostname in volume_info.diskful 

2787 

2788 drbd_path = None 

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

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

2791 # or diskful available to init HA. 

2792 # It also avoid this error in xensource.log 

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

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

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

2796 available = False 

2797 try: 

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

2799 available = util.pathexists(drbd_path) 

2800 except Exception: 

2801 pass 

2802 

2803 if not available: 

2804 raise xs_errors.XenError( 

2805 'VDIUnavailable', 

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

2807 ) 

2808 

2809 # 1. Prepare http-nbd folder. 

2810 try: 

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

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

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

2814 os.remove(self.path) 

2815 except OSError as e: 

2816 if e.errno != errno.EEXIST: 

2817 raise xs_errors.XenError( 

2818 'VDIUnavailable', 

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

2820 ) 

2821 

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

2823 http_service = None 

2824 if drbd_path: 

2825 assert(drbd_path in ( 

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

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

2828 )) 

2829 self._start_persistent_http_server(volume_name) 

2830 

2831 # 3. Start NBD server in all cases. 

2832 try: 

2833 self._start_persistent_nbd_server(volume_name) 

2834 except Exception as e: 

2835 if drbd_path: 

2836 self._kill_persistent_http_server(volume_name) 

2837 raise 

2838 

2839 self.attached = True 

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

2841 

2842 def _detach_using_http_nbd(self): 

2843 volume_name = self._check_http_nbd_volume_name() 

2844 self._kill_persistent_nbd_server(volume_name) 

2845 self._kill_persistent_http_server(volume_name) 

2846 

2847# ------------------------------------------------------------------------------ 

2848 

2849 

2850if __name__ == '__main__': 

2851 def run(): 

2852 SRCommand.run(LinstorSR, DRIVER_INFO) 

2853 

2854 if not TRACE_PERFS: 

2855 run() 

2856 else: 

2857 util.make_profile('LinstorSR', run) 

2858else: 

2859 SR.registerSR(LinstorSR)