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, MultiLinstorCowUtil 

23 from linstorjournaler import LinstorJournaler 

24 from linstorvolumemanager import get_controller_uri 

25 from linstorvolumemanager import get_controller_node_name 

26 from linstorvolumemanager import LinstorVolumeManager 

27 from linstorvolumemanager import LinstorVolumeManagerError 

28 from linstorvolumemanager import DATABASE_VOLUME_NAME 

29 from linstorvolumemanager import PERSISTENT_PREFIX 

30 

31 LINSTOR_AVAILABLE = True 

32except ImportError: 

33 PERSISTENT_PREFIX = 'unknown' 

34 

35 LINSTOR_AVAILABLE = False 

36 

37import blktap2 

38import cleanup 

39import errno 

40import functools 

41import lock 

42import lvutil 

43import os 

44import re 

45import scsiutil 

46import signal 

47import socket 

48import SR 

49import SRCommand 

50import subprocess 

51import sys 

52import time 

53import traceback 

54import util 

55import VDI 

56import xml.etree.ElementTree as xml_parser 

57import xmlrpc.client 

58import xs_errors 

59 

60from cowutil import CowUtil, ImageFormat, getImageStringFromVdiType 

61from srmetadata import \ 

62 NAME_LABEL_TAG, NAME_DESCRIPTION_TAG, IS_A_SNAPSHOT_TAG, SNAPSHOT_OF_TAG, \ 

63 TYPE_TAG, VDI_TYPE_TAG, READ_ONLY_TAG, SNAPSHOT_TIME_TAG, \ 

64 METADATA_OF_POOL_TAG 

65from vditype import VdiType 

66 

67HIDDEN_TAG = 'hidden' 

68 

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

70 

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

72 

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

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

75# specific conditions: 

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

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

78USE_HTTP_NBD_SERVERS = True 

79 

80# Useful flag to trace calls using cProfile. 

81TRACE_PERFS = False 

82 

83# Enable/Disable COW key hash support. 

84USE_KEY_HASH = False 

85 

86# Special volumes. 

87HA_VOLUME_NAME = PERSISTENT_PREFIX + 'ha-statefile' 

88REDO_LOG_VOLUME_NAME = PERSISTENT_PREFIX + 'redo-log' 

89 

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

91 

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

93# 'VDI_CONFIG_CBT', 'SR_PROBE' 

94 

95CAPABILITIES = [ 

96 'ATOMIC_PAUSE', 

97 'SR_UPDATE', 

98 'VDI_CREATE', 

99 'VDI_DELETE', 

100 'VDI_UPDATE', 

101 'VDI_ATTACH', 

102 'VDI_DETACH', 

103 'VDI_ACTIVATE', 

104 'VDI_DEACTIVATE', 

105 'VDI_CLONE', 

106 'VDI_MIRROR', 

107 'VDI_RESIZE', 

108 'VDI_SNAPSHOT', 

109 'VDI_GENERATE_CONFIG' 

110] 

111 

112CONFIGURATION = [ 

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

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

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

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

117] 

118 

119DRIVER_INFO = { 

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

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

122 'vendor': 'Vates', 

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

124 'driver_version': '1.0', 

125 'required_api_version': '1.0', 

126 'capabilities': CAPABILITIES, 

127 'configuration': CONFIGURATION 

128} 

129 

130DRIVER_CONFIG = {'ATTACH_FROM_CONFIG_WITH_TAPDISK': False} 

131 

132OPS_EXCLUSIVE = [ 

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

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

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

136] 

137 

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

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

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

141 

142 

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

144 volume_metadata = linstor.get_volume_metadata(vdi_uuid) 

145 vdi_type = volume_metadata.get(VDI_TYPE_TAG) 

146 if not VdiType.isCowImage(vdi_type): 

147 return 

148 

149 device_path = linstor.get_device_path(vdi_uuid) 

150 

151 linstorcowutil = LinstorCowUtil(session, linstor, vdi_type) 

152 

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

154 # there is nothing to do. 

155 cow_size = linstorcowutil.compute_volume_size( 

156 linstorcowutil.get_size_virt(vdi_uuid) 

157 ) 

158 

159 volume_info = linstor.get_volume_info(vdi_uuid) 

160 volume_size = volume_info.virtual_size 

161 

162 if cow_size > volume_size: 

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

164 

165 

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

167 volume_metadata = linstor.get_volume_metadata(vdi_uuid) 

168 vdi_type = volume_metadata.get(VDI_TYPE_TAG) 

169 if not VdiType.isCowImage(vdi_type): 

170 return 

171 

172 def check_vbd_count(): 

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

174 vbds = session.xenapi.VBD.get_all_records_where( 

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

176 ) 

177 

178 num_plugged = 0 

179 for vbd_rec in vbds.values(): 

180 if vbd_rec['currently_attached']: 

181 num_plugged += 1 

182 if num_plugged > 1: 

183 raise xs_errors.XenError( 

184 'VDIUnavailable', 

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

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

187 ) 

188 

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

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

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

192 

193 device_path = linstor.get_device_path(vdi_uuid) 

194 linstorcowutil = LinstorCowUtil(session, linstor, vdi_type) 

195 new_volume_size = LinstorVolumeManager.round_up_volume_size( 

196 linstorcowutil.get_size_phys(vdi_uuid) 

197 ) 

198 

199 volume_info = linstor.get_volume_info(vdi_uuid) 

200 old_volume_size = volume_info.virtual_size 

201 linstorcowutil.deflate(device_path, new_volume_size, old_volume_size) 

202 

203 

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

205 # This function must always return without errors. 

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

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

208 try: 

209 detach_thin_impl(session, linstor, sr_uuid, vdi_uuid) 

210 except Exception as e: 

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

212 

213 

214def get_ips_from_xha_config_file(): 

215 ips = dict() 

216 host_id = None 

217 try: 

218 # Ensure there is no dirty read problem. 

219 # For example if the HA is reloaded. 

220 tree = util.retry( 

221 lambda: xml_parser.parse(XHA_CONFIG_PATH), 

222 maxretry=10, 

223 period=1 

224 ) 

225 except: 

226 return (None, ips) 

227 

228 def parse_host_nodes(ips, node): 

229 current_id = None 

230 current_ip = None 

231 

232 for sub_node in node: 

233 if sub_node.tag == 'IPaddress': 

234 current_ip = sub_node.text 

235 elif sub_node.tag == 'HostID': 

236 current_id = sub_node.text 

237 else: 

238 continue 

239 

240 if current_id and current_ip: 

241 ips[current_id] = current_ip 

242 return 

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

244 

245 def parse_common_config(ips, node): 

246 for sub_node in node: 

247 if sub_node.tag == 'host': 

248 parse_host_nodes(ips, sub_node) 

249 

250 def parse_local_config(ips, node): 

251 for sub_node in node: 

252 if sub_node.tag == 'localhost': 

253 for host_node in sub_node: 

254 if host_node.tag == 'HostID': 

255 return host_node.text 

256 

257 for node in tree.getroot(): 

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

259 parse_common_config(ips, node) 

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

261 host_id = parse_local_config(ips, node) 

262 else: 

263 continue 

264 

265 if ips and host_id: 

266 break 

267 

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

269 

270 

271def activate_lvm_group(group_name): 

272 path = group_name.split('/') 

273 assert path and len(path) <= 2 

274 try: 

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

276 except Exception as e: 

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

278 

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

280 

281# Usage example: 

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

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

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

285 

286 

287class LinstorSR(SR.SR): 

288 DRIVER_TYPE = 'linstor' 

289 

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

291 PROVISIONING_DEFAULT = 'thin' 

292 

293 MANAGER_PLUGIN = 'linstor-manager' 

294 

295 INIT_STATUS_NOT_SET = 0 

296 INIT_STATUS_IN_PROGRESS = 1 

297 INIT_STATUS_OK = 2 

298 INIT_STATUS_FAIL = 3 

299 

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

301 # SR methods. 

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

303 

304 _linstor: Optional["LinstorVolumeManager"] = None 

305 

306 @override 

307 @staticmethod 

308 def handles(type) -> bool: 

309 return type == LinstorSR.DRIVER_TYPE 

310 

311 def __init__(self, srcmd, sr_uuid): 

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

313 self._init_image_formats( 

314 preferred_image_formats=[ImageFormat.VHD], 

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

316 ) 

317 

318 @override 

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

320 if not LINSTOR_AVAILABLE: 

321 raise util.SMException( 

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

323 ) 

324 

325 # Check parameters. 

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

327 raise xs_errors.XenError('LinstorConfigGroupNameMissing') 

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

329 raise xs_errors.XenError('LinstorConfigRedundancyMissing') 

330 

331 self.driver_config = DRIVER_CONFIG 

332 

333 # Check provisioning config. 

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

335 if provisioning: 

336 if provisioning in self.PROVISIONING_TYPES: 

337 self._provisioning = provisioning 

338 else: 

339 raise xs_errors.XenError( 

340 'InvalidArg', 

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

342 self.PROVISIONING_TYPES 

343 ) 

344 ) 

345 else: 

346 self._provisioning = self.PROVISIONING_DEFAULT 

347 

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

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

350 util.strtobool(monitor_db_quorum) 

351 

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

353 # 'vdi_attach_from_config' command is executed. 

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

355 if self._has_session: 

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

357 else: 

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

359 

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

361 if provisioning in self.PROVISIONING_TYPES: 

362 self._provisioning = provisioning 

