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

61from srmetadata import \ 

62 NAME_LABEL_TAG, NAME_DESCRIPTION_TAG, IS_A_SNAPSHOT_TAG, SNAPSHOT_OF_TAG, \ 

63 TYPE_TAG, VDI_TYPE_TAG, READ_ONLY_TAG, SNAPSHOT_TIME_TAG, \ 

64 METADATA_OF_POOL_TAG 

65from vditype import VdiType 

66 

67HIDDEN_TAG = 'hidden' 

68 

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

70 

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

72 

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

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

75# specific conditions: 

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

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

78USE_HTTP_NBD_SERVERS = True 

79 

80# Useful flag to trace calls using cProfile. 

81TRACE_PERFS = False 

82 

83# Enable/Disable COW key hash support. 

84USE_KEY_HASH = False 

85 

86# Special volumes. 

87HA_VOLUME_NAME = PERSISTENT_PREFIX + 'ha-statefile' 

88REDO_LOG_VOLUME_NAME = PERSISTENT_PREFIX + 'redo-log' 

89 

90# TODO: Simplify with File SR and LVM SR 

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

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

93CREATE_PARAM_TYPES = { 

94 "raw": VdiType.RAW, 

95 "vhd": VdiType.VHD, 

96 "qcow2": VdiType.QCOW2 

97} 

98 

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

100 

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

102# 'VDI_CONFIG_CBT', 'SR_PROBE' 

103 

104CAPABILITIES = [ 

105 'ATOMIC_PAUSE', 

106 'SR_UPDATE', 

107 'VDI_CREATE', 

108 'VDI_DELETE', 

109 'VDI_UPDATE', 

110 'VDI_ATTACH', 

111 'VDI_DETACH', 

112 'VDI_ACTIVATE', 

113 'VDI_DEACTIVATE', 

114 'VDI_CLONE', 

115 'VDI_MIRROR', 

116 'VDI_RESIZE', 

117 'VDI_SNAPSHOT', 

118 'VDI_GENERATE_CONFIG' 

119] 

120 

121CONFIGURATION = [ 

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

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

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

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

126] 

127 

128DRIVER_INFO = { 

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

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

131 'vendor': 'Vates', 

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

133 'driver_version': '1.0', 

134 'required_api_version': '1.0', 

135 'capabilities': CAPABILITIES, 

136 'configuration': CONFIGURATION 

137} 

138 

139DRIVER_CONFIG = {'ATTACH_FROM_CONFIG_WITH_TAPDISK': False} 

140 

141OPS_EXCLUSIVE = [ 

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

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

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

145] 

146 

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

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

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

150 

151 

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

153 volume_metadata = linstor.get_volume_metadata(vdi_uuid) 

154 vdi_type = volume_metadata.get(VDI_TYPE_TAG) 

155 if not VdiType.isCowImage(vdi_type): 

156 return 

157 

158 device_path = linstor.get_device_path(vdi_uuid) 

159 

160 linstorcowutil = LinstorCowUtil(session, linstor, vdi_type) 

161 

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

163 # there is nothing to do. 

164 cow_size = linstorcowutil.compute_volume_size( 

165 linstorcowutil.get_size_virt(vdi_uuid) 

166 ) 

167 

168 volume_info = linstor.get_volume_info(vdi_uuid) 

169 volume_size = volume_info.virtual_size 

170 

171 if cow_size > volume_size: 

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

173 

174 

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

176 volume_metadata = linstor.get_volume_metadata(vdi_uuid) 

177 vdi_type = volume_metadata.get(VDI_TYPE_TAG) 

178 if not VdiType.isCowImage(vdi_type): 

179 return 

180 

181 def check_vbd_count(): 

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

183 vbds = session.xenapi.VBD.get_all_records_where( 

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

185 ) 

186 

187 num_plugged = 0 

188 for vbd_rec in vbds.values(): 

189 if vbd_rec['currently_attached']: 

190 num_plugged += 1 

191 if num_plugged > 1: 

192 raise xs_errors.XenError( 

193 'VDIUnavailable', 

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

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

196 ) 

197 

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

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

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

201 

202 device_path = linstor.get_device_path(vdi_uuid) 

203 linstorcowutil = LinstorCowUtil(session, linstor, vdi_type) 

204 new_volume_size = LinstorVolumeManager.round_up_volume_size( 

205 linstorcowutil.get_size_phys(vdi_uuid) 

206 ) 

207 

208 volume_info = linstor.get_volume_info(vdi_uuid) 

209 old_volume_size = volume_info.virtual_size 

210 linstorcowutil.deflate(device_path, new_volume_size, old_volume_size) 

211 

212 

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

214 # This function must always return without errors. 

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

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

217 try: 

218 detach_thin_impl(session, linstor, sr_uuid, vdi_uuid) 

219 except Exception as e: 

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

221 

222 

223def get_ips_from_xha_config_file(): 

224 ips = dict() 

225 host_id = None 

226 try: 

227 # Ensure there is no dirty read problem. 

228 # For example if the HA is reloaded. 

229 tree = util.retry( 

230 lambda: xml_parser.parse(XHA_CONFIG_PATH), 

231 maxretry=10, 

232 period=1 

233 ) 

234 except: 

235 return (None, ips) 

236 

237 def parse_host_nodes(ips, node): 

238 current_id = None 

239 current_ip = None 

240 

241 for sub_node in node: 

242 if sub_node.tag == 'IPaddress': 

243 current_ip = sub_node.text 

244 elif sub_node.tag == 'HostID': 

245 current_id = sub_node.text 

246 else: 

247 continue 

248 

249 if current_id and current_ip: 

250 ips[current_id] = current_ip 

251 return 

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

253 

254 def parse_common_config(ips, node): 

255 for sub_node in node: 

256 if sub_node.tag == 'host': 

257 parse_host_nodes(ips, sub_node) 

258 

259 def parse_local_config(ips, node): 

260 for sub_node in node: 

261 if sub_node.tag == 'localhost': 

262 for host_node in sub_node: 

263 if host_node.tag == 'HostID': 

264 return host_node.text 

265 

266 for node in tree.getroot(): 

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

268 parse_common_config(ips, node) 

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

270 host_id = parse_local_config(ips, node) 

271 else: 

272 continue 

273 

274 if ips and host_id: 

275 break 

276 

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

278 

279 

280def activate_lvm_group(group_name): 

281 path = group_name.split('/') 

282 assert path and len(path) <= 2 

283 try: 

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

285 except Exception as e: 

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

287 

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

289 

290# Usage example: 

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

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

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

294 

295 

296class LinstorSR(SR.SR): 

297 DRIVER_TYPE = 'linstor' 

298 

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

300 PROVISIONING_DEFAULT = 'thin' 

301 

302 MANAGER_PLUGIN = 'linstor-manager' 

303 

304 INIT_STATUS_NOT_SET = 0 

305 INIT_STATUS_IN_PROGRESS = 1 

306 INIT_STATUS_OK = 2 

307 INIT_STATUS_FAIL = 3 

308 

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

310 # SR methods. 

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

312 

313 _linstor: Optional["LinstorVolumeManager"] = None 

314 

315 @override 

316 @staticmethod 

317 def handles(type) -> bool: 

318 return type == LinstorSR.DRIVER_TYPE 

319 

320 def __init__(self, srcmd, sr_uuid): 

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

322 self._init_preferred_image_formats([ImageFormat.VHD]) 

323 

324 @override 

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

326 if not LINSTOR_AVAILABLE: 

327 raise util.SMException( 

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

329 ) 

330 

331 # Check parameters. 

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

333 raise xs_errors.XenError('LinstorConfigGroupNameMissing') 

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

335 raise xs_errors.XenError('LinstorConfigRedundancyMissing') 

336 

337 self.driver_config = DRIVER_CONFIG 

338 

339 # Check provisioning config. 

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

341 if provisioning: 

342 if provisioning in self.PROVISIONING_TYPES: 

343 self._provisioning = provisioning 

344 else: 

345 raise xs_errors.XenError( 

346 'InvalidArg', 

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

348 self.PROVISIONING_TYPES 

349 ) 

350 ) 

351 else: 

352 self._provisioning = self.PROVISIONING_DEFAULT 

353 

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

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

356 util.strtobool(monitor_db_quorum) 

357 

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

359 # 'vdi_attach_from_config' command is executed. 

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

361 if self._has_session: 

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

363 else: 

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

365 

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

367 if provisioning in self.PROVISIONING_TYPES: 

368 self._provisioning = provisioning 

369 

370 # Define properties for SR parent class. 

371 self.ops_exclusive = OPS_EXCLUSIVE 

372 self.path = LinstorVolumeManager.DEV_ROOT_PATH 

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

374 self.sr_vditype = SR.DEFAULT_TAP 

375 

376 if self.cmd == 'sr_create': 

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

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

379 self._journaler = None 

380 

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

382 class LinstorProxy: 

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

384 self.sr = sr 

385 

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

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

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

389 

390 self._linstor_proxy = LinstorProxy(self) 

391 

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

393 

394 self._vdi_shared_time = 0 

395 

396 self._init_status = self.INIT_STATUS_NOT_SET 

397 

398 self._vdis_loaded = False 

399 self._all_volume_info_cache = None 