363 

364 # Define properties for SR parent class. 

365 self.ops_exclusive = OPS_EXCLUSIVE 

366 self.path = LinstorVolumeManager.DEV_ROOT_PATH 

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

368 self.sr_vditype = SR.DEFAULT_TAP 

369 

370 if self.cmd == 'sr_create': 

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

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

373 self._journaler = None 

374 

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

376 class LinstorProxy: 

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

378 self.sr = sr 

379 

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

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

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

383 

384 self._linstor_proxy = LinstorProxy(self) 

385 

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

387 

388 self._vdi_shared_time = 0 

389 

390 self._init_status = self.INIT_STATUS_NOT_SET 

391 

392 self._vdis_loaded = False 

393 self._all_volume_info_cache = None 

394 self._all_volume_metadata_cache = None 

395 self._multi_cowutil = None 

396 

397 # To remove in python 3.10. 

398 # Use directly @staticmethod instead. 

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

400 def _locked_load(method): 

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

402 self._init_status = self.INIT_STATUS_OK 

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

404 

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

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

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

408 activate_lvm_group(self._group_name) 

409 

410 if not self._has_session: 

411 if self.srcmd.cmd in ( 

412 'vdi_attach_from_config', 

413 'vdi_detach_from_config', 

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

415 # empty command. 

416 None 

417 ): 

418 def create_linstor(uri, attempt_count=30): 

419 self._linstor = LinstorVolumeManager( 

420 uri, 

421 self._group_name, 

422 logger=util.SMlog, 

423 attempt_count=attempt_count 

424 ) 

425 

426 controller_uri = get_controller_uri() 

427 if controller_uri: 

428 create_linstor(controller_uri) 

429 else: 

430 def connect(): 

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

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

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

434 controller_uri = 'linstor://' + ip 

435 try: 

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

437 create_linstor(controller_uri, attempt_count=0) 

438 return controller_uri 

439 except: 

440 pass 

441 

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

443 if not controller_uri: 

444 raise xs_errors.XenError( 

445 'SRUnavailable', 

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

447 ) 

448 

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

450 

451 if not self.is_master(): 

452 if self.cmd in [ 

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

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

455 'vdi_snapshot', 'vdi_clone' 

456 ]: 

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

458 raise xs_errors.XenError('LinstorMaster') 

459 

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

461 # the VDI before the LinstorJournaler/LinstorVolumeManager 

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

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

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

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

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

467 self._vdi_shared_time = time.time() 

468 

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

470 try: 

471 self._reconnect() 

472 except Exception as e: 

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

474 

475 if self._linstor: 

476 try: 

477 hosts = self._linstor.disconnected_hosts 

478 except Exception as e: 

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

480 

481 if hosts: 

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

483 

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

485 if ( 

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

487 self.cmd != 'vdi_create' 

488 ): 

489 self._linstor.ensure_volume_is_not_locked( 

490 self.srcmd.params['vdi_uuid'] 

491 ) 

492 

493 try: 

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

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

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

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

498 # 

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

500 # resourceless volumes. 

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

502 'vdi_attach', 'vdi_detach', 

503 'vdi_activate', 'vdi_deactivate', 

504 'vdi_epoch_begin', 'vdi_epoch_end', 

505 'vdi_update', 'vdi_destroy', 

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

507 ]: 

508 journaler = self._get_journaler() 

509 load_vdis = ( 

510 self.cmd == 'sr_scan' or 

511 self.cmd == 'sr_attach' 

512 ) or len( 

513 journaler.get_all(LinstorJournaler.INFLATE) 

514 ) or len( 

515 journaler.get_all(LinstorJournaler.CLONE) 

516 ) 

517 

518 if load_vdis: 

519 self._load_vdis() 

520 

521 self._linstor.remove_resourceless_volumes() 

522 

523 self._synchronize_metadata() 

524 except Exception as e: 

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

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

527 # from the XAPI database otherwise. 

528 raise e 

529 util.SMlog( 

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

531 ) 

532 util.SMlog(traceback.format_exc()) 

533 

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

535 

536 @functools.wraps(wrapped_method) 

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

538 if self._init_status in \ 

539 (self.INIT_STATUS_OK, self.INIT_STATUS_IN_PROGRESS): 

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

541 if self._init_status == self.INIT_STATUS_FAIL: 

542 util.SMlog( 

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

544 .format(method) 

545 ) 

546 else: 

547 try: 

548 self._init_status = self.INIT_STATUS_IN_PROGRESS 

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

550 except Exception: 

551 if self._init_status != self.INIT_STATUS_OK: 

552 self._init_status = self.INIT_STATUS_FAIL 

553 raise 

554 

555 return wrap 

556 

557 @override 

558 def cleanup(self) -> None: 

559 if self._vdi_shared_time: 

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

561 

562 @override 

563 @_locked_load 

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

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

566 

567 host_adresses = util.get_host_addresses(self.session) 

568 if self._redundancy > len(host_adresses): 

569 raise xs_errors.XenError( 

570 'LinstorSRCreate', 

571 opterr='Redundancy greater than host count' 

572 ) 

573 

574 srs = util.get_linstor_srs_uuid(self.session) 

575 try: 

576 srs.pop(self.uuid) 

577 except KeyError: 

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

579 pass 

580 

581 pbd_ref = util.find_pbd_ref_from_dconf_value( 

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

583 ) 

584 if pbd_ref: 

585 raise xs_errors.XenError( 

586 'LinstorSRCreate', 

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

588 ) 

589 

590 if srs: 

591 raise xs_errors.XenError( 

592 'LinstorSRCreate', 

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

594 ) 

595 

596 online_hosts = util.get_enabled_hosts(self.session) 

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

598 raise xs_errors.XenError( 

599 'LinstorSRCreate', 

600 opterr='Not enough online hosts' 

601 ) 

602 

603 ips = {} 

604 for host_ref in online_hosts: 

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

606 hostname = record['hostname'] 

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

608 

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

610 raise xs_errors.XenError( 

611 'LinstorSRCreate', 

612 opterr='Multiple hosts with same hostname' 

613 ) 

614 

615 # Ensure ports are opened and LINSTOR satellites 

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

617 # must be stopped. 

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

619 

620 # Create SR. 

621 # Throw if the SR already exists. 

622 try: 

623 self._linstor = LinstorVolumeManager.create_sr( 

624 self._group_name, 

625 ips, 

626 self._redundancy, 

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

628 logger=util.SMlog 

629 ) 

630 

631 util.SMlog( 

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

633 ) 

634 self._update_drbd_reactor_on_all_hosts(enabled=True) 

635 except Exception as e: 

636 if not self._linstor: 

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

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

639 

640 try: 

641 self._linstor.destroy() 

642 except Exception as e2: 

643 util.SMlog( 

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

645 .format(e2) 

646 ) 

647 raise e 

648 

649 @override 

650 @_locked_load 

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

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

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

654 

655 assert self._linstor 

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

657 raise xs_errors.XenError('SRNotEmpty') 

658 

659 node_name = get_controller_node_name() 

660 if not node_name: 

661 raise xs_errors.XenError( 

662 'LinstorSRDelete', 

663 opterr='Cannot get controller node name' 

664 ) 

665 

666 host_ref = None 

667 if node_name == 'localhost': 

668 host_ref = util.get_this_host_ref(self.session) 

669 else: 

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

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

672 if r_name == node_name: 

673 host_ref = slave 

674 break 

675 

676 if not host_ref: 

677 raise xs_errors.XenError( 

678 'LinstorSRDelete', 

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

680 node_name 

681 ) 

682 ) 

683 

684 try: 

685 if self._monitor_db_quorum: 

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

687 self._update_drbd_reactor_on_all_hosts( 

688 controller_node_name=node_name, enabled=False 

689 ) 

690 

691 args = { 

692 'groupName': self._group_name, 

693 } 

694 self._exec_manager_command( 

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

696 ) 

697 except Exception as e: 

698 try: 

699 self._update_drbd_reactor_on_all_hosts( 

700 controller_node_name=node_name, enabled=True 

701 ) 

702 if self._monitor_db_quorum: 

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

704 except Exception as e2: 

705 util.SMlog( 

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

707 .format(e2) 

708 ) 

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

710 raise xs_errors.XenError( 

711 'LinstorSRDelete', 

712 opterr=str(e) 

713 ) 

714 

715 lock.Lock.cleanupAll(self.uuid) 

716 

717 @override 

718 @_locked_load 

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

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

721 

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

723 if not self._linstor: 

724 raise xs_errors.XenError( 

725 'SRUnavailable', 

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

727 ) 

728 

729 self._update_stats(0) 

730 

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

732 xenapi = self.session.xenapi 

733 self._linstor.metadata = { 

734 NAME_LABEL_TAG: util.to_plain_string( 

735 xenapi.SR.get_name_label(self.sr_ref) 

736 ), 

737 NAME_DESCRIPTION_TAG: util.to_plain_string( 

738 xenapi.SR.get_name_description(self.sr_ref) 

739 ) 

740 } 

741 

742 @override 

743 @_locked_load 

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

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

746 

747 if not self._linstor: 

748 raise xs_errors.XenError( 

749 'SRUnavailable', 

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

751 ) 

752 

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

754 self._linstor.set_drbd_ha_properties(DATABASE_VOLUME_NAME) 

755 

756 @override 

757 @_locked_load 

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

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

760 cleanup.abort(self.uuid) 

761 