400 self._all_volume_metadata_cache = None 

401 self._multi_cowutil = None 

402 

403 # To remove in python 3.10. 

404 # Use directly @staticmethod instead. 

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

406 def _locked_load(method): 

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

408 self._init_status = self.INIT_STATUS_OK 

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

410 

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

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

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

414 activate_lvm_group(self._group_name) 

415 

416 if not self._has_session: 

417 if self.srcmd.cmd in ( 

418 'vdi_attach_from_config', 

419 'vdi_detach_from_config', 

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

421 # empty command. 

422 None 

423 ): 

424 def create_linstor(uri, attempt_count=30): 

425 self._linstor = LinstorVolumeManager( 

426 uri, 

427 self._group_name, 

428 logger=util.SMlog, 

429 attempt_count=attempt_count 

430 ) 

431 

432 controller_uri = get_controller_uri() 

433 if controller_uri: 

434 create_linstor(controller_uri) 

435 else: 

436 def connect(): 

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

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

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

440 controller_uri = 'linstor://' + ip 

441 try: 

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

443 create_linstor(controller_uri, attempt_count=0) 

444 return controller_uri 

445 except: 

446 pass 

447 

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

449 if not controller_uri: 

450 raise xs_errors.XenError( 

451 'SRUnavailable', 

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

453 ) 

454 

455 self._journaler = LinstorJournaler( 

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

457 ) 

458 

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

460 

461 if not self.is_master(): 

462 if self.cmd in [ 

463 'sr_create', 'sr_delete', 'sr_update', 'sr_probe', 

464 'sr_scan', 'vdi_create', 'vdi_delete', 'vdi_resize', 

465 'vdi_snapshot', 'vdi_clone' 

466 ]: 

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

468 raise xs_errors.XenError('LinstorMaster') 

469 

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

471 # the VDI before the LinstorJournaler/LinstorVolumeManager 

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

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

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

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

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

477 self._vdi_shared_time = time.time() 

478 

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

480 try: 

481 self._reconnect() 

482 except Exception as e: 

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

484 

485 if self._linstor: 

486 try: 

487 hosts = self._linstor.disconnected_hosts 

488 except Exception as e: 

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

490 

491 if hosts: 

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

493 

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

495 if ( 

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

497 self.cmd != 'vdi_create' 

498 ): 

499 self._linstor.ensure_volume_is_not_locked( 

500 self.srcmd.params['vdi_uuid'] 

501 ) 

502 

503 try: 

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

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

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

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

508 # 

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

510 # resourceless volumes. 

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

512 'vdi_attach', 'vdi_detach', 

513 'vdi_activate', 'vdi_deactivate', 

514 'vdi_epoch_begin', 'vdi_epoch_end', 

515 'vdi_update', 'vdi_destroy' 

516 ]: 

517 load_vdis = ( 

518 self.cmd == 'sr_scan' or 

519 self.cmd == 'sr_attach' 

520 ) or len( 

521 self._journaler.get_all(LinstorJournaler.INFLATE) 

522 ) or len( 

523 self._journaler.get_all(LinstorJournaler.CLONE) 

524 ) 

525 

526 if load_vdis: 

527 self._load_vdis() 

528 

529 self._linstor.remove_resourceless_volumes() 

530 

531 self._synchronize_metadata() 

532 except Exception as e: 

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

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

535 # from the XAPI database otherwise. 

536 raise e 

537 util.SMlog( 

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

539 ) 

540 util.SMlog(traceback.format_exc()) 

541 

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

543 

544 @functools.wraps(wrapped_method) 

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

546 if self._init_status in \ 

547 (self.INIT_STATUS_OK, self.INIT_STATUS_IN_PROGRESS): 

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

549 if self._init_status == self.INIT_STATUS_FAIL: 

550 util.SMlog( 

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

552 .format(method) 

553 ) 

554 else: 

555 try: 

556 self._init_status = self.INIT_STATUS_IN_PROGRESS 

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

558 except Exception: 

559 if self._init_status != self.INIT_STATUS_OK: 

560 self._init_status = self.INIT_STATUS_FAIL 

561 raise 

562 

563 return wrap 

564 

565 @override 

566 def cleanup(self) -> None: 

567 if self._vdi_shared_time: 

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

569 

570 @override 

571 @_locked_load 

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

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

574 

575 host_adresses = util.get_host_addresses(self.session) 

576 if self._redundancy > len(host_adresses): 

577 raise xs_errors.XenError( 

578 'LinstorSRCreate', 

579 opterr='Redundancy greater than host count' 

580 ) 

581 

582 xenapi = self.session.xenapi 

583 srs = xenapi.SR.get_all_records_where( 

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

585 ) 

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

587 

588 for sr in srs.values(): 

589 for pbd in sr['PBDs']: 

590 device_config = xenapi.PBD.get_device_config(pbd) 

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

592 if group_name and group_name == self._group_name: 

593 raise xs_errors.XenError( 

594 'LinstorSRCreate', 

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

596 xenapi.PBD.get_uuid(pbd) 

597 ) 

598 ) 

599 

600 if srs: 

601 raise xs_errors.XenError( 

602 'LinstorSRCreate', 

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

604 ) 

605 

606 online_hosts = util.get_enabled_hosts(self.session) 

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

608 raise xs_errors.XenError( 

609 'LinstorSRCreate', 

610 opterr='Not enough online hosts' 

611 ) 

612 

613 ips = {} 

614 for host_ref in online_hosts: 

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

616 hostname = record['hostname'] 

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

618 

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

620 raise xs_errors.XenError( 

621 'LinstorSRCreate', 

622 opterr='Multiple hosts with same hostname' 

623 ) 

624 

625 # Ensure ports are opened and LINSTOR satellites 

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

627 # must be stopped. 

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

629 

630 # Create SR. 

631 # Throw if the SR already exists. 

632 try: 

633 self._linstor = LinstorVolumeManager.create_sr( 

634 self._group_name, 

635 ips, 

636 self._redundancy, 

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

638 logger=util.SMlog 

639 ) 

640 

641 util.SMlog( 

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

643 ) 

644 self._update_drbd_reactor_on_all_hosts(enabled=True) 

645 except Exception as e: 

646 if not self._linstor: 

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

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

649 

650 try: 

651 self._linstor.destroy() 

652 except Exception as e2: 

653 util.SMlog( 

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

655 .format(e2) 

656 ) 

657 raise e 

658 

659 @override 

660 @_locked_load 

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

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

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

664 

665 assert self._linstor 

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

667 raise xs_errors.XenError('SRNotEmpty') 

668 

669 node_name = get_controller_node_name() 

670 if not node_name: 

671 raise xs_errors.XenError( 

672 'LinstorSRDelete', 

673 opterr='Cannot get controller node name' 

674 ) 

675 

676 host_ref = None 

677 if node_name == 'localhost': 

678 host_ref = util.get_this_host_ref(self.session) 

679 else: 

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

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

682 if r_name == node_name: 

683 host_ref = slave 

684 break 

685 

686 if not host_ref: 

687 raise xs_errors.XenError( 

688 'LinstorSRDelete', 

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

690 node_name 

691 ) 

692 ) 

693 

694 try: 

695 if self._monitor_db_quorum: 

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

697 self._update_drbd_reactor_on_all_hosts( 

698 controller_node_name=node_name, enabled=False 

699 ) 

700 

701 args = { 

702 'groupName': self._group_name, 

703 } 

704 self._exec_manager_command( 

705 host_ref, 'destroy', args, 'LinstorSRDelete' 

706 ) 

707 except Exception as e: 

708 try: 

709 self._update_drbd_reactor_on_all_hosts( 

710 controller_node_name=node_name, enabled=True 

711 ) 

712 if self._monitor_db_quorum: 

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

714 except Exception as e2: 

715 util.SMlog( 

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

717 .format(e2) 

718 ) 

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

720 raise xs_errors.XenError( 

721 'LinstorSRDelete', 

722 opterr=str(e) 

723 ) 

724 

725 lock.Lock.cleanupAll(self.uuid) 

726 

727 @override 

728 @_locked_load 

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

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

731 

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

733 if not self._linstor: 

734 raise xs_errors.XenError( 

735 'SRUnavailable', 

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

737 ) 

738 

739 self._update_stats(0) 

740 

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

742 xenapi = self.session.xenapi 

743 self._linstor.metadata = { 

744 NAME_LABEL_TAG: util.to_plain_string( 

745 xenapi.SR.get_name_label(self.sr_ref) 

746 ), 

747 NAME_DESCRIPTION_TAG: util.to_plain_string( 

748 xenapi.SR.get_name_description(self.sr_ref) 

749 ) 

750 } 

751 

752 @override 

753 @_locked_load 

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

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

756 

757 if not self._linstor: 

758 raise xs_errors.XenError( 

759 'SRUnavailable', 

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

761 ) 

762 

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

764 self._linstor.set_drbd_ha_properties(DATABASE_VOLUME_NAME) 

765 

766 @override 

767 @_locked_load 

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

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

770 cleanup.abort(self.uuid) 

771 

772 @override 

773 @_locked_load 

774 def probe(self) -> str: 

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

776 # TODO 

777 return '' 

778 

779 @override 