762 @override 

763 @_locked_load 

764 def probe(self) -> str: 

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

766 # TODO 

767 return '' 

768 

769 @override 

770 @_locked_load 

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

772 if self._init_status == self.INIT_STATUS_FAIL: 

773 return 

774 

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

776 if not self._linstor: 

777 raise xs_errors.XenError( 

778 'SRUnavailable', 

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

780 ) 

781 

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

783 # are loaded. 

784 self._load_vdis() 

785 self._update_physical_size() 

786 

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

788 if self.vdis[vdi_uuid].deleted: 

789 del self.vdis[vdi_uuid] 

790 

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

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

793 try: 

794 self._linstor.get_database_path() 

795 except Exception as e: 

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

797 # VDIs in the XAPI database... 

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

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

800 ): 

801 raise xs_errors.XenError( 

802 'SRUnavailable', 

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

804 ) 

805 

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

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

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

809 self._kick_gc() 

810 

811 def is_master(self): 

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

813 if 'SRmaster' not in self.dconf: 

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

815 else: 

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

817 

818 return self._is_master 

819 

820 @override 

821 @_locked_load 

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

823 return LinstorVDI(self, uuid) 

824 

825 # To remove in python 3.10 

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

827 _locked_load = staticmethod(_locked_load) 

828 

829 # -------------------------------------------------------------------------- 

830 # Lock. 

831 # -------------------------------------------------------------------------- 

832 

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

834 master = util.get_master_ref(self.session) 

835 

836 command = 'lockVdi' 

837 args = { 

838 'groupName': self._group_name, 

839 'srUuid': self.uuid, 

840 'vdiUuid': vdi_uuid, 

841 'locked': str(locked) 

842 } 

843 

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

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

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

847 # 

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

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

850 # to implement without impacting performance. 

851 if not locked: 

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

853 timeout = LinstorVolumeManager.LOCKED_EXPIRATION_DELAY * 0.7 

854 if elapsed_time >= timeout: 

855 util.SMlog( 

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

857 .format(vdi_uuid) 

858 ) 

859 return 

860 

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

862 

863 # -------------------------------------------------------------------------- 

864 # Network. 

865 # -------------------------------------------------------------------------- 

866 

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

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

869 host_uuid = host_rec['uuid'] 

870 

871 try: 

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

873 host_ref, self.MANAGER_PLUGIN, command, args 

874 ) 

875 except Exception as e: 

876 util.SMlog( 

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

878 host_uuid, self.MANAGER_PLUGIN, command, args 

879 ) 

880 ) 

881 raise e 

882 

883 util.SMlog( 

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

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

886 ) 

887 ) 

888 if ret == 'False': 

889 raise xs_errors.XenError( 

890 error, 

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

892 ) 

893 

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

895 self._exec_manager_command( 

896 host, 

897 'prepareSr' if enabled else 'releaseSr', 

898 {'groupName': group_name}, 

899 'SRUnavailable' 

900 ) 

901 

902 def _prepare_sr_on_all_hosts(self, group_name, enabled): 

903 master = util.get_master_ref(self.session) 

904 self._prepare_sr(master, group_name, enabled) 

905 

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

907 self._prepare_sr(slave, group_name, enabled) 

908 

909 def _update_drbd_reactor(self, host, enabled): 

910 self._exec_manager_command( 

911 host, 

912 'updateDrbdReactor', 

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

914 'SRUnavailable' 

915 ) 

916 

917 def _update_drbd_reactor_on_all_hosts( 

918 self, enabled, controller_node_name=None 

919 ): 

920 if controller_node_name == 'localhost': 

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

922 util.get_this_host_ref(self.session) 

923 )['hostname'] 

924 assert controller_node_name 

925 assert controller_node_name != 'localhost' 

926 

927 controller_host = None 

928 secondary_hosts = [] 

929 

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

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

932 hostname = host_rec['hostname'] 

933 if controller_node_name == hostname: 

934 controller_host = host_ref 

935 else: 

936 secondary_hosts.append((host_ref, hostname)) 

937 

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

939 if controller_node_name and not controller_host: 

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

941 controller_node_name 

942 )) 

943 

944 if enabled and controller_host: 

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

946 action_name, controller_node_name 

947 )) 

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

949 # node name first. 

950 self._update_drbd_reactor(controller_host, enabled) 

951 

952 for host_ref, hostname in secondary_hosts: 

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

954 action_name, hostname 

955 )) 

956 self._update_drbd_reactor(host_ref, enabled) 

957 

958 if not enabled and controller_host: 

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

960 action_name, controller_node_name 

961 )) 

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

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

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

965 self._update_drbd_reactor(controller_host, enabled) 

966 

967 # -------------------------------------------------------------------------- 

968 # Metadata. 

969 # -------------------------------------------------------------------------- 

970 

971 def _synchronize_metadata_and_xapi(self): 

972 try: 

973 # First synch SR parameters. 

974 self.update(self.uuid) 

975 

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

977 xenapi = self.session.xenapi 

978 volumes_metadata = self._linstor.get_volumes_with_metadata() 

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

980 try: 

981 vdi_ref = xenapi.VDI.get_by_uuid(vdi_uuid) 

982 except Exception: 

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

984 continue 

985 

986 label = util.to_plain_string( 

987 xenapi.VDI.get_name_label(vdi_ref) 

988 ) 

989 description = util.to_plain_string( 

990 xenapi.VDI.get_name_description(vdi_ref) 

991 ) 

992 

993 if ( 

994 volume_metadata.get(NAME_LABEL_TAG) != label or 

995 volume_metadata.get(NAME_DESCRIPTION_TAG) != description 

996 ): 

997 self._linstor.update_volume_metadata(vdi_uuid, { 

998 NAME_LABEL_TAG: label, 

999 NAME_DESCRIPTION_TAG: description 

1000 }) 

1001 except Exception as e: 

1002 raise xs_errors.XenError( 

1003 'MetadataError', 

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

1005 ) 

1006 

1007 def _synchronize_metadata(self): 

1008 if not self.is_master(): 

1009 return 

1010 

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

1012 if self.cmd == 'sr_attach': 

1013 try: 

1014 util.SMlog( 

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

1016 ) 

1017 self._synchronize_metadata_and_xapi() 

1018 except Exception as e: 

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

1020 

1021 # -------------------------------------------------------------------------- 

1022 # Stats. 

1023 # -------------------------------------------------------------------------- 

1024 

1025 def _update_stats(self, virt_alloc_delta): 

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

1027 self.sr_ref 

1028 )) 

1029 

1030 # Update size attributes of the SR parent class. 

1031 self.virtual_allocation = valloc + virt_alloc_delta 

1032 

1033 self._update_physical_size() 

1034 

1035 # Notify SR parent class. 

1036 self._db_update() 

1037 

1038 def _update_physical_size(self): 

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

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

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

1042 self.physical_size = min_physical_size * pool_count // \ 

1043 self._linstor.redundancy 

1044 

1045 self.physical_utilisation = self._linstor.allocated_volume_size 

1046 

1047 # -------------------------------------------------------------------------- 

1048 # VDIs. 

1049 # -------------------------------------------------------------------------- 

1050 

1051 def _load_vdis(self): 

1052 if self._vdis_loaded: 

1053 return 

1054 

1055 assert self.is_master() 

1056 

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

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

1059 # enjoy it with a few lines. 

1060 self._create_linstor_cache() 

1061 self._load_vdis_ex() 

1062 self._destroy_linstor_cache() 

1063 

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

1065 self._vdis_loaded = True 

1066 

1067 self._undo_all_journal_transactions() 

1068 

1069 def _load_vdis_ex(self): 

1070 # 1. Get existing VDIs in XAPI. 

1071 xenapi = self.session.xenapi 

1072 xapi_vdi_uuids = set() 

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

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

1075 

1076 # 2. Get volumes info. 

1077 all_volume_info = self._all_volume_info_cache 

1078 volumes_metadata = self._all_volume_metadata_cache 

1079 

1080 # 3. Get CBT vdis. 

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

1082 cbt_vdis = set() 

1083 for volume_metadata in volumes_metadata.values(): 

1084 cbt_uuid = volume_metadata.get(CBTLOG_TAG) 

1085 if cbt_uuid: 

1086 cbt_vdis.add(cbt_uuid) 

1087 

1088 introduce = False 

1089 

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

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

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

1093 LinstorJournaler.CLONE 

1094 ).items()) 

1095 

1096 if has_clone_entries: 

1097 util.SMlog( 

1098 'Cannot introduce VDIs during scan because it exists ' 

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

1100 ) 

1101 else: 

1102 introduce = True 

1103 

1104 # 4. Now process all volume info. 

1105 vdi_to_snaps = {} 

1106 vdi_uuids = [] 

1107 

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

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

1110 continue 

1111 

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

1113 if vdi_uuid not in xapi_vdi_uuids: 

1114 if not introduce: 

1115 continue 

1116 

1117 if vdi_uuid.startswith('DELETED_'): 

1118 continue 

1119 

1120 volume_metadata = volumes_metadata.get(vdi_uuid) 

1121 if not volume_metadata: 

1122 util.SMlog( 

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

1124 .format(vdi_uuid) 

1125 ) 

1126 continue 

1127 

1128 util.SMlog( 

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

1130 'LINSTOR and not in XAPI...' 

1131 .format(vdi_uuid) 

1132 ) 

1133 

1134 try: 

1135 self._linstor.get_device_path(vdi_uuid) 