780 @_locked_load 

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

782 if self._init_status == self.INIT_STATUS_FAIL: 

783 return 

784 

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

786 if not self._linstor: 

787 raise xs_errors.XenError( 

788 'SRUnavailable', 

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

790 ) 

791 

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

793 # are loaded. 

794 self._load_vdis() 

795 self._update_physical_size() 

796 

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

798 if self.vdis[vdi_uuid].deleted: 

799 del self.vdis[vdi_uuid] 

800 

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

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

803 try: 

804 self._linstor.get_database_path() 

805 except Exception as e: 

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

807 # VDIs in the XAPI database... 

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

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

810 ): 

811 raise xs_errors.XenError( 

812 'SRUnavailable', 

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

814 ) 

815 

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

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

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

819 self._kick_gc() 

820 

821 def is_master(self): 

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

823 if 'SRmaster' not in self.dconf: 

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

825 else: 

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

827 

828 return self._is_master 

829 

830 @override 

831 @_locked_load 

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

833 return LinstorVDI(self, uuid) 

834 

835 # To remove in python 3.10 

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

837 _locked_load = staticmethod(_locked_load) 

838 

839 # -------------------------------------------------------------------------- 

840 # Lock. 

841 # -------------------------------------------------------------------------- 

842 

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

844 master = util.get_master_ref(self.session) 

845 

846 command = 'lockVdi' 

847 args = { 

848 'groupName': self._group_name, 

849 'srUuid': self.uuid, 

850 'vdiUuid': vdi_uuid, 

851 'locked': str(locked) 

852 } 

853 

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

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

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

857 # 

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

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

860 # to implement without impacting performance. 

861 if not locked: 

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

863 timeout = LinstorVolumeManager.LOCKED_EXPIRATION_DELAY * 0.7 

864 if elapsed_time >= timeout: 

865 util.SMlog( 

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

867 .format(vdi_uuid) 

868 ) 

869 return 

870 

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

872 

873 # -------------------------------------------------------------------------- 

874 # Network. 

875 # -------------------------------------------------------------------------- 

876 

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

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

879 host_uuid = host_rec['uuid'] 

880 

881 try: 

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

883 host_ref, self.MANAGER_PLUGIN, command, args 

884 ) 

885 except Exception as e: 

886 util.SMlog( 

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

888 host_uuid, self.MANAGER_PLUGIN, command, args 

889 ) 

890 ) 

891 raise e 

892 

893 util.SMlog( 

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

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

896 ) 

897 ) 

898 if ret == 'False': 

899 raise xs_errors.XenError( 

900 error, 

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

902 ) 

903 

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

905 self._exec_manager_command( 

906 host, 

907 'prepareSr' if enabled else 'releaseSr', 

908 {'groupName': group_name}, 

909 'SRUnavailable' 

910 ) 

911 

912 def _prepare_sr_on_all_hosts(self, group_name, enabled): 

913 master = util.get_master_ref(self.session) 

914 self._prepare_sr(master, group_name, enabled) 

915 

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

917 self._prepare_sr(slave, group_name, enabled) 

918 

919 def _update_drbd_reactor(self, host, enabled): 

920 self._exec_manager_command( 

921 host, 

922 'updateDrbdReactor', 

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

924 'SRUnavailable' 

925 ) 

926 

927 def _update_drbd_reactor_on_all_hosts( 

928 self, enabled, controller_node_name=None 

929 ): 

930 if controller_node_name == 'localhost': 

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

932 util.get_this_host_ref(self.session) 

933 )['hostname'] 

934 assert controller_node_name 

935 assert controller_node_name != 'localhost' 

936 

937 controller_host = None 

938 secondary_hosts = [] 

939 

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

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

942 hostname = host_rec['hostname'] 

943 if controller_node_name == hostname: 

944 controller_host = host_ref 

945 else: 

946 secondary_hosts.append((host_ref, hostname)) 

947 

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

949 if controller_node_name and not controller_host: 

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

951 controller_node_name 

952 )) 

953 

954 if enabled and controller_host: 

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

956 action_name, controller_node_name 

957 )) 

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

959 # node name first. 

960 self._update_drbd_reactor(controller_host, enabled) 

961 

962 for host_ref, hostname in secondary_hosts: 

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

964 action_name, hostname 

965 )) 

966 self._update_drbd_reactor(host_ref, enabled) 

967 

968 if not enabled and controller_host: 

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

970 action_name, controller_node_name 

971 )) 

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

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

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

975 self._update_drbd_reactor(controller_host, enabled) 

976 

977 # -------------------------------------------------------------------------- 

978 # Metadata. 

979 # -------------------------------------------------------------------------- 

980 

981 def _synchronize_metadata_and_xapi(self): 

982 try: 

983 # First synch SR parameters. 

984 self.update(self.uuid) 

985 

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

987 xenapi = self.session.xenapi 

988 volumes_metadata = self._linstor.get_volumes_with_metadata() 

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

990 try: 

991 vdi_ref = xenapi.VDI.get_by_uuid(vdi_uuid) 

992 except Exception: 

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

994 continue 

995 

996 label = util.to_plain_string( 

997 xenapi.VDI.get_name_label(vdi_ref) 

998 ) 

999 description = util.to_plain_string( 

1000 xenapi.VDI.get_name_description(vdi_ref) 

1001 ) 

1002 

1003 if ( 

1004 volume_metadata.get(NAME_LABEL_TAG) != label or 

1005 volume_metadata.get(NAME_DESCRIPTION_TAG) != description 

1006 ): 

1007 self._linstor.update_volume_metadata(vdi_uuid, { 

1008 NAME_LABEL_TAG: label, 

1009 NAME_DESCRIPTION_TAG: description 

1010 }) 

1011 except Exception as e: 

1012 raise xs_errors.XenError( 

1013 'MetadataError', 

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

1015 ) 

1016 

1017 def _synchronize_metadata(self): 

1018 if not self.is_master(): 

1019 return 

1020 

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

1022 if self.cmd == 'sr_attach': 

1023 try: 

1024 util.SMlog( 

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

1026 ) 

1027 self._synchronize_metadata_and_xapi() 

1028 except Exception as e: 

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

1030 

1031 # -------------------------------------------------------------------------- 

1032 # Stats. 

1033 # -------------------------------------------------------------------------- 

1034 

1035 def _update_stats(self, virt_alloc_delta): 

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

1037 self.sr_ref 

1038 )) 

1039 

1040 # Update size attributes of the SR parent class. 

1041 self.virtual_allocation = valloc + virt_alloc_delta 

1042 

1043 self._update_physical_size() 

1044 

1045 # Notify SR parent class. 

1046 self._db_update() 

1047 

1048 def _update_physical_size(self): 

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

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

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

1052 self.physical_size = min_physical_size * pool_count // \ 

1053 self._linstor.redundancy 

1054 

1055 self.physical_utilisation = self._linstor.allocated_volume_size 

1056 

1057 # -------------------------------------------------------------------------- 

1058 # VDIs. 

1059 # -------------------------------------------------------------------------- 

1060 

1061 def _load_vdis(self): 

1062 if self._vdis_loaded: 

1063 return 

1064 

1065 assert self.is_master() 

1066 

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

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

1069 # enjoy it with a few lines. 

1070 self._create_linstor_cache() 

1071 self._load_vdis_ex() 

1072 self._destroy_linstor_cache() 

1073 

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

1075 self._vdis_loaded = True 

1076 

1077 self._undo_all_journal_transactions() 

1078 

1079 def _load_vdis_ex(self): 

1080 # 1. Get existing VDIs in XAPI. 

1081 xenapi = self.session.xenapi 

1082 xapi_vdi_uuids = set() 

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

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

1085 

1086 # 2. Get volumes info. 

1087 all_volume_info = self._all_volume_info_cache 

1088 volumes_metadata = self._all_volume_metadata_cache 

1089 

1090 # 3. Get CBT vdis. 

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

1092 cbt_vdis = set() 

1093 for volume_metadata in volumes_metadata.values(): 

1094 cbt_uuid = volume_metadata.get(CBTLOG_TAG) 

1095 if cbt_uuid: 

1096 cbt_vdis.add(cbt_uuid) 

1097 

1098 introduce = False 

1099 

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

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

1102 has_clone_entries = list(self._journaler.get_all( 

1103 LinstorJournaler.CLONE 

1104 ).items()) 

1105 

1106 if has_clone_entries: 

1107 util.SMlog( 

1108 'Cannot introduce VDIs during scan because it exists ' 

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

1110 ) 

1111 else: 

1112 introduce = True 

1113 

1114 # 4. Now process all volume info. 

1115 vdi_to_snaps = {} 

1116 vdi_uuids = [] 

1117 

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

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

1120 continue 

1121 

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

1123 if vdi_uuid not in xapi_vdi_uuids: 

1124 if not introduce: 

1125 continue 

1126 

1127 if vdi_uuid.startswith('DELETED_'): 

1128 continue 

1129 

1130 volume_metadata = volumes_metadata.get(vdi_uuid) 

1131 if not volume_metadata: 

1132 util.SMlog( 

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

1134 .format(vdi_uuid) 

1135 ) 

1136 continue 

1137 

1138 util.SMlog( 

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

1140 'LINSTOR and not in XAPI...' 

1141 .format(vdi_uuid) 

1142 ) 

1143 

1144 try: 

1145 self._linstor.get_device_path(vdi_uuid) 

1146 except Exception as e: 

1147 util.SMlog( 

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

1149 .format(vdi_uuid, e) 

1150 ) 

1151 continue 

1152 

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

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

1155 vdi_type = volume_metadata.get(VDI_TYPE_TAG) 

1156 

1157 if not vdi_type: 

1158 util.SMlog( 

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

1160 'without vdi_type' 

1161 ) 

1162 continue 

1163 

1164 sm_config = { 

1165 'vdi_type': vdi_type 

1166 } 

1167 

1168 if not VdiType.isCowImage(vdi_type): 

1169 managed = not volume_metadata.get(HIDDEN_TAG) 

1170 else: 

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

1172 managed = not image_info.hidden 

1173 if image_info.parentUuid: 

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

1175 

1176 util.SMlog( 

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

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

1179 name_label, 

1180 volume_info.virtual_size, 

1181 volume_info.allocated_size 

1182 ) 

1183 ) 

1184 

1185 vdi_ref = xenapi.VDI.db_introduce( 

1186 vdi_uuid, 

1187 name_label, 

1188 volume_metadata.get(NAME_DESCRIPTION_TAG) or '', 

1189 self.sr_ref, 

1190 type, 

1191 False, # sharable 

1192 bool(volume_metadata.get(READ_ONLY_TAG)), 

1193 {}, # other_config 

1194 vdi_uuid, # location 

1195 {}, # xenstore_data 

1196 sm_config, 

1197 managed, 

1198 str(volume_info.virtual_size), 

1199 str(volume_info.allocated_size) 

1200 ) 

1201 

1202 is_a_snapshot = volume_metadata.get(IS_A_SNAPSHOT_TAG) 

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

1204 if is_a_snapshot: 

1205 xenapi.VDI.set_snapshot_time( 

1206 vdi_ref, 

1207 xmlrpc.client.DateTime( 

1208 volume_metadata[SNAPSHOT_TIME_TAG] or 

1209 '19700101T00:00:00Z' 

1210 ) 

1211 ) 

1212 

1213 snap_uuid = volume_metadata[SNAPSHOT_OF_TAG] 

1214 if snap_uuid in vdi_to_snaps: 

1215 vdi_to_snaps[snap_uuid].append(vdi_uuid) 

1216 else: 

1217 vdi_to_snaps[snap_uuid] = [vdi_uuid] 

1218 

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

1220 vdi_uuids.append(vdi_uuid) 

1221 

1222 # 5. Create VDIs. 

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

1224 

1225 def load_vdi(vdi_uuid, multi_cowutil): 

1226 vdi = self.vdi(vdi_uuid) 

1227 

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

1229 cowutil_instance = multi_cowutil.get_local_cowutil(vdi.vdi_type) 

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

1231 

1232 return vdi 

1233 

1234 try: 

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

1236 finally: 

1237 multi_cowutil = self._multi_cowutil 

1238 self._multi_cowutil = None 

1239 del multi_cowutil 

1240 

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

1242 # or already in XAPI. 

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

1244 volume_metadata = volumes_metadata.get(vdi.uuid) 

1245 cbt_uuid = volume_metadata.get(CBTLOG_TAG) 

1246 if cbt_uuid in cbt_vdis: 

1247 vdi_ref = xenapi.VDI.get_by_uuid(vdi_uuid) 

1248 xenapi.VDI.set_cbt_enabled(vdi_ref, True) 

1249 # For existing VDIs, update local state too. 

1250 # Scan in base class SR updates existing VDIs 

1251 # again based on local states. 

1252 self.vdis[vdi_uuid].cbt_enabled = True 

1253 cbt_vdis.remove(cbt_uuid) 

1254 

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

1256 for src_uuid in vdi_to_snaps: 

1257 try: 

1258 src_ref = xenapi.VDI.get_by_uuid(src_uuid) 

1259 except Exception: 

1260 # The source VDI no longer exists, continue. 

1261 continue 

1262 

1263 for snap_uuid in vdi_to_snaps[src_uuid]: 

1264 try: 

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

1266 snap_ref = xenapi.VDI.get_by_uuid(snap_uuid) 

1267 xenapi.VDI.set_snapshot_of(snap_ref, src_ref) 

1268 except Exception as e: 

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

1270 

1271 # TODO: Check correctly how to use CBT. 

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

1273 

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

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

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

1277 for cbt_uuid in cbt_vdis: 

1278 new_vdi = self.vdi(cbt_uuid) 

1279 new_vdi.ty = 'cbt_metadata' 

1280 new_vdi.cbt_enabled = True 

1281 self.vdis[cbt_uuid] = new_vdi 

1282 

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

1284 self.virtual_allocation = 0 

1285 

1286 # 10. Build geneology. 

1287 geneology = {} 

1288 

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

1290 if vdi.parent: 

1291 if vdi.parent in self.vdis: 

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

1293 if vdi.parent in geneology: 

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

1295 else: 

1296 geneology[vdi.parent] = [vdi_uuid] 

1297 if not vdi.hidden: 

1298 self.virtual_allocation += vdi.size 

1299 

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

1301 # will be GC'ed. 

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

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

1304 util.SMlog( 

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

1306 ) 

1307 del self.vdis[vdi_uuid] 

1308 

1309 # -------------------------------------------------------------------------- 

1310 # Journals. 

1311 # -------------------------------------------------------------------------- 

1312 

1313 def _get_vdi_path_and_parent(self, vdi_uuid, volume_name): 

1314 try: 

1315 device_path = self._linstor.build_device_path(volume_name) 

1316 if not util.pathexists(device_path): 

1317 return (None, None) 

1318 

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

1320 volume_metadata = self._linstor.get_volume_metadata(vdi_uuid) 

1321 vdi_type = volume_metadata[VDI_TYPE_TAG] 

1322 if not VdiType.isCowImage(vdi_type): 

1323 return (device_path, None) 

1324 

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

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

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

1328 return (None, None) 

1329 

1330 image_info = linstorcowutil.get_info(vdi_uuid) 

1331 if image_info: 

1332 return (device_path, image_info.parentUuid) 

1333 except Exception as e: 

1334 util.SMlog( 

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

1336 .format(e) 

1337 ) 

1338 return (None, None) 

1339 

1340 def _undo_all_journal_transactions(self): 

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

1342 self.lock.acquire() 

1343 try: 

1344 self._handle_interrupted_inflate_ops() 

1345 self._handle_interrupted_clone_ops() 

1346 pass 

1347 finally: 

1348 self.lock.release() 

1349 

1350 def _handle_interrupted_inflate_ops(self): 

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

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

1353 self._handle_interrupted_inflate(vdi_uuid, old_size) 

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

1355 

1356 def _handle_interrupted_clone_ops(self): 

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

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

1359 self._handle_interrupted_clone(vdi_uuid, old_size) 

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

1361 

1362 def _handle_interrupted_inflate(self, vdi_uuid, old_size): 

1363 util.SMlog( 

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

1365 .format(vdi_uuid, old_size) 

1366 ) 

1367 

1368 vdi = self.vdis.get(vdi_uuid) 

1369 if not vdi: 

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

1371 return 

1372 

1373 assert not self._all_volume_info_cache 

1374 volume_info = self._linstor.get_volume_info(vdi_uuid) 

1375 

1376 current_size = volume_info.virtual_size 

1377 assert current_size > 0 

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

1379 

1380 def _handle_interrupted_clone( 

1381 self, vdi_uuid, clone_info, force_undo=False 

1382 ): 

1383 util.SMlog( 

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

1385 .format(vdi_uuid, clone_info) 

1386 ) 

1387 

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

1389 

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

1391 volume_names = self._linstor.get_volumes_with_name() 

1392 

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

1394 if base_uuid not in volume_names: 

1395 if vdi_uuid in volume_names: 

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

1397 return 

1398 raise util.SMException( 

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

1400 .format(base_uuid, vdi_uuid) 

1401 ) 

1402 

1403 if force_undo: 

1404 util.SMlog('Explicit revert') 

1405 self._undo_clone( 

1406 volume_names, vdi_uuid, base_uuid, snap_uuid 

1407 ) 

1408 return 

1409 

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

1411 if vdi_uuid not in volume_names or \ 

1412 (snap_uuid and snap_uuid not in volume_names): 

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

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

1415 return 

1416 

1417 vdi_path, vdi_parent_uuid = self._get_vdi_path_and_parent( 

1418 vdi_uuid, volume_names[vdi_uuid] 

1419 ) 

1420 snap_path, snap_parent_uuid = self._get_vdi_path_and_parent( 

1421 snap_uuid, volume_names[snap_uuid] 

1422 ) 

1423 

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

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

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

1427 return 

1428 

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

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

1431 

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

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

1434 base_metadata = self._linstor.get_volume_metadata(base_uuid) 

1435 base_type = base_metadata[VDI_TYPE_TAG] 

1436 

1437 if not util.pathexists(base_path): 

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

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

1440 return 

1441 

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

1443 

1444 # Un-hide the parent. 

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

1446 if VdiType.isCowImage(base_type): 