1136 except Exception as e: 

1137 util.SMlog( 

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

1139 .format(vdi_uuid, e) 

1140 ) 

1141 continue 

1142 

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

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

1145 vdi_type = volume_metadata.get(VDI_TYPE_TAG) 

1146 

1147 if not vdi_type: 

1148 util.SMlog( 

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

1150 'without vdi_type' 

1151 ) 

1152 continue 

1153 

1154 sm_config = { 

1155 'vdi_type': vdi_type 

1156 } 

1157 

1158 if not VdiType.isCowImage(vdi_type): 

1159 managed = not volume_metadata.get(HIDDEN_TAG) 

1160 else: 

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

1162 managed = not image_info.hidden 

1163 if image_info.parentUuid: 

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

1165 

1166 util.SMlog( 

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

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

1169 name_label, 

1170 volume_info.virtual_size, 

1171 volume_info.allocated_size 

1172 ) 

1173 ) 

1174 

1175 vdi_ref = xenapi.VDI.db_introduce( 

1176 vdi_uuid, 

1177 name_label, 

1178 volume_metadata.get(NAME_DESCRIPTION_TAG) or '', 

1179 self.sr_ref, 

1180 type, 

1181 False, # sharable 

1182 bool(volume_metadata.get(READ_ONLY_TAG)), 

1183 {}, # other_config 

1184 vdi_uuid, # location 

1185 {}, # xenstore_data 

1186 sm_config, 

1187 managed, 

1188 str(volume_info.virtual_size), 

1189 str(volume_info.allocated_size) 

1190 ) 

1191 

1192 is_a_snapshot = volume_metadata.get(IS_A_SNAPSHOT_TAG) 

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

1194 if is_a_snapshot: 

1195 xenapi.VDI.set_snapshot_time( 

1196 vdi_ref, 

1197 xmlrpc.client.DateTime( 

1198 volume_metadata[SNAPSHOT_TIME_TAG] or 

1199 '19700101T00:00:00Z' 

1200 ) 

1201 ) 

1202 

1203 snap_uuid = volume_metadata[SNAPSHOT_OF_TAG] 

1204 if snap_uuid in vdi_to_snaps: 

1205 vdi_to_snaps[snap_uuid].append(vdi_uuid) 

1206 else: 

1207 vdi_to_snaps[snap_uuid] = [vdi_uuid] 

1208 

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

1210 vdi_uuids.append(vdi_uuid) 

1211 

1212 # 5. Create VDIs. 

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

1214 

1215 def load_vdi(vdi_uuid, multi_cowutil): 

1216 vdi = self.vdi(vdi_uuid) 

1217 

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

1219 cowutil_instance = multi_cowutil.get_local_cowutil(vdi.vdi_type) 

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

1221 

1222 return vdi 

1223 

1224 try: 

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

1226 finally: 

1227 multi_cowutil = self._multi_cowutil 

1228 self._multi_cowutil = None 

1229 del multi_cowutil 

1230 

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

1232 # or already in XAPI. 

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

1234 volume_metadata = volumes_metadata.get(vdi.uuid) 

1235 cbt_uuid = volume_metadata.get(CBTLOG_TAG) 

1236 if cbt_uuid in cbt_vdis: 

1237 vdi_ref = xenapi.VDI.get_by_uuid(vdi_uuid) 

1238 xenapi.VDI.set_cbt_enabled(vdi_ref, True) 

1239 # For existing VDIs, update local state too. 

1240 # Scan in base class SR updates existing VDIs 

1241 # again based on local states. 

1242 self.vdis[vdi_uuid].cbt_enabled = True 

1243 cbt_vdis.remove(cbt_uuid) 

1244 

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

1246 for src_uuid in vdi_to_snaps: 

1247 try: 

1248 src_ref = xenapi.VDI.get_by_uuid(src_uuid) 

1249 except Exception: 

1250 # The source VDI no longer exists, continue. 

1251 continue 

1252 

1253 for snap_uuid in vdi_to_snaps[src_uuid]: 

1254 try: 

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

1256 snap_ref = xenapi.VDI.get_by_uuid(snap_uuid) 

1257 xenapi.VDI.set_snapshot_of(snap_ref, src_ref) 

1258 except Exception as e: 

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

1260 

1261 # TODO: Check correctly how to use CBT. 

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

1263 

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

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

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

1267 for cbt_uuid in cbt_vdis: 

1268 new_vdi = self.vdi(cbt_uuid) 

1269 new_vdi.ty = 'cbt_metadata' 

1270 new_vdi.cbt_enabled = True 

1271 self.vdis[cbt_uuid] = new_vdi 

1272 

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

1274 self.virtual_allocation = 0 

1275 

1276 # 10. Build geneology. 

1277 geneology = {} 

1278 

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

1280 if vdi.parent: 

1281 if vdi.parent in self.vdis: 

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

1283 if vdi.parent in geneology: 

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

1285 else: 

1286 geneology[vdi.parent] = [vdi_uuid] 

1287 if not vdi.hidden: 

1288 self.virtual_allocation += vdi.size 

1289 

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

1291 # will be GC'ed. 

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

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

1294 util.SMlog( 

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

1296 ) 

1297 del self.vdis[vdi_uuid] 

1298 

1299 # -------------------------------------------------------------------------- 

1300 # Journals. 

1301 # -------------------------------------------------------------------------- 

1302 

1303 def _get_journaler(self): 

1304 if not self._journaler: 

1305 self._journaler = LinstorJournaler( 

1306 self._group_name, 

1307 native_client=self._linstor.native_client, 

1308 logger=util.SMlog 

1309 ) 

1310 return self._journaler 

1311 

1312 def _get_vdi_path_and_parent(self, vdi_uuid, volume_name): 

1313 try: 

1314 device_path = self._linstor.build_device_path(volume_name) 

1315 if not util.pathexists(device_path): 

1316 return (None, None) 

1317 

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

1319 volume_metadata = self._linstor.get_volume_metadata(vdi_uuid) 

1320 vdi_type = volume_metadata[VDI_TYPE_TAG] 

1321 if not VdiType.isCowImage(vdi_type): 

1322 return (device_path, None) 

1323 

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

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

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

1327 return (None, None) 

1328 

1329 image_info = linstorcowutil.get_info(vdi_uuid) 

1330 if image_info: 

1331 return (device_path, image_info.parentUuid) 

1332 except Exception as e: 

1333 util.SMlog( 

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

1335 .format(e) 

1336 ) 

1337 return (None, None) 

1338 

1339 def _undo_all_journal_transactions(self): 

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

1341 self.lock.acquire() 

1342 try: 

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

1344 self._journaler = None 

1345 journaler = self._get_journaler() 

1346 self._handle_interrupted_inflate_ops(journaler) 

1347 self._handle_interrupted_clone_ops(journaler) 

1348 finally: 

1349 self.lock.release() 

1350 

1351 def _handle_interrupted_inflate_ops(self, journaler): 

1352 transactions = journaler.get_all(LinstorJournaler.INFLATE) 

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

1354 self._handle_interrupted_inflate(vdi_uuid, old_size) 

1355 journaler.remove(LinstorJournaler.INFLATE, vdi_uuid) 

1356 

1357 def _handle_interrupted_clone_ops(self, journaler): 

1358 transactions = journaler.get_all(LinstorJournaler.CLONE) 

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

1360 self._handle_interrupted_clone(vdi_uuid, old_size) 

1361 journaler.remove(LinstorJournaler.CLONE, vdi_uuid) 

1362 

1363 def _handle_interrupted_inflate(self, vdi_uuid, old_size): 

1364 util.SMlog( 

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

1366 .format(vdi_uuid, old_size) 

1367 ) 

1368 

1369 vdi = self.vdis.get(vdi_uuid) 

1370 if not vdi: 

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

1372 return 

1373 

1374 assert not self._all_volume_info_cache 

1375 volume_info = self._linstor.get_volume_info(vdi_uuid) 

1376 

1377 current_size = volume_info.virtual_size 

1378 assert current_size > 0 

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

1380 

1381 def _handle_interrupted_clone( 

1382 self, vdi_uuid, clone_info, force_undo=False 

1383 ): 

1384 util.SMlog( 

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

1386 .format(vdi_uuid, clone_info) 

1387 ) 

1388 

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

1390 

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

1392 volume_names = self._linstor.get_volumes_with_name() 

1393 

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

1395 if base_uuid not in volume_names: 

1396 if vdi_uuid in volume_names: 

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

1398 return 

1399 raise util.SMException( 

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

1401 .format(base_uuid, vdi_uuid) 

1402 ) 

1403 

1404 if force_undo: 

1405 util.SMlog('Explicit revert') 

1406 self._undo_clone( 

1407 volume_names, vdi_uuid, base_uuid, snap_uuid 

1408 ) 

1409 return 

1410 

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

1412 if vdi_uuid not in volume_names or \ 

1413 (snap_uuid and snap_uuid not in volume_names): 

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

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

1416 return 

1417 

1418 vdi_path, vdi_parent_uuid = self._get_vdi_path_and_parent( 

1419 vdi_uuid, volume_names[vdi_uuid] 

1420 ) 

1421 snap_path, snap_parent_uuid = self._get_vdi_path_and_parent( 

1422 snap_uuid, volume_names[snap_uuid] 

1423 ) 

1424 

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

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

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

1428 return 

1429 

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

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

1432 

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

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