1447 image_info = linstorcowutil.get_info(base_uuid, False) 

1448 if image_info.hidden: 

1449 linstorcowutil.set_hidden(base_path, False) 

1450 elif base_metadata.get(HIDDEN_TAG): 

1451 self._linstor.update_volume_metadata( 

1452 base_uuid, {HIDDEN_TAG: False} 

1453 ) 

1454 

1455 # Remove the child nodes. 

1456 if snap_uuid and snap_uuid in volume_names: 

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

1458 

1459 try: 

1460 self._linstor.destroy_volume(snap_uuid) 

1461 except Exception as e: 

1462 util.SMlog( 

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

1464 .format(snap_uuid, e) 

1465 ) 

1466 

1467 if vdi_uuid in volume_names: 

1468 try: 

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

1470 self._linstor.destroy_volume(vdi_uuid) 

1471 except Exception as e: 

1472 util.SMlog( 

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

1474 .format(vdi_uuid, e) 

1475 ) 

1476 # We can get an exception like this: 

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

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

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

1480 # UUID of this bad VDI before. 

1481 self._linstor.update_volume_uuid( 

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

1483 ) 

1484 

1485 # Rename! 

1486 self._linstor.update_volume_uuid(base_uuid, vdi_uuid) 

1487 

1488 # Inflate to the right size. 

1489 if VdiType.isCowImage(base_type): 

1490 vdi = self.vdi(vdi_uuid) 

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

1492 volume_size = linstorcowutil.compute_volume_size(vdi.size) 

1493 linstorcowutil.inflate( 

1494 self._journaler, vdi_uuid, vdi.path, 

1495 volume_size, vdi.capacity 

1496 ) 

1497 self.vdis[vdi_uuid] = vdi 

1498 

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

1500 # flag to facilitate vm deactivate. 

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

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

1503 

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

1505 

1506 # -------------------------------------------------------------------------- 

1507 # Cache. 

1508 # -------------------------------------------------------------------------- 

1509 

1510 def _create_linstor_cache(self): 

1511 reconnect = False 

1512 

1513 def create_cache(): 

1514 nonlocal reconnect 

1515 try: 

1516 if reconnect: 

1517 self._reconnect() 

1518 return self._linstor.get_volumes_with_info() 

1519 except Exception as e: 

1520 reconnect = True 

1521 raise e 

1522 

1523 self._all_volume_metadata_cache = \ 

1524 self._linstor.get_volumes_with_metadata() 

1525 self._all_volume_info_cache = util.retry( 

1526 create_cache, 

1527 maxretry=10, 

1528 period=3 

1529 ) 

1530 

1531 def _destroy_linstor_cache(self): 

1532 self._all_volume_info_cache = None 

1533 self._all_volume_metadata_cache = None 

1534 

1535 # -------------------------------------------------------------------------- 

1536 # Misc. 

1537 # -------------------------------------------------------------------------- 

1538 

1539 def _reconnect(self): 

1540 controller_uri = get_controller_uri() 

1541 

1542 self._journaler = LinstorJournaler( 

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

1544 ) 

1545 

1546 # Try to open SR if exists. 

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

1548 # we are trying to execute an exclusive operation. 

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

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

1551 # the SR is locked. 

1552 self._linstor = LinstorVolumeManager( 

1553 controller_uri, 

1554 self._group_name, 

1555 repair=( 

1556 self.is_master() and 

1557 self.srcmd.cmd in self.ops_exclusive 

1558 ), 

1559 logger=util.SMlog 

1560 ) 

1561 

1562 def _ensure_space_available(self, amount_needed): 

1563 space_available = self._linstor.max_volume_size_allowed 

1564 if (space_available < amount_needed): 

1565 util.SMlog( 

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

1567 space_available, amount_needed 

1568 ) 

1569 ) 

1570 raise xs_errors.XenError('SRNoSpace') 

1571 

1572 def _kick_gc(self): 

1573 util.SMlog('Kicking GC') 

1574 cleanup.start_gc_service(self.uuid) 

1575 

1576# ============================================================================== 

1577# LinstorSr VDI 

1578# ============================================================================== 

1579 

1580 

1581class LinstorVDI(VDI.VDI): 

1582 # -------------------------------------------------------------------------- 

1583 # VDI methods. 

1584 # -------------------------------------------------------------------------- 

1585 

1586 @override 

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

1588 self._lock = self.sr.lock 

1589 self._exists = True 

1590 self._linstor = self.sr._linstor 

1591 

1592 # Update hidden parent property. 

1593 self.hidden = False 

1594 

1595 def raise_bad_load(e): 

1596 util.SMlog( 

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

1598 ) 

1599 util.SMlog(traceback.format_exc()) 

1600 raise xs_errors.XenError( 

1601 'VDIUnavailable', 

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

1603 ) 

1604 

1605 # Try to load VDI. 

1606 try: 

1607 if ( 

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

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

1610 ): 

1611 self._set_type(VdiType.RAW) 

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

1613 else: 

1614 self._determine_type_and_path() 

1615 self._load_this() 

1616 

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

1618 self.uuid, self.path, self.hidden 

1619 )) 

1620 except LinstorVolumeManagerError as e: 

1621 # 1. It may be a VDI deletion. 

1622 if e.code == LinstorVolumeManagerError.ERR_VOLUME_NOT_EXISTS: 

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

1624 self.deleted = True 

1625 return 

1626 

1627 # 2. Or maybe a creation. 

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

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

1630 

1631 self._exists = False 

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

1633 if vdi_sm_config: 

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

1635 if image_format: 

1636 try: 

1637 self._set_type(CREATE_PARAM_TYPES[image_format]) 

1638 except: 

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

1640 

1641 if not self.vdi_type: 

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

1643 

1644 if VdiType.isCowImage(self.vdi_type): 

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

1646 

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

1648 self._update_device_name(None) 

1649 return 

1650 raise_bad_load(e) 

1651 except Exception as e: 

1652 raise_bad_load(e) 

1653 

1654 @override 

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

1656 # Usage example: 

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

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

1659 

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

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

1662 if self._exists: 

1663 raise xs_errors.XenError('VDIExists') 

1664 

1665 assert self.uuid 

1666 assert self.ty 

1667 assert self.vdi_type 

1668 

1669 # 2. Compute size and check space available. 

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

1671 volume_size = self.linstorcowutil.compute_volume_size(size) 

1672 util.SMlog( 

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

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

1675 ) 

1676 self.sr._ensure_space_available(volume_size) 

1677 

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

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

1680 

1681 # 4. Create! 

1682 failed = False 

1683 try: 

1684 volume_name = None 

1685 if self.ty == 'ha_statefile': 

1686 volume_name = HA_VOLUME_NAME 

1687 elif self.ty == 'redo_log': 

1688 volume_name = REDO_LOG_VOLUME_NAME 

1689 

1690 self._linstor.create_volume( 

1691 self.uuid, 

1692 volume_size, 

1693 persistent=False, 

1694 volume_name=volume_name, 

1695 high_availability=volume_name is not None 

1696 ) 

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

1698 

1699 self._update_device_name(volume_info.name) 

1700 

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

1702 self.size = volume_info.virtual_size 

1703 else: 

1704 self.linstorcowutil.create( 

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

1706 ) 

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

1708 

1709 if self._key_hash: 

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

1711 

1712 # Because cowutil commands modify the volume data, 

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

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

1715 

1716 volume_metadata = { 

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

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

1719 IS_A_SNAPSHOT_TAG: False, 

1720 SNAPSHOT_OF_TAG: '', 

1721 SNAPSHOT_TIME_TAG: '', 

1722 TYPE_TAG: self.ty, 

1723 VDI_TYPE_TAG: self.vdi_type, 

1724 READ_ONLY_TAG: bool(self.read_only), 

1725 METADATA_OF_POOL_TAG: '' 

1726 } 

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

1728 

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

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

1731 # an already opened volume. 

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

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

1734 

1735 self._linstor.mark_volume_as_persistent(self.uuid) 

1736 except util.CommandException as e: 

1737 failed = True 

1738 raise xs_errors.XenError( 

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

1740 ) 

1741 except Exception as e: 

1742 failed = True 

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

1744 finally: 

1745 if failed: 

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

1747 try: 

1748 self._linstor.destroy_volume(self.uuid) 

1749 except Exception as e: 

1750 util.SMlog( 

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

1752 '{}'.format(e) 

1753 ) 

1754 

1755 self.utilisation = volume_info.allocated_size 

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

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

1758 

1759 self.ref = self._db_introduce() 

1760 self.sr._update_stats(self.size) 

1761 

1762 return VDI.VDI.get_params(self) 

1763 

1764 @override 

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

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

1767 if self.attached: 

1768 raise xs_errors.XenError('VDIInUse') 

1769 

1770 if self.deleted: 

1771 return super(LinstorVDI, self).delete( 

1772 sr_uuid, vdi_uuid, data_only 

1773 ) 

1774 

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

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

1777 raise xs_errors.XenError( 

1778 'VDIDelete', 

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

1780 ) 

1781 

1782 try: 

1783 # Remove from XAPI and delete from LINSTOR. 

1784 self._linstor.destroy_volume(self.uuid) 

1785 if not data_only: 