1435 base_metadata = self._linstor.get_volume_metadata(base_uuid) 

1436 base_type = base_metadata[VDI_TYPE_TAG] 

1437 

1438 if not util.pathexists(base_path): 

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

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

1441 return 

1442 

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

1444 

1445 # Un-hide the parent. 

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

1447 if VdiType.isCowImage(base_type): 

1448 image_info = linstorcowutil.get_info(base_uuid, False) 

1449 if image_info.hidden: 

1450 linstorcowutil.set_hidden(base_path, False) 

1451 elif base_metadata.get(HIDDEN_TAG): 

1452 self._linstor.update_volume_metadata( 

1453 base_uuid, {HIDDEN_TAG: False} 

1454 ) 

1455 

1456 # Remove the child nodes. 

1457 if snap_uuid and snap_uuid in volume_names: 

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

1459 

1460 try: 

1461 self._linstor.destroy_volume(snap_uuid) 

1462 except Exception as e: 

1463 util.SMlog( 

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

1465 .format(snap_uuid, e) 

1466 ) 

1467 

1468 if vdi_uuid in volume_names: 

1469 try: 

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

1471 self._linstor.destroy_volume(vdi_uuid) 

1472 except Exception as e: 

1473 util.SMlog( 

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

1475 .format(vdi_uuid, e) 

1476 ) 

1477 # We can get an exception like this: 

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

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

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

1481 # UUID of this bad VDI before. 

1482 self._linstor.update_volume_uuid( 

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

1484 ) 

1485 

1486 # Rename! 

1487 self._linstor.update_volume_uuid(base_uuid, vdi_uuid) 

1488 

1489 # Inflate to the right size. 

1490 if VdiType.isCowImage(base_type): 

1491 vdi = self.vdi(vdi_uuid) 

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

1493 volume_size = linstorcowutil.compute_volume_size(vdi.size) 

1494 linstorcowutil.inflate( 

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

1496 volume_size, vdi.capacity 

1497 ) 

1498 self.vdis[vdi_uuid] = vdi 

1499 

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

1501 # flag to facilitate vm deactivate. 

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

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

1504 

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

1506 

1507 # -------------------------------------------------------------------------- 

1508 # Cache. 

1509 # -------------------------------------------------------------------------- 

1510 

1511 def _create_linstor_cache(self): 

1512 reconnect = False 

1513 

1514 def create_cache(): 

1515 nonlocal reconnect 

1516 try: 

1517 if reconnect: 

1518 self._reconnect() 

1519 return self._linstor.get_volumes_with_info() 

1520 except Exception as e: 

1521 reconnect = True 

1522 raise e 

1523 

1524 self._all_volume_metadata_cache = \ 

1525 self._linstor.get_volumes_with_metadata() 

1526 self._all_volume_info_cache = util.retry( 

1527 create_cache, 

1528 maxretry=10, 

1529 period=3 

1530 ) 

1531 

1532 def _destroy_linstor_cache(self): 

1533 self._all_volume_info_cache = None 

1534 self._all_volume_metadata_cache = None 

1535 

1536 # -------------------------------------------------------------------------- 

1537 # Misc. 

1538 # -------------------------------------------------------------------------- 

1539 

1540 def _reconnect(self): 

1541 controller_uri = get_controller_uri() 

1542 

1543 # Try to open SR if exists. 

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

1545 # we are trying to execute an exclusive operation. 

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

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

1548 # the SR is locked. 

1549 self._linstor = LinstorVolumeManager( 

1550 controller_uri, 

1551 self._group_name, 

1552 repair=( 

1553 self.is_master() and 

1554 self.srcmd.cmd in self.ops_exclusive 

1555 ), 

1556 logger=util.SMlog 

1557 ) 

1558 

1559 def _ensure_space_available(self, amount_needed): 

1560 space_available = self._linstor.max_volume_size_allowed 

1561 if (space_available < amount_needed): 

1562 util.SMlog( 

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

1564 space_available, amount_needed 

1565 ) 

1566 ) 

1567 raise xs_errors.XenError('SRNoSpace') 

1568 

1569 def _kick_gc(self): 

1570 util.SMlog('Kicking GC') 

1571 cleanup.start_gc_service(self.uuid) 

1572 

1573# ============================================================================== 

1574# LinstorSr VDI 

1575# ============================================================================== 

1576 

1577 

1578class LinstorVDI(VDI.VDI): 

1579 # -------------------------------------------------------------------------- 

1580 # VDI methods. 

1581 # -------------------------------------------------------------------------- 

1582 

1583 @override 

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

1585 self._lock = self.sr.lock 

1586 self._exists = True 

1587 self._linstor = self.sr._linstor 

1588 

1589 # Update hidden parent property. 

1590 self.hidden = False 

1591 

1592 def raise_bad_load(e): 

1593 util.SMlog( 

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

1595 ) 

1596 util.SMlog(traceback.format_exc()) 

1597 raise xs_errors.XenError( 

1598 'VDIUnavailable', 

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

1600 ) 

1601 

1602 # Try to load VDI. 

1603 try: 

1604 if ( 

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

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

1607 ): 

1608 self._set_type(VdiType.RAW) 

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

1610 else: 

1611 self._determine_type_and_path() 

1612 self._load_this() 

1613 

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

1615 self.uuid, self.path, self.hidden 

1616 )) 

1617 except LinstorVolumeManagerError as e: 

1618 # 1. It may be a VDI deletion. 

1619 if e.code == LinstorVolumeManagerError.ERR_VOLUME_NOT_EXISTS: 

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

1621 self.deleted = True 

1622 return 

1623 

1624 # 2. Or maybe a creation. 

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

1626 image_format = None 

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

1628 

1629 self._exists = False 

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

1631 if vdi_sm_config: 

1632 image_format = self.sr.read_config_image_format(vdi_sm_config) 

1633 

1634 if not image_format: 

1635 image_format = self.sr.preferred_image_formats[0] 

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

1637 

1638 if VdiType.isCowImage(self.vdi_type): 

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

1640 

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

1642 self._update_device_name(None) 

1643 return 

1644 raise_bad_load(e) 

1645 except Exception as e: 

1646 raise_bad_load(e) 

1647 

1648 @override 

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

1650 # Usage example: 

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

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

1653 

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

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

1656 if self._exists: 

1657 raise xs_errors.XenError('VDIExists') 

1658 

1659 assert self.uuid 

1660 assert self.ty 

1661 assert self.vdi_type 

1662 

1663 # 2. Compute size and check space available. 

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

1665 volume_size = self.linstorcowutil.compute_volume_size(size) 

1666 util.SMlog( 

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

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

1669 ) 

1670 self.sr._ensure_space_available(volume_size) 

1671 

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

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

1674 

1675 # 4. Create! 

1676 failed = False 

1677 try: 

1678 volume_name = None 

1679 if self.ty == 'ha_statefile': 

1680 volume_name = HA_VOLUME_NAME 

1681 elif self.ty == 'redo_log': 

1682 volume_name = REDO_LOG_VOLUME_NAME 

1683 

1684 self._linstor.create_volume( 

1685 self.uuid, 

1686 volume_size, 

1687 persistent=False, 

1688 volume_name=volume_name, 

1689 high_availability=volume_name is not None 

1690 ) 

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

1692 

1693 self._update_device_name(volume_info.name) 

1694 

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

1696 self.size = volume_info.virtual_size 

1697 else: 

1698 self.linstorcowutil.create( 

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

1700 ) 

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

1702 

1703 if self._key_hash: 

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

1705 

1706 # Because cowutil commands modify the volume data, 

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

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

1709 

1710 volume_metadata = { 

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

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

1713 IS_A_SNAPSHOT_TAG: False, 

1714 SNAPSHOT_OF_TAG: '', 

1715 SNAPSHOT_TIME_TAG: '', 

1716 TYPE_TAG: self.ty, 

1717 VDI_TYPE_TAG: self.vdi_type, 

1718 READ_ONLY_TAG: bool(self.read_only), 

1719 METADATA_OF_POOL_TAG: '' 

1720 } 

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

1722 

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

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

1725 # an already opened volume. 

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

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

1728 

1729 self._linstor.mark_volume_as_persistent(self.uuid) 

1730 except util.CommandException as e: 

1731 failed = True 

1732 raise xs_errors.XenError( 

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

1734 ) 

1735 except Exception as e: 

1736 failed = True 

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

1738 finally: 

1739 if failed: 

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

1741 try: 

1742 self._linstor.destroy_volume(self.uuid) 

1743 except Exception as e: 

1744 util.SMlog( 

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

1746 '{}'.format(e) 

1747 ) 

1748 

1749 self.utilisation = volume_info.allocated_size 

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

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

1752 

1753 self.ref = self._db_introduce() 

1754 self.sr._update_stats(self.size) 

1755 

1756 return VDI.VDI.get_params(self) 

1757 

1758 @override 

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

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

1761 if self.attached: 

1762 raise xs_errors.XenError('VDIInUse') 

1763 

1764 if self.deleted: 

1765 return super(LinstorVDI, self).delete( 

1766 sr_uuid, vdi_uuid, data_only 

1767 ) 

1768 

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

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

1771 raise xs_errors.XenError( 

1772 'VDIDelete', 

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

1774 ) 

1775 

1776 try: 

1777 # Remove from XAPI and delete from LINSTOR. 

1778 self._linstor.destroy_volume(self.uuid) 

1779 if not data_only: 

1780 self._db_forget() 

1781 