1786 self._db_forget() 

1787 

1788 self.sr.lock.cleanupAll(vdi_uuid) 

1789 except Exception as e: 

1790 util.SMlog( 

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

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

1793 ) 

1794 

1795 try: 

1796 raise e 

1797 except LinstorVolumeManagerError as e: 

1798 if e.code != LinstorVolumeManagerError.ERR_VOLUME_DESTROY: 

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

1800 

1801 return 

1802 

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

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

1805 

1806 # TODO: Check size after delete. 

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

1808 self.sr._kick_gc() 

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

1810 

1811 @override 

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

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

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

1815 if ( 

1816 not attach_from_config or 

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

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

1819 raise xs_errors.XenError( 

1820 'VDIUnavailable', 

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

1822 'scan SR first to trigger auto-repair' 

1823 ) 

1824 

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

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

1827 

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

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

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

1831 # than the COW size + bitmap size. 

1832 need_inflate = True 

1833 if ( 

1834 not VdiType.isCowImage(self.vdi_type) or 

1835 not writable or 

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

1837 ): 

1838 need_inflate = False 

1839 

1840 if need_inflate: 

1841 try: 

1842 self._prepare_thin(True) 

1843 except Exception as e: 

1844 raise xs_errors.XenError( 

1845 'VDIUnavailable', 

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

1847 .format(e) 

1848 ) 

1849 

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

1851 self.xenstore_data = {} 

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

1853 

1854 if ( 

1855 USE_HTTP_NBD_SERVERS and 

1856 attach_from_config and 

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

1858 ): 

1859 return self._attach_using_http_nbd() 

1860 

1861 # Ensure we have a path... 

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

1863 

1864 self.attached = True 

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

1866 

1867 @override 

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

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

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

1871 self.attached = False 

1872 

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

1874 return self._detach_using_http_nbd() 

1875 

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

1877 return 

1878 

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

1880 # equal to the LINSTOR volume size. 

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

1882 already_deflated = self.capacity <= volume_size 

1883 

1884 if already_deflated: 

1885 util.SMlog( 

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

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

1888 ) 

1889 

1890 need_deflate = True 

1891 if already_deflated: 

1892 need_deflate = False 

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

1894 need_deflate = False 

1895 

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

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

1898 need_deflate = True 

1899 

1900 if need_deflate: 

1901 try: 

1902 self._prepare_thin(False) 

1903 except Exception as e: 

1904 raise xs_errors.XenError( 

1905 'VDIUnavailable', 

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

1907 .format(e) 

1908 ) 

1909 

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

1911 if self.sr.is_master(): 

1912 return 

1913 

1914 while vdi_uuid: 

1915 try: 

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

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

1918 except Exception: 

1919 break 

1920 

1921 if util.pathexists(path): 

1922 try: 

1923 self._linstor.remove_volume_if_diskless(vdi_uuid) 

1924 except Exception as e: 

1925 # Ensure we can always detach properly. 

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

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

1928 vdi_uuid = parent_vdi_uuid 

1929 

1930 @override 

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

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

1933 if not self.sr.is_master(): 

1934 raise xs_errors.XenError( 

1935 'VDISize', 

1936 opterr='resize on slave not allowed' 

1937 ) 

1938 

1939 if self.hidden: 

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

1941 

1942 # Compute the virtual COW and DRBD volume size. 

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

1944 volume_size = self.linstorcowutil.compute_volume_size(size) 

1945 util.SMlog( 

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

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

1948 ) 

1949 

1950 if size < self.size: 

1951 util.SMlog( 

1952 'vdi_resize: shrinking not supported: ' 

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

1954 ) 

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

1956 

1957 if size == self.size: 

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

1959 

1960 # Compute VDI sizes 

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

1962 old_volume_size = self.size 

1963 new_volume_size = LinstorVolumeManager.round_up_volume_size(size) 

1964 else: 

1965 old_volume_size = self.utilisation 

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

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

1968 new_volume_size = old_volume_size 

1969 else: 

1970 new_volume_size = self.linstorcowutil.compute_volume_size(size) 

1971 assert new_volume_size >= old_volume_size 

1972 

1973 space_needed = new_volume_size - old_volume_size 

1974 self.sr._ensure_space_available(space_needed) 

1975 

1976 old_size = self.size 

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

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

1979 else: 

1980 if new_volume_size != old_volume_size: 

1981 self.linstorcowutil.inflate( 

1982 self.sr._journaler, self.uuid, self.path, 

1983 new_volume_size, old_volume_size 

1984 ) 

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

1986 

1987 # Reload size attributes. 

1988 self._load_this() 

1989 

1990 # Update metadata 

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

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

1993 self.session.xenapi.VDI.set_physical_utilisation( 

1994 vdi_ref, str(self.utilisation) 

1995 ) 

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

1997 return VDI.VDI.get_params(self) 

1998 

1999 @override 

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

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

2002 

2003 @override 

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

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

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

2007 raise xs_errors.XenError('Unimplemented') 

2008 

2009 parent_uuid = vdi1 

2010 parent_path = self._linstor.get_device_path(parent_uuid) 

2011 

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

2013 # have a readonly error. 

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

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

2016 

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

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

2019 try: 

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

2021 self.linstorcowutil.set_hidden(parent_path) 

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

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

2024 ) 

2025 finally: 

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

2027 

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

2029 raise util.SMException( 

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

2031 ) 

2032 

2033 util.SMlog('Compose done') 

2034 

2035 @override 

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

2037 """ 

2038 Generate the XML config required to attach and activate 

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

2040 activation is handled by vdi_attach_from_config below. 

2041 """ 

2042 

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

2044 

2045 resp = {} 

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

2047 resp['sr_uuid'] = sr_uuid 

2048 resp['vdi_uuid'] = self.uuid 

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

2050 resp['command'] = 'vdi_attach_from_config' 

2051 

2052 # By default, we generate a normal config. 

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

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

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

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

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

2058 # instead. 

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

2060 if not USE_HTTP_NBD_SERVERS or volume_name not in [ 

2061 HA_VOLUME_NAME, REDO_LOG_VOLUME_NAME 

2062 ]: 

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

2064 available = False 

2065 # Try to refresh symlink path... 

2066 try: 

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

2068 available = util.pathexists(self.path) 

2069 except Exception: 

2070 pass 

2071 if not available: 

2072 raise xs_errors.XenError('VDIUnavailable') 

2073 

2074 resp['vdi_path'] = self.path 

2075 else: 

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

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

2078 

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

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

2081 

2082 @override 

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

2084 """ 

2085 Attach and activate a VDI using config generated by 

2086 vdi_generate_config above. This is used for cases such as 

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

2088 """ 

2089 

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

2091 

2092 try: 

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

2094 self.sr.attach(sr_uuid) 

2095 

2096 if not DRIVER_CONFIG['ATTACH_FROM_CONFIG_WITH_TAPDISK']: 

2097 return self.attach(sr_uuid, vdi_uuid) 

2098 except Exception: 

2099 util.logException('LinstorVDI.attach_from_config') 

2100 raise xs_errors.XenError( 

2101 'SRUnavailable', 

2102 opterr='Unable to attach from config' 

2103 ) 

2104 return '' 

2105 

2106 def reset_leaf(self, sr_uuid, vdi_uuid): 

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

2108 raise xs_errors.XenError('Unimplemented') 

2109 

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

2111 raise util.SMException( 

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

2113 .format(self.uuid) 

2114 ) 

2115 

2116 self.linstorcowutil.kill_data(self.path) 

2117 

2118 def _load_this(self): 

2119 volume_metadata = None 

2120 if self.sr._all_volume_metadata_cache: 

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

2122 assert volume_metadata 

2123 else: 

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

2125 

2126 volume_info = None 

2127 if self.sr._all_volume_info_cache: 

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

2129 assert volume_info 

2130 else: 

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

2132 

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

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

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

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

2137 # be lower than virtual size at creation. 

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

2139 self.utilisation = volume_info.allocated_size 

2140 self.capacity = volume_info.virtual_size 

2141 

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

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

2144 self.size = volume_info.virtual_size 

2145 self.parent = '' 

2146 else: 

2147 if self.sr._multi_cowutil: 

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

2149 else: 

2150 cowutil_instance = self.linstorcowutil 

2151 

2152 image_info = cowutil_instance.get_info(self.uuid) 

2153 self.hidden = image_info.hidden 

2154 self.size = image_info.sizeVirt 

2155 self.parent = image_info.parentUuid 

2156 

2157 if self.hidden: 

2158 self.managed = False 

2159 

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

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

2162 

2163 # Update sm_config_override of VDI parent class. 

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

2165 

2166 def _mark_hidden(self, hidden=True): 

2167 if self.hidden == hidden: 

2168 return 

2169 

2170 if VdiType.isCowImage(self.vdi_type): 

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

2172 else: 

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

2174 HIDDEN_TAG: hidden 

2175 }) 

2176 self.hidden = hidden 

2177 

2178 @override 

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

2180 xenapi = self.session.xenapi 

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

2182 

2183 volume_metadata = { 

2184 NAME_LABEL_TAG: util.to_plain_string( 

2185 xenapi.VDI.get_name_label(vdi_ref) 

2186 ), 

2187 NAME_DESCRIPTION_TAG: util.to_plain_string( 

2188 xenapi.VDI.get_name_description(vdi_ref) 

2189 ) 

2190 } 