1782 self.sr.lock.cleanupAll(vdi_uuid) 

1783 except Exception as e: 

1784 util.SMlog( 

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

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

1787 ) 

1788 

1789 try: 

1790 raise e 

1791 except LinstorVolumeManagerError as e: 

1792 if e.code != LinstorVolumeManagerError.ERR_VOLUME_DESTROY: 

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

1794 

1795 return 

1796 

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

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

1799 

1800 # TODO: Check size after delete. 

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

1802 self.sr._kick_gc() 

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

1804 

1805 @override 

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

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

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

1809 if ( 

1810 not attach_from_config or 

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

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

1813 raise xs_errors.XenError( 

1814 'VDIUnavailable', 

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

1816 'scan SR first to trigger auto-repair' 

1817 ) 

1818 

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

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

1821 

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

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

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

1825 # than the COW size + bitmap size. 

1826 need_inflate = True 

1827 if ( 

1828 not VdiType.isCowImage(self.vdi_type) or 

1829 not writable or 

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

1831 ): 

1832 need_inflate = False 

1833 

1834 if need_inflate: 

1835 try: 

1836 self._prepare_thin(True) 

1837 except Exception as e: 

1838 raise xs_errors.XenError( 

1839 'VDIUnavailable', 

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

1841 .format(e) 

1842 ) 

1843 

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

1845 self.xenstore_data = {} 

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

1847 

1848 if ( 

1849 USE_HTTP_NBD_SERVERS and 

1850 attach_from_config and 

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

1852 ): 

1853 return self._attach_using_http_nbd() 

1854 

1855 # Ensure we have a path... 

1856 chain = self.linstorcowutil.create_chain_paths( 

1857 self.uuid, 

1858 readonly=not writable, 

1859 cb_openers=cleanup.LinstorSR.abort_gc_from_openers_vdi 

1860 ) 

1861 chain.close() 

1862 

1863 self.attached = True 

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

1865 

1866 @override 

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

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

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

1870 self.attached = False 

1871 

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

1873 return self._detach_using_http_nbd() 

1874 

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

1876 return 

1877 

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

1879 # equal to the LINSTOR volume size. 

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

1881 already_deflated = self.capacity <= volume_size 

1882 

1883 if already_deflated: 

1884 util.SMlog( 

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

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

1887 ) 

1888 

1889 need_deflate = True 

1890 if already_deflated: 

1891 need_deflate = False 

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

1893 need_deflate = False 

1894 

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

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

1897 need_deflate = True 

1898 

1899 if need_deflate: 

1900 try: 

1901 self._prepare_thin(False) 

1902 except Exception as e: 

1903 raise xs_errors.XenError( 

1904 'VDIUnavailable', 

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

1906 .format(e) 

1907 ) 

1908 

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

1910 if self.sr.is_master(): 

1911 return 

1912 

1913 while vdi_uuid: 

1914 try: 

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

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

1917 except Exception: 

1918 break 

1919 

1920 if util.pathexists(path): 

1921 try: 

1922 self._linstor.remove_volume_if_diskless(vdi_uuid) 

1923 except Exception as e: 

1924 # Ensure we can always detach properly. 

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

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

1927 vdi_uuid = parent_vdi_uuid 

1928 

1929 @override 

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

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

1932 if not self.sr.is_master(): 

1933 raise xs_errors.XenError( 

1934 'VDISize', 

1935 opterr='resize on slave not allowed' 

1936 ) 

1937 

1938 if self.hidden: 

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

1940 

1941 # Compute the virtual COW and DRBD volume size. 

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

1943 volume_size = self.linstorcowutil.compute_volume_size(size) 

1944 util.SMlog( 

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

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

1947 ) 

1948 

1949 if size < self.size: 

1950 util.SMlog( 

1951 'vdi_resize: shrinking not supported: ' 

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

1953 ) 

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

1955 

1956 if size == self.size: 

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

1958 

1959 # Compute VDI sizes 

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

1961 old_volume_size = self.size 

1962 new_volume_size = LinstorVolumeManager.round_up_volume_size(size) 

1963 else: 

1964 old_volume_size = self.utilisation 

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

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

1967 new_volume_size = old_volume_size 

1968 else: 

1969 new_volume_size = self.linstorcowutil.compute_volume_size(size) 

1970 assert new_volume_size >= old_volume_size 

1971 

1972 space_needed = new_volume_size - old_volume_size 

1973 self.sr._ensure_space_available(space_needed) 

1974 

1975 old_size = self.size 

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

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

1978 else: 

1979 if new_volume_size != old_volume_size: 

1980 self.linstorcowutil.inflate( 

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

1982 new_volume_size, old_volume_size 

1983 ) 

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

1985 

1986 # Reload size attributes. 

1987 self._load_this() 

1988 

1989 # Update metadata 

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

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

1992 self.session.xenapi.VDI.set_physical_utilisation( 

1993 vdi_ref, str(self.utilisation) 

1994 ) 

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

1996 return VDI.VDI.get_params(self) 

1997 

1998 @override 

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

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

2001 

2002 @override 

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

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

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

2006 raise xs_errors.XenError('Unimplemented') 

2007 

2008 parent_uuid = vdi1 

2009 parent_path = self._linstor.get_device_path(parent_uuid) 

2010 

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

2012 # have a readonly error. 

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

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

2015 

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

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

2018 try: 

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

2020 self.linstorcowutil.set_hidden(parent_path) 

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

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

2023 ) 

2024 finally: 

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

2026 

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

2028 raise util.SMException( 

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

2030 ) 

2031 

2032 util.SMlog('Compose done') 

2033 

2034 @override 

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

2036 """ 

2037 Generate the XML config required to attach and activate 

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

2039 activation is handled by vdi_attach_from_config below. 

2040 """ 

2041 

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

2043 

2044 resp = {} 

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

2046 resp['sr_uuid'] = sr_uuid 

2047 resp['vdi_uuid'] = self.uuid 

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

2049 resp['command'] = 'vdi_attach_from_config' 

2050 

2051 # By default, we generate a normal config. 

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

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

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

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

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

2057 # instead. 

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

2059 if not USE_HTTP_NBD_SERVERS or volume_name not in [ 

2060 HA_VOLUME_NAME, REDO_LOG_VOLUME_NAME 

2061 ]: 

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

2063 available = False 

2064 # Try to refresh symlink path... 

2065 try: 

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

2067 available = util.pathexists(self.path) 

2068 except Exception: 

2069 pass 

2070 if not available: 

2071 raise xs_errors.XenError('VDIUnavailable') 

2072 

2073 resp['vdi_path'] = self.path 

2074 else: 

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

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

2077 

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

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

2080 

2081 @override 

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

2083 """ 

2084 Attach and activate a VDI using config generated by 

2085 vdi_generate_config above. This is used for cases such as 

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

2087 """ 

2088 

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

2090 

2091 try: 

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

2093 self.sr.attach(sr_uuid) 

2094 

2095 if not DRIVER_CONFIG['ATTACH_FROM_CONFIG_WITH_TAPDISK']: 

2096 return self.attach(sr_uuid, vdi_uuid) 

2097 except Exception: 

2098 util.logException('LinstorVDI.attach_from_config') 

2099 raise xs_errors.XenError( 

2100 'SRUnavailable', 

2101 opterr='Unable to attach from config' 

2102 ) 

2103 return '' 

2104 

2105 def reset_leaf(self, sr_uuid, vdi_uuid): 

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

2107 raise xs_errors.XenError('Unimplemented') 

2108 

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

2110 raise util.SMException( 

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

2112 .format(self.uuid) 

2113 ) 

2114 

2115 self.linstorcowutil.kill_data(self.path) 

2116 

2117 def _load_this(self): 

2118 volume_metadata = None 

2119 if self.sr._all_volume_metadata_cache: 

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

2121 assert volume_metadata 

2122 else: 

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

2124 

2125 volume_info = None 

2126 if self.sr._all_volume_info_cache: 

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

2128 assert volume_info 

2129 else: 

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

2131 

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

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

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

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

2136 # be lower than virtual size at creation. 

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

2138 self.utilisation = volume_info.allocated_size 

2139 self.capacity = volume_info.virtual_size 

2140 

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

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

2143 self.size = volume_info.virtual_size 

2144 self.parent = '' 

2145 else: 

2146 if self.sr._multi_cowutil: 

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

2148 else: 

2149 cowutil_instance = self.linstorcowutil 

2150 

2151 image_info = cowutil_instance.get_info(self.uuid) 

2152 self.hidden = image_info.hidden 

2153 self.size = image_info.sizeVirt 

2154 self.parent = image_info.parentUuid 

2155 

2156 if self.hidden: 

2157 self.managed = False 

2158 

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

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

2161 

2162 # Update sm_config_override of VDI parent class. 

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

2164 

2165 def _mark_hidden(self, hidden=True): 

2166 if self.hidden == hidden: 

2167 return 

2168 

2169 if VdiType.isCowImage(self.vdi_type): 

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

2171 else: 

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

2173 HIDDEN_TAG: hidden 

2174 }) 

2175 self.hidden = hidden 

2176 

2177 @override 

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

2179 xenapi = self.session.xenapi 

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

2181 

2182 volume_metadata = { 

2183 NAME_LABEL_TAG: util.to_plain_string( 

2184 xenapi.VDI.get_name_label(vdi_ref) 

2185 ), 

2186 NAME_DESCRIPTION_TAG: util.to_plain_string( 

2187 xenapi.VDI.get_name_description(vdi_ref) 

2188 ) 

2189 } 

2190 

2191 try: 

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

2193 except LinstorVolumeManagerError as e: 

2194 if e.code == LinstorVolumeManagerError.ERR_VOLUME_NOT_EXISTS: 

2195 raise xs_errors.XenError( 

2196 'VDIUnavailable', 

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

2198 ) 

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

2200 

2201 # -------------------------------------------------------------------------- 

2202 # Thin provisioning. 

2203 # -------------------------------------------------------------------------- 

2204 

2205 def _prepare_thin(self, attach): 

2206 if self.sr.is_master(): 

2207 if attach: 

2208 attach_thin( 

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

2210 self.sr.uuid, self.uuid 

2211 ) 

2212 else: 

2213 detach_thin( 

2214 self.session, self._linstor, self.sr.uuid, self.uuid 

2215 ) 

2216 else: 

2217 fn = 'attach' if attach else 'detach' 

2218 

2219 master = util.get_master_ref(self.session) 

2220 

2221 args = { 

2222 'groupName': self.sr._group_name, 

2223 'srUuid': self.sr.uuid, 

2224 'vdiUuid': self.uuid 

2225 } 

2226 

2227 try: 

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

2229 except Exception: 

2230 if fn != 'detach': 

2231 raise 

2232 

2233 # Reload size attrs after inflate or deflate! 

2234 self._load_this() 

2235 self.sr._update_physical_size() 

2236 

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

2238 self.session.xenapi.VDI.set_physical_utilisation( 

2239 vdi_ref, str(self.utilisation) 

2240 ) 

2241 

2242 self.session.xenapi.SR.set_physical_utilisation( 

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

2244 ) 

2245 

2246 # -------------------------------------------------------------------------- 

2247 # Generic helpers. 

2248 # -------------------------------------------------------------------------- 

2249 

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

2251 self.vdi_type = vdi_type 

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

2253 

2254 def _determine_type_and_path(self): 

2255 """ 

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

2257 """ 

2258 

2259 if self.sr._all_volume_metadata_cache: 

2260 # We are currently loading all volumes. 

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

2262 if not volume_metadata: 

2263 raise xs_errors.XenError( 

2264 'VDIUnavailable', 

2265 opterr='failed to get metadata' 

2266 ) 

2267 else: 

2268 # Simple load. 

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

2270 

2271 # Set type and path. 

2272 vdi_type = volume_metadata.get(VDI_TYPE_TAG) 

2273 if not vdi_type: 

2274 raise xs_errors.XenError( 

2275 'VDIUnavailable', 

2276 opterr='failed to get vdi_type in metadata' 

2277 ) 

2278 self._set_type(vdi_type) 

2279 

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

2281 

2282 def _update_device_name(self, device_name): 

2283 self._device_name = device_name 

2284 

2285 # Mark path of VDI parent class. 

2286 if device_name: 

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

2288 else: 

2289 self.path = None 

2290 

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

2292 """ 

2293 Snapshot self and return the snapshot VDI object. 

2294 """ 

2295 

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

2297 snap_path = self._linstor.shallow_clone_volume( 

2298 self.uuid, snap_uuid, persistent=False 

2299 ) 

2300 

2301 # 2. Write the snapshot content. 

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

2303 self.linstorcowutil.snapshot( 

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

2305 ) 

2306 

2307 # 3. Get snapshot parent. 

2308 snap_parent = self.linstorcowutil.get_parent(snap_uuid) 

2309 

2310 # 4. Update metadata. 

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

2312 volume_metadata = { 

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

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

2315 IS_A_SNAPSHOT_TAG: bool(snap_of_uuid), 

2316 SNAPSHOT_OF_TAG: snap_of_uuid, 

2317 SNAPSHOT_TIME_TAG: '', 

2318 TYPE_TAG: self.ty, 

2319 VDI_TYPE_TAG: snap_vdi_type, 

2320 READ_ONLY_TAG: False, 

2321 METADATA_OF_POOL_TAG: '' 

2322 } 

2323 self._linstor.set_volume_metadata(snap_uuid, volume_metadata) 

2324 

2325 # 5. Set size. 

2326 snap_vdi = LinstorVDI(self.sr, snap_uuid) 

2327 if not snap_vdi._exists: 

2328 raise xs_errors.XenError('VDISnapshot') 

2329 

2330 volume_info = self._linstor.get_volume_info(snap_uuid) 

2331 

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

2333 snap_vdi.utilisation = volume_info.allocated_size 

2334 

2335 # 6. Update sm config. 

2336 snap_vdi.sm_config = {} 

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

2338 if snap_parent: 

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

2340 snap_vdi.parent = snap_parent 

2341 

2342 snap_vdi.label = self.label 

2343 snap_vdi.description = self.description 

2344 

2345 self._linstor.mark_volume_as_persistent(snap_uuid) 

2346 

2347 return snap_vdi 

2348 

2349 # -------------------------------------------------------------------------- 

2350 # Implement specific SR methods. 

2351 # -------------------------------------------------------------------------- 

2352 

2353 @override 

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

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

2356 volume_uuid = self._linstor.get_volume_uuid_from_device_path(oldpath) 

2357 self._linstor.update_volume_name(volume_uuid, newpath) 

2358 

2359 @override 

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

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

2362 # If cbt enabled, save file consistency state. 

2363 if cbtlog is not None: 

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

2365 consistency_state = False 

2366 else: 

2367 consistency_state = True 

2368 util.SMlog( 

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

2370 .format(consistency_state, vdi_uuid) 

2371 ) 

2372 else: 

2373 consistency_state = None 

2374 

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

2376 raise xs_errors.XenError('Unimplemented') 

2377 

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

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

2380 try: 

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

2382 finally: 

2383 self.disable_leaf_on_secondary(vdi_uuid, secondary=secondary) 

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

2385 

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

2387 util.SMlog( 

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

2389 .format(self.uuid, snap_type) 

2390 ) 

2391 

2392 # 1. Checks... 

2393 if self.hidden: 

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

2395 

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

2397 

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

2399 if depth == -1: 

2400 raise xs_errors.XenError( 

2401 'VDIUnavailable', 

2402 opterr='failed to get COW depth' 

2403 ) 

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

2405 raise xs_errors.XenError('SnapshotChainTooLong') 

2406 

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

2408 chain = self.linstorcowutil.create_chain_paths( 

2409 self.uuid, 

2410 readonly=True, 

2411 cb_openers=cleanup.LinstorSR.abort_gc_from_openers_vdi 

2412 ) 

2413 chain.close() 

2414 

2415 volume_path = self.path 

2416 if not util.pathexists(volume_path): 

2417 raise xs_errors.XenError( 

2418 'EIO', 

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

2420 ) 

2421 

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

2423 base_uuid = util.gen_uuid() 

2424 snap_uuid = None 

2425 

2426 if snap_type == VDI.SNAPSHOT_DOUBLE: 

2427 snap_uuid = util.gen_uuid() 

2428 

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

2430 

2431 active_uuid = self.uuid 

2432 

2433 journaler = self.sr._get_journaler() 

2434 journaler.create( 

2435 LinstorJournaler.CLONE, active_uuid, clone_info 

2436 ) 

2437 

2438 try: 

2439 # 3. Self becomes the new base. 

2440 # The device path remains the same. 

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

2442 self.uuid = base_uuid 

2443 self.location = self.uuid 

2444 self.read_only = True 

2445 self.managed = False 

2446 

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

2448 active_vdi = self._create_snapshot(snap_vdi_type, active_uuid) 

2449 

2450 snap_vdi = None 

2451 if snap_type == VDI.SNAPSHOT_DOUBLE: 

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

2453 

2454 self.label = 'base copy' 

2455 self.description = '' 

2456 

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

2458 # in subsequent scans. 

2459 self._mark_hidden() 

2460 self._linstor.update_volume_metadata( 

2461 self.uuid, {READ_ONLY_TAG: True} 

2462 ) 

2463 

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

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

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

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

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

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

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

2471 active_vdi.sm_config[key] = sm_config[key] 

2472 

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

2474 # delete base if unused. 

2475 introduce_parent = True 

2476 try: 

2477 snap_parent = None 

2478 if snap_vdi: 

2479 snap_parent = snap_vdi.parent 

2480 

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

2482 snap_type == VDI.SNAPSHOT_SINGLE or 

2483 snap_type == VDI.SNAPSHOT_INTERNAL or 

2484 snap_parent != self.uuid 

2485 ): 

2486 util.SMlog( 

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

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

2489 ) 

2490 introduce_parent = False 

2491 self._linstor.destroy_volume(self.uuid) 

2492 except Exception as e: 

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

2494 pass 

2495 

2496 # 8. Introduce the new VDI records. 

2497 if snap_vdi: 

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

2499 # new snapshot disk. 

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

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

2502 # TODO: Maybe remove key_hash support. 

2503 if 'key_hash' in sm_config: 

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

2505 # If we have CBT enabled on the VDI, 

2506 # set CBT status for the new snapshot disk. 

2507 if cbtlog: 

2508 snap_vdi.cbt_enabled = True 

2509 

2510 if snap_vdi: 

2511 snap_vdi_ref = snap_vdi._db_introduce() 

2512 util.SMlog( 

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

2514 .format(snap_vdi_ref, snap_vdi.uuid) 

2515 ) 

2516 if introduce_parent: 