2191 

2192 try: 

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

2194 except LinstorVolumeManagerError as e: 

2195 if e.code == LinstorVolumeManagerError.ERR_VOLUME_NOT_EXISTS: 

2196 raise xs_errors.XenError( 

2197 'VDIUnavailable', 

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

2199 ) 

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

2201 

2202 # -------------------------------------------------------------------------- 

2203 # Thin provisioning. 

2204 # -------------------------------------------------------------------------- 

2205 

2206 def _prepare_thin(self, attach): 

2207 if self.sr.is_master(): 

2208 if attach: 

2209 attach_thin( 

2210 self.session, self.sr._journaler, self._linstor, 

2211 self.sr.uuid, self.uuid 

2212 ) 

2213 else: 

2214 detach_thin( 

2215 self.session, self._linstor, self.sr.uuid, self.uuid 

2216 ) 

2217 else: 

2218 fn = 'attach' if attach else 'detach' 

2219 

2220 master = util.get_master_ref(self.session) 

2221 

2222 args = { 

2223 'groupName': self.sr._group_name, 

2224 'srUuid': self.sr.uuid, 

2225 'vdiUuid': self.uuid 

2226 } 

2227 

2228 try: 

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

2230 except Exception: 

2231 if fn != 'detach': 

2232 raise 

2233 

2234 # Reload size attrs after inflate or deflate! 

2235 self._load_this() 

2236 self.sr._update_physical_size() 

2237 

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

2239 self.session.xenapi.VDI.set_physical_utilisation( 

2240 vdi_ref, str(self.utilisation) 

2241 ) 

2242 

2243 self.session.xenapi.SR.set_physical_utilisation( 

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

2245 ) 

2246 

2247 # -------------------------------------------------------------------------- 

2248 # Generic helpers. 

2249 # -------------------------------------------------------------------------- 

2250 

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

2252 self.vdi_type = vdi_type 

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

2254 

2255 def _determine_type_and_path(self): 

2256 """ 

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

2258 """ 

2259 

2260 if self.sr._all_volume_metadata_cache: 

2261 # We are currently loading all volumes. 

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

2263 if not volume_metadata: 

2264 raise xs_errors.XenError( 

2265 'VDIUnavailable', 

2266 opterr='failed to get metadata' 

2267 ) 

2268 else: 

2269 # Simple load. 

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

2271 

2272 # Set type and path. 

2273 vdi_type = volume_metadata.get(VDI_TYPE_TAG) 

2274 if not vdi_type: 

2275 raise xs_errors.XenError( 

2276 'VDIUnavailable', 

2277 opterr='failed to get vdi_type in metadata' 

2278 ) 

2279 self._set_type(vdi_type) 

2280 

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

2282 

2283 def _update_device_name(self, device_name): 

2284 self._device_name = device_name 

2285 

2286 # Mark path of VDI parent class. 

2287 if device_name: 

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

2289 else: 

2290 self.path = None 

2291 

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

2293 """ 

2294 Snapshot self and return the snapshot VDI object. 

2295 """ 

2296 

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

2298 snap_path = self._linstor.shallow_clone_volume( 

2299 self.uuid, snap_uuid, persistent=False 

2300 ) 

2301 

2302 # 2. Write the snapshot content. 

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

2304 self.linstorcowutil.snapshot( 

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

2306 ) 

2307 

2308 # 3. Get snapshot parent. 

2309 snap_parent = self.linstorcowutil.get_parent(snap_uuid) 

2310 

2311 # 4. Update metadata. 

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

2313 volume_metadata = { 

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

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

2316 IS_A_SNAPSHOT_TAG: bool(snap_of_uuid), 

2317 SNAPSHOT_OF_TAG: snap_of_uuid, 

2318 SNAPSHOT_TIME_TAG: '', 

2319 TYPE_TAG: self.ty, 

2320 VDI_TYPE_TAG: snap_vdi_type, 

2321 READ_ONLY_TAG: False, 

2322 METADATA_OF_POOL_TAG: '' 

2323 } 

2324 self._linstor.set_volume_metadata(snap_uuid, volume_metadata) 

2325 

2326 # 5. Set size. 

2327 snap_vdi = LinstorVDI(self.sr, snap_uuid) 

2328 if not snap_vdi._exists: 

2329 raise xs_errors.XenError('VDISnapshot') 

2330 

2331 volume_info = self._linstor.get_volume_info(snap_uuid) 

2332 

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

2334 snap_vdi.utilisation = volume_info.allocated_size 

2335 

2336 # 6. Update sm config. 

2337 snap_vdi.sm_config = {} 

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

2339 if snap_parent: 

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

2341 snap_vdi.parent = snap_parent 

2342 

2343 snap_vdi.label = self.label 

2344 snap_vdi.description = self.description 

2345 

2346 self._linstor.mark_volume_as_persistent(snap_uuid) 

2347 

2348 return snap_vdi 

2349 

2350 # -------------------------------------------------------------------------- 

2351 # Implement specific SR methods. 

2352 # -------------------------------------------------------------------------- 

2353 

2354 @override 

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

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

2357 volume_uuid = self._linstor.get_volume_uuid_from_device_path(oldpath) 

2358 self._linstor.update_volume_name(volume_uuid, newpath) 

2359 

2360 @override 

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

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

2363 # If cbt enabled, save file consistency state. 

2364 if cbtlog is not None: 

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

2366 consistency_state = False 

2367 else: 

2368 consistency_state = True 

2369 util.SMlog( 

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

2371 .format(consistency_state, vdi_uuid) 

2372 ) 

2373 else: 

2374 consistency_state = None 

2375 

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

2377 raise xs_errors.XenError('Unimplemented') 

2378 

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

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

2381 try: 

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

2383 finally: 

2384 self.disable_leaf_on_secondary(vdi_uuid, secondary=secondary) 

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

2386 

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

2388 util.SMlog( 

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

2390 .format(self.uuid, snap_type) 

2391 ) 

2392 

2393 # 1. Checks... 

2394 if self.hidden: 

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

2396 

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

2398 

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

2400 if depth == -1: 

2401 raise xs_errors.XenError( 

2402 'VDIUnavailable', 

2403 opterr='failed to get COW depth' 

2404 ) 

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

2406 raise xs_errors.XenError('SnapshotChainTooLong') 

2407 

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

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

2410 

2411 volume_path = self.path 

2412 if not util.pathexists(volume_path): 

2413 raise xs_errors.XenError( 

2414 'EIO', 

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

2416 ) 

2417 

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

2419 base_uuid = util.gen_uuid() 

2420 snap_uuid = None 

2421 

2422 if snap_type == VDI.SNAPSHOT_DOUBLE: 

2423 snap_uuid = util.gen_uuid() 

2424 

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

2426 

2427 active_uuid = self.uuid 

2428 self.sr._journaler.create( 

2429 LinstorJournaler.CLONE, active_uuid, clone_info 

2430 ) 

2431 

2432 try: 

2433 # 3. Self becomes the new base. 

2434 # The device path remains the same. 

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

2436 self.uuid = base_uuid 

2437 self.location = self.uuid 

2438 self.read_only = True 

2439 self.managed = False 

2440 

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

2442 active_vdi = self._create_snapshot(snap_vdi_type, active_uuid) 

2443 

2444 snap_vdi = None 

2445 if snap_type == VDI.SNAPSHOT_DOUBLE: 

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

2447 

2448 self.label = 'base copy' 

2449 self.description = '' 

2450 

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

2452 # in subsequent scans. 

2453 self._mark_hidden() 

2454 self._linstor.update_volume_metadata( 

2455 self.uuid, {READ_ONLY_TAG: True} 

2456 ) 

2457 

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

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

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

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

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

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

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

2465 active_vdi.sm_config[key] = sm_config[key] 

2466 

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

2468 # delete base if unused. 

2469 introduce_parent = True 

2470 try: 

2471 snap_parent = None 

2472 if snap_vdi: 

2473 snap_parent = snap_vdi.parent 

2474 

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

2476 snap_type == VDI.SNAPSHOT_SINGLE or 

2477 snap_type == VDI.SNAPSHOT_INTERNAL or 

2478 snap_parent != self.uuid 

2479 ): 

2480 util.SMlog( 

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

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

2483 ) 

2484 introduce_parent = False 

2485 self._linstor.destroy_volume(self.uuid) 

2486 except Exception as e: 

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

2488 pass 

2489 

2490 # 8. Introduce the new VDI records. 

2491 if snap_vdi: 

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

2493 # new snapshot disk. 

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

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

2496 # TODO: Maybe remove key_hash support. 

2497 if 'key_hash' in sm_config: 

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

2499 # If we have CBT enabled on the VDI, 

2500 # set CBT status for the new snapshot disk. 

2501 if cbtlog: 

2502 snap_vdi.cbt_enabled = True 

2503 

2504 if snap_vdi: 

2505 snap_vdi_ref = snap_vdi._db_introduce() 

2506 util.SMlog( 

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

2508 .format(snap_vdi_ref, snap_vdi.uuid) 

2509 ) 