2517 base_vdi_ref = self._db_introduce() 

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

2519 util.SMlog( 

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

2521 .format(base_vdi_ref, self.uuid) 

2522 ) 

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

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

2525 NAME_DESCRIPTION_TAG: util.to_plain_string( 

2526 self.description 

2527 ), 

2528 READ_ONLY_TAG: True, 

2529 METADATA_OF_POOL_TAG: '' 

2530 }) 

2531 

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

2533 if snap_type == VDI.SNAPSHOT_DOUBLE and cbtlog: 

2534 try: 

2535 self._cbt_snapshot(snap_uuid, cbt_consistency) 

2536 except Exception: 

2537 # CBT operation failed. 

2538 # TODO: Implement me. 

2539 raise 

2540 

2541 if snap_type != VDI.SNAPSHOT_INTERNAL: 

2542 self.sr._update_stats(self.size) 

2543 

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

2545 ret_vdi = snap_vdi 

2546 if not ret_vdi: 

2547 ret_vdi = self 

2548 if not ret_vdi: 

2549 ret_vdi = active_vdi 

2550 

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

2552 self.session.xenapi.VDI.set_sm_config( 

2553 vdi_ref, active_vdi.sm_config 

2554 ) 

2555 except Exception as e: 

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

2557 try: 

2558 self.sr._handle_interrupted_clone( 

2559 active_uuid, clone_info, force_undo=True 

2560 ) 

2561 journaler.remove(LinstorJournaler.CLONE, active_uuid) 

2562 except Exception as clean_error: 

2563 util.SMlog( 

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

2565 .format(clean_error) 

2566 ) 

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

2568 

2569 journaler.remove(LinstorJournaler.CLONE, active_uuid) 

2570 

2571 return ret_vdi.get_params() 

2572 

2573 @staticmethod 

2574 def _start_persistent_http_server(volume_name): 

2575 pid_path = None 

2576 http_server = None 

2577 

2578 try: 

2579 if volume_name == HA_VOLUME_NAME: 

2580 port = '8076' 

2581 else: 

2582 port = '8077' 

2583 

2584 try: 

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

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

2587 # block indefinitely. 

2588 session = util.timeout(5, util.get_localAPI_session) 

2589 host_ip = util.get_this_host_address(session) 

2590 except: 

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

2592 host_ip, _ = get_ips_from_xha_config_file() 

2593 if not host_ip: 

2594 raise Exception( 

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

2596 ) 

2597 

2598 arguments = [ 

2599 'http-disk-server', 

2600 '--disk', 

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

2602 '--ip', 

2603 host_ip, 

2604 '--port', 

2605 port 

2606 ] 

2607 

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

2609 http_server = subprocess.Popen( 

2610 [FORK_LOG_DAEMON] + arguments, 

2611 stdout=subprocess.PIPE, 

2612 stderr=subprocess.STDOUT, 

2613 universal_newlines=True, 

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

2615 # touch the current one. 

2616 preexec_fn=os.setsid 

2617 ) 

2618 

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

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

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

2622 

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

2624 def is_ready(): 

2625 while http_server.poll() is None: 

2626 line = http_server.stdout.readline() 

2627 if reg_server_ready.search(line): 

2628 return True 

2629 return False 

2630 try: 

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

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

2633 except util.TimeoutException: 

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

2635 except Exception as e: 

2636 if pid_path: 

2637 try: 

2638 os.remove(pid_path) 

2639 except Exception: 

2640 pass 

2641 

2642 if http_server: 

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

2644 try: 

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

2646 except: 

2647 pass 

2648 

2649 raise xs_errors.XenError( 

2650 'VDIUnavailable', 

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

2652 ) 

2653 

2654 def _start_persistent_nbd_server(self, volume_name): 

2655 pid_path = None 

2656 nbd_path = None 

2657 nbd_server = None 

2658 

2659 try: 

2660 # We use a precomputed device size. 

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

2662 if volume_name == HA_VOLUME_NAME: 

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

2664 port = '8076' 

2665 device_size = 4 * 1024 * 1024 

2666 else: 

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

2668 port = '8077' 

2669 device_size = 256 * 1024 * 1024 

2670 

2671 try: 

2672 session = util.timeout(5, util.get_localAPI_session) 

2673 ips = util.get_host_addresses(session) 

2674 except Exception as e: 

2675 _, ips = get_ips_from_xha_config_file() 

2676 if not ips: 

2677 raise Exception( 

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

2679 ) 

2680 ips = ips.values() 

2681 

2682 arguments = [ 

2683 'nbd-http-server', 

2684 '--socket-path', 

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

2686 '--nbd-name', 

2687 volume_name, 

2688 '--urls', 

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

2690 '--device-size', 

2691 str(device_size) 

2692 ] 

2693 

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

2695 nbd_server = subprocess.Popen( 

2696 [FORK_LOG_DAEMON] + arguments, 

2697 stdout=subprocess.PIPE, 

2698 stderr=subprocess.STDOUT, 

2699 universal_newlines=True, 

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

2701 # touch the current one. 

2702 preexec_fn=os.setsid 

2703 ) 

2704 

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

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

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

2708 

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

2710 def get_nbd_path(): 

2711 while nbd_server.poll() is None: 

2712 line = nbd_server.stdout.readline() 

2713 match = reg_nbd_path.search(line) 

2714 if match: 

2715 return match.group(1) 

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

2717 try: 

2718 nbd_path = util.timeout(10, get_nbd_path) 

2719 if nbd_path is None: 

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

2721 except util.TimeoutException: 

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

2723 

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

2725 os.symlink(nbd_path, self.path) 

2726 except Exception as e: 

2727 if pid_path: 

2728 try: 

2729 os.remove(pid_path) 

2730 except Exception: 

2731 pass 

2732 

2733 if nbd_path: 

2734 try: 

2735 os.remove(nbd_path) 

2736 except Exception: 

2737 pass 

2738 

2739 if nbd_server: 

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

2741 try: 

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

2743 except: 

2744 pass 

2745 

2746 raise xs_errors.XenError( 

2747 'VDIUnavailable', 

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

2749 ) 

2750 

2751 @classmethod 

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

2753 try: 

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

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

2756 return 

2757 

2758 pid = None 

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

2760 try: 

2761 pid = int(pid_file.read()) 

2762 except Exception: 

2763 pass 

2764 

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

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

2767 try: 

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

2769 except Exception as e: 

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

2771 

2772 os.remove(path) 

2773 except: 

2774 pass 

2775 

2776 @classmethod 

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

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

2779 

2780 @classmethod 

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

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

2783 

2784 def _check_http_nbd_volume_name(self): 

2785 volume_name = self.path[14:] 

2786 if volume_name not in [ 

2787 HA_VOLUME_NAME, REDO_LOG_VOLUME_NAME 

2788 ]: 

2789 raise xs_errors.XenError( 

2790 'VDIUnavailable', 

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

2792 ) 

2793 return volume_name 

2794 

2795 def _attach_using_http_nbd(self): 

2796 volume_name = self._check_http_nbd_volume_name() 

2797 

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

2799 self._kill_persistent_nbd_server(volume_name) 

2800 self._kill_persistent_http_server(volume_name) 

2801 

2802 # 0. Fetch drbd path. 

2803 must_get_device_path = True 

2804 if not self.sr.is_master(): 

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

2806 try: 

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

2808 except Exception as e: 

2809 raise xs_errors.XenError( 

2810 'VDIUnavailable', 

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

2812 .format(self.uuid, e) 

2813 ) 

2814 

2815 hostname = socket.gethostname() 

2816 must_get_device_path = hostname in volume_info.diskful 

2817 

2818 drbd_path = None 

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

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

2821 # or diskful available to init HA. 

2822 # It also avoid this error in xensource.log 

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

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

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

2826 available = False 

2827 try: 

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

2829 available = util.pathexists(drbd_path) 

2830 except Exception: 

2831 pass 

2832 

2833 if not available: 

2834 raise xs_errors.XenError( 

2835 'VDIUnavailable', 

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

2837 ) 

2838 

2839 # 1. Prepare http-nbd folder. 

2840 try: 

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

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

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

2844 os.remove(self.path) 

2845 except OSError as e: 

2846 if e.errno != errno.EEXIST: 

2847 raise xs_errors.XenError( 

2848 'VDIUnavailable', 

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

2850 ) 

2851 

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

2853 http_service = None 

2854 if drbd_path: 

2855 assert(drbd_path in ( 

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

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

2858 )) 

2859 self._start_persistent_http_server(volume_name) 

2860 

2861 # 3. Start NBD server in all cases. 

2862 try: 

2863 self._start_persistent_nbd_server(volume_name) 

2864 except Exception as e: 

2865 if drbd_path: 

2866 self._kill_persistent_http_server(volume_name) 

2867 raise 

2868 

2869 self.attached = True 

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

2871 

2872 def _detach_using_http_nbd(self): 

2873 volume_name = self._check_http_nbd_volume_name() 

2874 self._kill_persistent_nbd_server(volume_name) 

2875 self._kill_persistent_http_server(volume_name) 

2876 

2877# ------------------------------------------------------------------------------ 

2878 

2879 

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

2881 def run(): 

2882 SRCommand.run(LinstorSR, DRIVER_INFO) 

2883 

2884 if not TRACE_PERFS: 

2885 run() 

2886 else: 

2887 util.make_profile('LinstorSR', run) 

2888else: 

2889 SR.registerSR(LinstorSR)