2510 if introduce_parent: 

2511 base_vdi_ref = self._db_introduce() 

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

2513 util.SMlog( 

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

2515 .format(base_vdi_ref, self.uuid) 

2516 ) 

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

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

2519 NAME_DESCRIPTION_TAG: util.to_plain_string( 

2520 self.description 

2521 ), 

2522 READ_ONLY_TAG: True, 

2523 METADATA_OF_POOL_TAG: '' 

2524 }) 

2525 

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

2527 if snap_type == VDI.SNAPSHOT_DOUBLE and cbtlog: 

2528 try: 

2529 self._cbt_snapshot(snap_uuid, cbt_consistency) 

2530 except Exception: 

2531 # CBT operation failed. 

2532 # TODO: Implement me. 

2533 raise 

2534 

2535 if snap_type != VDI.SNAPSHOT_INTERNAL: 

2536 self.sr._update_stats(self.size) 

2537 

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

2539 ret_vdi = snap_vdi 

2540 if not ret_vdi: 

2541 ret_vdi = self 

2542 if not ret_vdi: 

2543 ret_vdi = active_vdi 

2544 

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

2546 self.session.xenapi.VDI.set_sm_config( 

2547 vdi_ref, active_vdi.sm_config 

2548 ) 

2549 except Exception as e: 

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

2551 try: 

2552 self.sr._handle_interrupted_clone( 

2553 active_uuid, clone_info, force_undo=True 

2554 ) 

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

2556 except Exception as clean_error: 

2557 util.SMlog( 

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

2559 .format(clean_error) 

2560 ) 

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

2562 

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

2564 

2565 return ret_vdi.get_params() 

2566 

2567 @staticmethod 

2568 def _start_persistent_http_server(volume_name): 

2569 pid_path = None 

2570 http_server = None 

2571 

2572 try: 

2573 if volume_name == HA_VOLUME_NAME: 

2574 port = '8076' 

2575 else: 

2576 port = '8077' 

2577 

2578 try: 

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

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

2581 # block indefinitely. 

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

2583 host_ip = util.get_this_host_address(session) 

2584 except: 

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

2586 host_ip, _ = get_ips_from_xha_config_file() 

2587 if not host_ip: 

2588 raise Exception( 

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

2590 ) 

2591 

2592 arguments = [ 

2593 'http-disk-server', 

2594 '--disk', 

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

2596 '--ip', 

2597 host_ip, 

2598 '--port', 

2599 port 

2600 ] 

2601 

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

2603 http_server = subprocess.Popen( 

2604 [FORK_LOG_DAEMON] + arguments, 

2605 stdout=subprocess.PIPE, 

2606 stderr=subprocess.STDOUT, 

2607 universal_newlines=True, 

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

2609 # touch the current one. 

2610 preexec_fn=os.setsid 

2611 ) 

2612 

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

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

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

2616 

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

2618 def is_ready(): 

2619 while http_server.poll() is None: 

2620 line = http_server.stdout.readline() 

2621 if reg_server_ready.search(line): 

2622 return True 

2623 return False 

2624 try: 

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

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

2627 except util.TimeoutException: 

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

2629 except Exception as e: 

2630 if pid_path: 

2631 try: 

2632 os.remove(pid_path) 

2633 except Exception: 

2634 pass 

2635 

2636 if http_server: 

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

2638 try: 

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

2640 except: 

2641 pass 

2642 

2643 raise xs_errors.XenError( 

2644 'VDIUnavailable', 

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

2646 ) 

2647 

2648 def _start_persistent_nbd_server(self, volume_name): 

2649 pid_path = None 

2650 nbd_path = None 

2651 nbd_server = None 

2652 

2653 try: 

2654 # We use a precomputed device size. 

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

2656 if volume_name == HA_VOLUME_NAME: 

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

2658 port = '8076' 

2659 device_size = 4 * 1024 * 1024 

2660 else: 

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

2662 port = '8077' 

2663 device_size = 256 * 1024 * 1024 

2664 

2665 try: 

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

2667 ips = util.get_host_addresses(session) 

2668 except Exception as e: 

2669 _, ips = get_ips_from_xha_config_file() 

2670 if not ips: 

2671 raise Exception( 

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

2673 ) 

2674 ips = ips.values() 

2675 

2676 arguments = [ 

2677 'nbd-http-server', 

2678 '--socket-path', 

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

2680 '--nbd-name', 

2681 volume_name, 

2682 '--urls', 

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

2684 '--device-size', 

2685 str(device_size) 

2686 ] 

2687 

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

2689 nbd_server = subprocess.Popen( 

2690 [FORK_LOG_DAEMON] + arguments, 

2691 stdout=subprocess.PIPE, 

2692 stderr=subprocess.STDOUT, 

2693 universal_newlines=True, 

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

2695 # touch the current one. 

2696 preexec_fn=os.setsid 

2697 ) 

2698 

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

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

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

2702 

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

2704 def get_nbd_path(): 

2705 while nbd_server.poll() is None: 

2706 line = nbd_server.stdout.readline() 

2707 match = reg_nbd_path.search(line) 

2708 if match: 

2709 return match.group(1) 

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

2711 try: 

2712 nbd_path = util.timeout_call(10, get_nbd_path) 

2713 if nbd_path is None: 

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

2715 except util.TimeoutException: 

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

2717 

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

2719 os.symlink(nbd_path, self.path) 

2720 except Exception as e: 

2721 if pid_path: 

2722 try: 

2723 os.remove(pid_path) 

2724 except Exception: 

2725 pass 

2726 

2727 if nbd_path: 

2728 try: 

2729 os.remove(nbd_path) 

2730 except Exception: 

2731 pass 

2732 

2733 if nbd_server: 

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

2735 try: 

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

2737 except: 

2738 pass 

2739 

2740 raise xs_errors.XenError( 

2741 'VDIUnavailable', 

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

2743 ) 

2744 

2745 @classmethod 

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

2747 try: 

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

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

2750 return 

2751 

2752 pid = None 

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

2754 try: 

2755 pid = int(pid_file.read()) 

2756 except Exception: 

2757 pass 

2758 

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

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

2761 try: 

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

2763 except Exception as e: 

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

2765 

2766 os.remove(path) 

2767 except: 

2768 pass 

2769 

2770 @classmethod 

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

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

2773 

2774 @classmethod 

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

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

2777 

2778 def _check_http_nbd_volume_name(self): 

2779 volume_name = self.path[14:] 

2780 if volume_name not in [ 

2781 HA_VOLUME_NAME, REDO_LOG_VOLUME_NAME 

2782 ]: 

2783 raise xs_errors.XenError( 

2784 'VDIUnavailable', 

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

2786 ) 

2787 return volume_name 

2788 

2789 def _attach_using_http_nbd(self): 

2790 volume_name = self._check_http_nbd_volume_name() 

2791 

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

2793 self._kill_persistent_nbd_server(volume_name) 

2794 self._kill_persistent_http_server(volume_name) 

2795 

2796 # 0. Fetch drbd path. 

2797 must_get_device_path = True 

2798 if not self.sr.is_master(): 

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

2800 try: 

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

2802 except Exception as e: 

2803 raise xs_errors.XenError( 

2804 'VDIUnavailable', 

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

2806 .format(self.uuid, e) 

2807 ) 

2808 

2809 hostname = socket.gethostname() 

2810 must_get_device_path = hostname in volume_info.diskful 

2811 

2812 drbd_path = None 

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

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

2815 # or diskful available to init HA. 

2816 # It also avoid this error in xensource.log 

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

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

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

2820 available = False 

2821 try: 

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

2823 available = util.pathexists(drbd_path) 

2824 except Exception: 

2825 pass 

2826 

2827 if not available: 

2828 raise xs_errors.XenError( 

2829 'VDIUnavailable', 

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

2831 ) 

2832 

2833 # 1. Prepare http-nbd folder. 

2834 try: 

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

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

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

2838 os.remove(self.path) 

2839 except OSError as e: 

2840 if e.errno != errno.EEXIST: 

2841 raise xs_errors.XenError( 

2842 'VDIUnavailable', 

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

2844 ) 

2845 

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

2847 http_service = None 

2848 if drbd_path: 

2849 assert(drbd_path in ( 

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

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

2852 )) 

2853 self._start_persistent_http_server(volume_name) 

2854 

2855 # 3. Start NBD server in all cases. 

2856 try: 

2857 self._start_persistent_nbd_server(volume_name) 

2858 except Exception as e: 

2859 if drbd_path: 

2860 self._kill_persistent_http_server(volume_name) 

2861 raise 

2862 

2863 self.attached = True 

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

2865 

2866 def _detach_using_http_nbd(self): 

2867 volume_name = self._check_http_nbd_volume_name() 

2868 self._kill_persistent_nbd_server(volume_name) 

2869 self._kill_persistent_http_server(volume_name) 

2870 

2871# ------------------------------------------------------------------------------ 

2872 

2873 

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

2875 def run(): 

2876 SRCommand.run(LinstorSR, DRIVER_INFO) 

2877 

2878 if not TRACE_PERFS: 

2879 run() 

2880 else: 

2881 util.make_profile('LinstorSR', run) 

2882else: 

2883 SR.registerSR(LinstorSR)