Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1#!/usr/bin/env python3 

2# 

3# Copyright (C) 2020 Vates SAS - ronan.abhamon@vates.fr 

4# 

5# This program is free software: you can redistribute it and/or modify 

6# it under the terms of the GNU General Public License as published by 

7# the Free Software Foundation, either version 3 of the License, or 

8# (at your option) any later version. 

9# This program is distributed in the hope that it will be useful, 

10# but WITHOUT ANY WARRANTY; without even the implied warranty of 

11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

12# GNU General Public License for more details. 

13# 

14# You should have received a copy of the GNU General Public License 

15# along with this program. If not, see <https://www.gnu.org/licenses/>. 

16 

17from sm_typing import Any, Optional, override 

18 

19from constants import CBTLOG_TAG 

20 

21try: 

22 from linstorcowutil import LinstorCowUtil, MultiLinstorCowUtil 

23 from linstorjournaler import LinstorJournaler 

24 from linstorvolumemanager import get_controller_uri 

25 from linstorvolumemanager import get_controller_node_name 

26 from linstorvolumemanager import LinstorVolumeManager 

27 from linstorvolumemanager import LinstorVolumeManagerError 

28 from linstorvolumemanager import DATABASE_VOLUME_NAME 

29 from linstorvolumemanager import PERSISTENT_PREFIX 

30 

31 LINSTOR_AVAILABLE = True 

32except ImportError: 

33 PERSISTENT_PREFIX = 'unknown' 

34 

35 LINSTOR_AVAILABLE = False 

36 

37import blktap2 

38import cleanup 

39import errno 

40import functools 

41import lock 

42import lvutil 

43import os 

44import re 

45import scsiutil 

46import signal 

47import socket 

48import SR 

49import SRCommand 

50import subprocess 

51import sys 

52import time 

53import traceback 

54import util 

55import VDI 

56import xml.etree.ElementTree as xml_parser 

57import xmlrpc.client 

58import xs_errors 

59 

60from cowutil import CowUtil, ImageFormat, getImageStringFromVdiType 

61from srmetadata import \ 

62 NAME_LABEL_TAG, NAME_DESCRIPTION_TAG, IS_A_SNAPSHOT_TAG, SNAPSHOT_OF_TAG, \ 

63 TYPE_TAG, VDI_TYPE_TAG, READ_ONLY_TAG, SNAPSHOT_TIME_TAG, \ 

64 METADATA_OF_POOL_TAG 

65from vditype import VdiType 

66 

67HIDDEN_TAG = 'hidden' 

68 

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

70 

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

72 

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

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

75# specific conditions: 

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

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

78USE_HTTP_NBD_SERVERS = True 

79 

80# Useful flag to trace calls using cProfile. 

81TRACE_PERFS = False 

82 

83# Enable/Disable COW key hash support. 

84USE_KEY_HASH = False 

85 

86# Special volumes. 

87HA_VOLUME_NAME = PERSISTENT_PREFIX + 'ha-statefile' 

88REDO_LOG_VOLUME_NAME = PERSISTENT_PREFIX + 'redo-log' 

89 

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

91 

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

93# 'VDI_CONFIG_CBT', 'SR_PROBE' 

94 

95CAPABILITIES = [ 

96 'ATOMIC_PAUSE', 

97 'SR_UPDATE', 

98 'VDI_CREATE', 

99 'VDI_DELETE', 

100 'VDI_UPDATE', 

101 'VDI_ATTACH', 

102 'VDI_DETACH', 

103 'VDI_ACTIVATE', 

104 'VDI_DEACTIVATE', 

105 'VDI_CLONE', 

106 'VDI_MIRROR', 

107 'VDI_RESIZE', 

108 'VDI_SNAPSHOT', 

109 'VDI_GENERATE_CONFIG' 

110] 

111 

112CONFIGURATION = [ 

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

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

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

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

117] 

118 

119DRIVER_INFO = { 

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

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

122 'vendor': 'Vates', 

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

124 'driver_version': '1.0', 

125 'required_api_version': '1.0', 

126 'capabilities': CAPABILITIES, 

127 'configuration': CONFIGURATION 

128} 

129 

130DRIVER_CONFIG = {'ATTACH_FROM_CONFIG_WITH_TAPDISK': False} 

131 

132OPS_EXCLUSIVE = [ 

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

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

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

136] 

137 

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

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

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

141 

142 

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

144 volume_metadata = linstor.get_volume_metadata(vdi_uuid) 

145 vdi_type = volume_metadata.get(VDI_TYPE_TAG) 

146 if not VdiType.isCowImage(vdi_type): 

147 return 

148 

149 device_path = linstor.get_device_path(vdi_uuid) 

150 

151 linstorcowutil = LinstorCowUtil(session, linstor, vdi_type) 

152 

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

154 # there is nothing to do. 

155 cow_size = linstorcowutil.compute_volume_size( 

156 linstorcowutil.get_size_virt(vdi_uuid) 

157 ) 

158 

159 volume_info = linstor.get_volume_info(vdi_uuid) 

160 volume_size = volume_info.virtual_size 

161 

162 if cow_size > volume_size: 

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

164 

165 

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

167 volume_metadata = linstor.get_volume_metadata(vdi_uuid) 

168 vdi_type = volume_metadata.get(VDI_TYPE_TAG) 

169 if not VdiType.isCowImage(vdi_type): 

170 return 

171 

172 def check_vbd_count(): 

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

174 vbds = session.xenapi.VBD.get_all_records_where( 

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

176 ) 

177 

178 num_plugged = 0 

179 for vbd_rec in vbds.values(): 

180 if vbd_rec['currently_attached']: 

181 num_plugged += 1 

182 if num_plugged > 1: 

183 raise xs_errors.XenError( 

184 'VDIUnavailable', 

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

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

187 ) 

188 

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

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

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

192 

193 device_path = linstor.get_device_path(vdi_uuid) 

194 linstorcowutil = LinstorCowUtil(session, linstor, vdi_type) 

195 new_volume_size = LinstorVolumeManager.round_up_volume_size( 

196 linstorcowutil.get_size_phys(vdi_uuid) 

197 ) 

198 

199 volume_info = linstor.get_volume_info(vdi_uuid) 

200 old_volume_size = volume_info.virtual_size 

201 linstorcowutil.deflate(device_path, new_volume_size, old_volume_size) 

202 

203 

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

205 # This function must always return without errors. 

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

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

208 try: 

209 detach_thin_impl(session, linstor, sr_uuid, vdi_uuid) 

210 except Exception as e: 

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

212 

213 

214def get_ips_from_xha_config_file(): 

215 ips = dict() 

216 host_id = None 

217 try: 

218 # Ensure there is no dirty read problem. 

219 # For example if the HA is reloaded. 

220 tree = util.retry( 

221 lambda: xml_parser.parse(XHA_CONFIG_PATH), 

222 maxretry=10, 

223 period=1 

224 ) 

225 except: 

226 return (None, ips) 

227 

228 def parse_host_nodes(ips, node): 

229 current_id = None 

230 current_ip = None 

231 

232 for sub_node in node: 

233 if sub_node.tag == 'IPaddress': 

234 current_ip = sub_node.text 

235 elif sub_node.tag == 'HostID': 

236 current_id = sub_node.text 

237 else: 

238 continue 

239 

240 if current_id and current_ip: 

241 ips[current_id] = current_ip 

242 return 

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

244 

245 def parse_common_config(ips, node): 

246 for sub_node in node: 

247 if sub_node.tag == 'host': 

248 parse_host_nodes(ips, sub_node) 

249 

250 def parse_local_config(ips, node): 

251 for sub_node in node: 

252 if sub_node.tag == 'localhost': 

253 for host_node in sub_node: 

254 if host_node.tag == 'HostID': 

255 return host_node.text 

256 

257 for node in tree.getroot(): 

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

259 parse_common_config(ips, node) 

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

261 host_id = parse_local_config(ips, node) 

262 else: 

263 continue 

264 

265 if ips and host_id: 

266 break 

267 

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

269 

270 

271def activate_lvm_group(group_name): 

272 path = group_name.split('/') 

273 assert path and len(path) <= 2 

274 try: 

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

276 except Exception as e: 

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

278 

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

280 

281# Usage example: 

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

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

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

285 

286 

287class LinstorSR(SR.SR): 

288 DRIVER_TYPE = 'linstor' 

289 

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

291 PROVISIONING_DEFAULT = 'thin' 

292 

293 MANAGER_PLUGIN = 'linstor-manager' 

294 

295 INIT_STATUS_NOT_SET = 0 

296 INIT_STATUS_IN_PROGRESS = 1 

297 INIT_STATUS_OK = 2 

298 INIT_STATUS_FAIL = 3 

299 

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

301 # SR methods. 

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

303 

304 _linstor: Optional["LinstorVolumeManager"] = None 

305 

306 @override 

307 @staticmethod 

308 def handles(type) -> bool: 

309 return type == LinstorSR.DRIVER_TYPE 

310 

311 def __init__(self, srcmd, sr_uuid): 

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

313 self._init_image_formats( 

314 preferred_image_formats=[ImageFormat.VHD], 

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

316 ) 

317 

318 @override 

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

320 if not LINSTOR_AVAILABLE: 

321 raise util.SMException( 

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

323 ) 

324 

325 # Check parameters. 

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

327 raise xs_errors.XenError('LinstorConfigGroupNameMissing') 

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

329 raise xs_errors.XenError('LinstorConfigRedundancyMissing') 

330 

331 self.driver_config = DRIVER_CONFIG 

332 

333 # Check provisioning config. 

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

335 if provisioning: 

336 if provisioning in self.PROVISIONING_TYPES: 

337 self._provisioning = provisioning 

338 else: 

339 raise xs_errors.XenError( 

340 'InvalidArg', 

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

342 self.PROVISIONING_TYPES 

343 ) 

344 ) 

345 else: 

346 self._provisioning = self.PROVISIONING_DEFAULT 

347 

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

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

350 util.strtobool(monitor_db_quorum) 

351 

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

353 # 'vdi_attach_from_config' command is executed. 

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

355 if self._has_session: 

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

357 else: 

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

359 

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

361 if provisioning in self.PROVISIONING_TYPES: 

362 self._provisioning = provisioning 

363 

364 # Define properties for SR parent class. 

365 self.ops_exclusive = OPS_EXCLUSIVE 

366 self.path = LinstorVolumeManager.DEV_ROOT_PATH 

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

368 self.sr_vditype = SR.DEFAULT_TAP 

369 

370 if self.cmd == 'sr_create': 

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

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

373 self._journaler = None 

374 

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

376 class LinstorProxy: 

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

378 self.sr = sr 

379 

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

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

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

383 

384 self._linstor_proxy = LinstorProxy(self) 

385 

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

387 

388 self._vdi_shared_time = 0 

389 

390 self._init_status = self.INIT_STATUS_NOT_SET 

391 

392 self._vdis_loaded = False 

393 self._all_volume_info_cache = None 

394 self._all_volume_metadata_cache = None 

395 self._multi_cowutil = None 

396 

397 # To remove in python 3.10. 

398 # Use directly @staticmethod instead. 

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

400 def _locked_load(method): 

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

402 self._init_status = self.INIT_STATUS_OK 

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

404 

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

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

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

408 activate_lvm_group(self._group_name) 

409 

410 if not self._has_session: 

411 if self.srcmd.cmd in ( 

412 'vdi_attach_from_config', 

413 'vdi_detach_from_config', 

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

415 # empty command. 

416 None 

417 ): 

418 def create_linstor(uri, attempt_count=30): 

419 self._linstor = LinstorVolumeManager( 

420 uri, 

421 self._group_name, 

422 logger=util.SMlog, 

423 attempt_count=attempt_count 

424 ) 

425 

426 controller_uri = get_controller_uri() 

427 if controller_uri: 

428 create_linstor(controller_uri) 

429 else: 

430 def connect(): 

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

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

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

434 controller_uri = 'linstor://' + ip 

435 try: 

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

437 create_linstor(controller_uri, attempt_count=0) 

438 return controller_uri 

439 except: 

440 pass 

441 

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

443 if not controller_uri: 

444 raise xs_errors.XenError( 

445 'SRUnavailable', 

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

447 ) 

448 

449 self._journaler = LinstorJournaler( 

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

451 ) 

452 

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

454 

455 if not self.is_master(): 

456 if self.cmd in [ 

457 'sr_create', 'sr_delete', 'sr_update', 'sr_probe', 

458 'sr_scan', 'vdi_create', 'vdi_delete', 'vdi_resize', 

459 'vdi_snapshot', 'vdi_clone' 

460 ]: 

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

462 raise xs_errors.XenError('LinstorMaster') 

463 

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

465 # the VDI before the LinstorJournaler/LinstorVolumeManager 

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

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

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

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

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

471 self._vdi_shared_time = time.time() 

472 

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

474 try: 

475 self._reconnect() 

476 except Exception as e: 

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

478 

479 if self._linstor: 

480 try: 

481 hosts = self._linstor.disconnected_hosts 

482 except Exception as e: 

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

484 

485 if hosts: 

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

487 

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

489 if ( 

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

491 self.cmd != 'vdi_create' 

492 ): 

493 self._linstor.ensure_volume_is_not_locked( 

494 self.srcmd.params['vdi_uuid'] 

495 ) 

496 

497 try: 

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

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

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

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

502 # 

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

504 # resourceless volumes. 

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

506 'vdi_attach', 'vdi_detach', 

507 'vdi_activate', 'vdi_deactivate', 

508 'vdi_epoch_begin', 'vdi_epoch_end', 

509 'vdi_update', 'vdi_destroy' 

510 ]: 

511 load_vdis = ( 

512 self.cmd == 'sr_scan' or 

513 self.cmd == 'sr_attach' 

514 ) or len( 

515 self._journaler.get_all(LinstorJournaler.INFLATE) 

516 ) or len( 

517 self._journaler.get_all(LinstorJournaler.CLONE) 

518 ) 

519 

520 if load_vdis: 

521 self._load_vdis() 

522 

523 self._linstor.remove_resourceless_volumes() 

524 

525 self._synchronize_metadata() 

526 except Exception as e: 

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

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

529 # from the XAPI database otherwise. 

530 raise e 

531 util.SMlog( 

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

533 ) 

534 util.SMlog(traceback.format_exc()) 

535 

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

537 

538 @functools.wraps(wrapped_method) 

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

540 if self._init_status in \ 

541 (self.INIT_STATUS_OK, self.INIT_STATUS_IN_PROGRESS): 

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

543 if self._init_status == self.INIT_STATUS_FAIL: 

544 util.SMlog( 

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

546 .format(method) 

547 ) 

548 else: 

549 try: 

550 self._init_status = self.INIT_STATUS_IN_PROGRESS 

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

552 except Exception: 

553 if self._init_status != self.INIT_STATUS_OK: 

554 self._init_status = self.INIT_STATUS_FAIL 

555 raise 

556 

557 return wrap 

558 

559 @override 

560 def cleanup(self) -> None: 

561 if self._vdi_shared_time: 

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

563 

564 @override 

565 @_locked_load 

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

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

568 

569 host_adresses = util.get_host_addresses(self.session) 

570 if self._redundancy > len(host_adresses): 

571 raise xs_errors.XenError( 

572 'LinstorSRCreate', 

573 opterr='Redundancy greater than host count' 

574 ) 

575 

576 xenapi = self.session.xenapi 

577 srs = xenapi.SR.get_all_records_where( 

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

579 ) 

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

581 

582 for sr in srs.values(): 

583 for pbd in sr['PBDs']: 

584 device_config = xenapi.PBD.get_device_config(pbd) 

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

586 if group_name and group_name == self._group_name: 

587 raise xs_errors.XenError( 

588 'LinstorSRCreate', 

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

590 xenapi.PBD.get_uuid(pbd) 

591 ) 

592 ) 

593 

594 if srs: 

595 raise xs_errors.XenError( 

596 'LinstorSRCreate', 

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

598 ) 

599 

600 online_hosts = util.get_enabled_hosts(self.session) 

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

602 raise xs_errors.XenError( 

603 'LinstorSRCreate', 

604 opterr='Not enough online hosts' 

605 ) 

606 

607 ips = {} 

608 for host_ref in online_hosts: 

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

610 hostname = record['hostname'] 

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

612 

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

614 raise xs_errors.XenError( 

615 'LinstorSRCreate', 

616 opterr='Multiple hosts with same hostname' 

617 ) 

618 

619 # Ensure ports are opened and LINSTOR satellites 

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

621 # must be stopped. 

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

623 

624 # Create SR. 

625 # Throw if the SR already exists. 

626 try: 

627 self._linstor = LinstorVolumeManager.create_sr( 

628 self._group_name, 

629 ips, 

630 self._redundancy, 

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

632 logger=util.SMlog 

633 ) 

634 

635 util.SMlog( 

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

637 ) 

638 self._update_drbd_reactor_on_all_hosts(enabled=True) 

639 except Exception as e: 

640 if not self._linstor: 

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

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

643 

644 try: 

645 self._linstor.destroy() 

646 except Exception as e2: 

647 util.SMlog( 

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

649 .format(e2) 

650 ) 

651 raise e 

652 

653 @override 

654 @_locked_load 

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

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

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

658 

659 assert self._linstor 

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

661 raise xs_errors.XenError('SRNotEmpty') 

662 

663 node_name = get_controller_node_name() 

664 if not node_name: 

665 raise xs_errors.XenError( 

666 'LinstorSRDelete', 

667 opterr='Cannot get controller node name' 

668 ) 

669 

670 host_ref = None 

671 if node_name == 'localhost': 

672 host_ref = util.get_this_host_ref(self.session) 

673 else: 

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

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

676 if r_name == node_name: 

677 host_ref = slave 

678 break 

679 

680 if not host_ref: 

681 raise xs_errors.XenError( 

682 'LinstorSRDelete', 

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

684 node_name 

685 ) 

686 ) 

687 

688 try: 

689 if self._monitor_db_quorum: 

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

691 self._update_drbd_reactor_on_all_hosts( 

692 controller_node_name=node_name, enabled=False 

693 ) 

694 

695 args = { 

696 'groupName': self._group_name, 

697 } 

698 self._exec_manager_command( 

699 host_ref, 'destroy', args, 'LinstorSRDelete' 

700 ) 

701 except Exception as e: 

702 try: 

703 self._update_drbd_reactor_on_all_hosts( 

704 controller_node_name=node_name, enabled=True 

705 ) 

706 if self._monitor_db_quorum: 

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

708 except Exception as e2: 

709 util.SMlog( 

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

711 .format(e2) 

712 ) 

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

714 raise xs_errors.XenError( 

715 'LinstorSRDelete', 

716 opterr=str(e) 

717 ) 

718 

719 lock.Lock.cleanupAll(self.uuid) 

720 

721 @override 

722 @_locked_load 

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

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

725 

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

727 if not self._linstor: 

728 raise xs_errors.XenError( 

729 'SRUnavailable', 

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

731 ) 

732 

733 self._update_stats(0) 

734 

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

736 xenapi = self.session.xenapi 

737 self._linstor.metadata = { 

738 NAME_LABEL_TAG: util.to_plain_string( 

739 xenapi.SR.get_name_label(self.sr_ref) 

740 ), 

741 NAME_DESCRIPTION_TAG: util.to_plain_string( 

742 xenapi.SR.get_name_description(self.sr_ref) 

743 ) 

744 } 

745 

746 @override 

747 @_locked_load 

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

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

750 

751 if not self._linstor: 

752 raise xs_errors.XenError( 

753 'SRUnavailable', 

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

755 ) 

756 

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

758 self._linstor.set_drbd_ha_properties(DATABASE_VOLUME_NAME) 

759 

760 @override 

761 @_locked_load 

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

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

764 cleanup.abort(self.uuid) 

765 

766 @override 

767 @_locked_load 

768 def probe(self) -> str: 

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

770 # TODO 

771 return '' 

772 

773 @override 

774 @_locked_load 

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

776 if self._init_status == self.INIT_STATUS_FAIL: 

777 return 

778 

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

780 if not self._linstor: 

781 raise xs_errors.XenError( 

782 'SRUnavailable', 

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

784 ) 

785 

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

787 # are loaded. 

788 self._load_vdis() 

789 self._update_physical_size() 

790 

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

792 if self.vdis[vdi_uuid].deleted: 

793 del self.vdis[vdi_uuid] 

794 

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

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

797 try: 

798 self._linstor.get_database_path() 

799 except Exception as e: 

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

801 # VDIs in the XAPI database... 

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

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

804 ): 

805 raise xs_errors.XenError( 

806 'SRUnavailable', 

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

808 ) 

809 

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

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

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

813 self._kick_gc() 

814 

815 def is_master(self): 

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

817 if 'SRmaster' not in self.dconf: 

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

819 else: 

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

821 

822 return self._is_master 

823 

824 @override 

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

826 self.database_backup("auto", delay=3600) 

827 

828 

829 @override 

830 @_locked_load 

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

832 return LinstorVDI(self, uuid) 

833 

834 # To remove in python 3.10 

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

836 _locked_load = staticmethod(_locked_load) 

837 

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

839 # Lock. 

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

841 

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

843 master = util.get_master_ref(self.session) 

844 

845 command = 'lockVdi' 

846 args = { 

847 'groupName': self._group_name, 

848 'srUuid': self.uuid, 

849 'vdiUuid': vdi_uuid, 

850 'locked': str(locked) 

851 } 

852 

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

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

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

856 # 

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

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

859 # to implement without impacting performance. 

860 if not locked: 

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

862 timeout = LinstorVolumeManager.LOCKED_EXPIRATION_DELAY * 0.7 

863 if elapsed_time >= timeout: 

864 util.SMlog( 

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

866 .format(vdi_uuid) 

867 ) 

868 return 

869 

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

871 

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

873 # Network. 

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

875 

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

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

878 host_uuid = host_rec['uuid'] 

879 

880 try: 

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

882 host_ref, self.MANAGER_PLUGIN, command, args 

883 ) 

884 except Exception as e: 

885 util.SMlog( 

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

887 host_uuid, self.MANAGER_PLUGIN, command, args 

888 ) 

889 ) 

890 raise e 

891 

892 util.SMlog( 

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

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

895 ) 

896 ) 

897 if ret == 'False': 

898 raise xs_errors.XenError( 

899 error, 

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

901 ) 

902 

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

904 self._exec_manager_command( 

905 host, 

906 'prepareSr' if enabled else 'releaseSr', 

907 {'groupName': group_name}, 

908 'SRUnavailable' 

909 ) 

910 

911 def _prepare_sr_on_all_hosts(self, group_name, enabled): 

912 master = util.get_master_ref(self.session) 

913 self._prepare_sr(master, group_name, enabled) 

914 

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

916 self._prepare_sr(slave, group_name, enabled) 

917 

918 def _update_drbd_reactor(self, host, enabled): 

919 self._exec_manager_command( 

920 host, 

921 'updateDrbdReactor', 

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

923 'SRUnavailable' 

924 ) 

925 

926 def _update_drbd_reactor_on_all_hosts( 

927 self, enabled, controller_node_name=None 

928 ): 

929 if controller_node_name == 'localhost': 

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

931 util.get_this_host_ref(self.session) 

932 )['hostname'] 

933 assert controller_node_name 

934 assert controller_node_name != 'localhost' 

935 

936 controller_host = None 

937 secondary_hosts = [] 

938 

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

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

941 hostname = host_rec['hostname'] 

942 if controller_node_name == hostname: 

943 controller_host = host_ref 

944 else: 

945 secondary_hosts.append((host_ref, hostname)) 

946 

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

948 if controller_node_name and not controller_host: 

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

950 controller_node_name 

951 )) 

952 

953 if enabled and controller_host: 

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

955 action_name, controller_node_name 

956 )) 

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

958 # node name first. 

959 self._update_drbd_reactor(controller_host, enabled) 

960 

961 for host_ref, hostname in secondary_hosts: 

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

963 action_name, hostname 

964 )) 

965 self._update_drbd_reactor(host_ref, enabled) 

966 

967 if not enabled and controller_host: 

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

969 action_name, controller_node_name 

970 )) 

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

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

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

974 self._update_drbd_reactor(controller_host, enabled) 

975 

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

977 # Metadata. 

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

979 

980 def _synchronize_metadata_and_xapi(self): 

981 try: 

982 # First synch SR parameters. 

983 self.update(self.uuid) 

984 

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

986 xenapi = self.session.xenapi 

987 volumes_metadata = self._linstor.get_volumes_with_metadata() 

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

989 try: 

990 vdi_ref = xenapi.VDI.get_by_uuid(vdi_uuid) 

991 except Exception: 

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

993 continue 

994 

995 label = util.to_plain_string( 

996 xenapi.VDI.get_name_label(vdi_ref) 

997 ) 

998 description = util.to_plain_string( 

999 xenapi.VDI.get_name_description(vdi_ref) 

1000 ) 

1001 

1002 if ( 

1003 volume_metadata.get(NAME_LABEL_TAG) != label or 

1004 volume_metadata.get(NAME_DESCRIPTION_TAG) != description 

1005 ): 

1006 self._linstor.update_volume_metadata(vdi_uuid, { 

1007 NAME_LABEL_TAG: label, 

1008 NAME_DESCRIPTION_TAG: description 

1009 }) 

1010 except Exception as e: 

1011 raise xs_errors.XenError( 

1012 'MetadataError', 

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

1014 ) 

1015 

1016 def _synchronize_metadata(self): 

1017 if not self.is_master(): 

1018 return 

1019 

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

1021 if self.cmd == 'sr_attach': 

1022 try: 

1023 util.SMlog( 

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

1025 ) 

1026 self._synchronize_metadata_and_xapi() 

1027 except Exception as e: 

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

1029 

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

1031 # Stats. 

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

1033 

1034 def _update_stats(self, virt_alloc_delta): 

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

1036 self.sr_ref 

1037 )) 

1038 

1039 # Update size attributes of the SR parent class. 

1040 self.virtual_allocation = valloc + virt_alloc_delta 

1041 

1042 self._update_physical_size() 

1043 

1044 # Notify SR parent class. 

1045 self._db_update() 

1046 

1047 def _update_physical_size(self): 

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

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

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

1051 self.physical_size = min_physical_size * pool_count // \ 

1052 self._linstor.redundancy 

1053 

1054 self.physical_utilisation = self._linstor.allocated_volume_size 

1055 

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

1057 # VDIs. 

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

1059 

1060 def _load_vdis(self): 

1061 if self._vdis_loaded: 

1062 return 

1063 

1064 assert self.is_master() 

1065 

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

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

1068 # enjoy it with a few lines. 

1069 self._create_linstor_cache() 

1070 self._load_vdis_ex() 

1071 self._destroy_linstor_cache() 

1072 

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

1074 self._vdis_loaded = True 

1075 

1076 self._undo_all_journal_transactions() 

1077 

1078 def _load_vdis_ex(self): 

1079 # 1. Get existing VDIs in XAPI. 

1080 xenapi = self.session.xenapi 

1081 xapi_vdi_uuids = set() 

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

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

1084 

1085 # 2. Get volumes info. 

1086 all_volume_info = self._all_volume_info_cache 

1087 volumes_metadata = self._all_volume_metadata_cache 

1088 

1089 # 3. Get CBT vdis. 

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

1091 cbt_vdis = set() 

1092 for volume_metadata in volumes_metadata.values(): 

1093 cbt_uuid = volume_metadata.get(CBTLOG_TAG) 

1094 if cbt_uuid: 

1095 cbt_vdis.add(cbt_uuid) 

1096 

1097 introduce = False 

1098 

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

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

1101 has_clone_entries = list(self._journaler.get_all( 

1102 LinstorJournaler.CLONE 

1103 ).items()) 

1104 

1105 if has_clone_entries: 

1106 util.SMlog( 

1107 'Cannot introduce VDIs during scan because it exists ' 

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

1109 ) 

1110 else: 

1111 introduce = True 

1112 

1113 # 4. Now process all volume info. 

1114 vdi_to_snaps = {} 

1115 vdi_uuids = [] 

1116 

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

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

1119 continue 

1120 

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

1122 if vdi_uuid not in xapi_vdi_uuids: 

1123 if not introduce: 

1124 continue 

1125 

1126 if vdi_uuid.startswith('DELETED_'): 

1127 continue 

1128 

1129 volume_metadata = volumes_metadata.get(vdi_uuid) 

1130 if not volume_metadata: 

1131 util.SMlog( 

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

1133 .format(vdi_uuid) 

1134 ) 

1135 continue 

1136 

1137 util.SMlog( 

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

1139 'LINSTOR and not in XAPI...' 

1140 .format(vdi_uuid) 

1141 ) 

1142 

1143 try: 

1144 self._linstor.get_device_path(vdi_uuid) 

1145 except Exception as e: 

1146 util.SMlog( 

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

1148 .format(vdi_uuid, e) 

1149 ) 

1150 continue 

1151 

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

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

1154 vdi_type = volume_metadata.get(VDI_TYPE_TAG) 

1155 

1156 if not vdi_type: 

1157 util.SMlog( 

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

1159 'without vdi_type' 

1160 ) 

1161 continue 

1162 

1163 sm_config = { 

1164 'vdi_type': vdi_type 

1165 } 

1166 

1167 if not VdiType.isCowImage(vdi_type): 

1168 managed = not volume_metadata.get(HIDDEN_TAG) 

1169 else: 

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

1171 managed = not image_info.hidden 

1172 if image_info.parentUuid: 

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

1174 

1175 util.SMlog( 

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

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

1178 name_label, 

1179 volume_info.virtual_size, 

1180 volume_info.allocated_size 

1181 ) 

1182 ) 

1183 

1184 vdi_ref = xenapi.VDI.db_introduce( 

1185 vdi_uuid, 

1186 name_label, 

1187 volume_metadata.get(NAME_DESCRIPTION_TAG) or '', 

1188 self.sr_ref, 

1189 type, 

1190 False, # sharable 

1191 bool(volume_metadata.get(READ_ONLY_TAG)), 

1192 {}, # other_config 

1193 vdi_uuid, # location 

1194 {}, # xenstore_data 

1195 sm_config, 

1196 managed, 

1197 str(volume_info.virtual_size), 

1198 str(volume_info.allocated_size) 

1199 ) 

1200 

1201 is_a_snapshot = volume_metadata.get(IS_A_SNAPSHOT_TAG) 

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

1203 if is_a_snapshot: 

1204 xenapi.VDI.set_snapshot_time( 

1205 vdi_ref, 

1206 xmlrpc.client.DateTime( 

1207 volume_metadata[SNAPSHOT_TIME_TAG] or 

1208 '19700101T00:00:00Z' 

1209 ) 

1210 ) 

1211 

1212 snap_uuid = volume_metadata[SNAPSHOT_OF_TAG] 

1213 if snap_uuid in vdi_to_snaps: 

1214 vdi_to_snaps[snap_uuid].append(vdi_uuid) 

1215 else: 

1216 vdi_to_snaps[snap_uuid] = [vdi_uuid] 

1217 

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

1219 vdi_uuids.append(vdi_uuid) 

1220 

1221 # 5. Create VDIs. 

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

1223 

1224 def load_vdi(vdi_uuid, multi_cowutil): 

1225 vdi = self.vdi(vdi_uuid) 

1226 

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

1228 cowutil_instance = multi_cowutil.get_local_cowutil(vdi.vdi_type) 

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

1230 

1231 return vdi 

1232 

1233 try: 

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

1235 finally: 

1236 multi_cowutil = self._multi_cowutil 

1237 self._multi_cowutil = None 

1238 del multi_cowutil 

1239 

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

1241 # or already in XAPI. 

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

1243 volume_metadata = volumes_metadata.get(vdi.uuid) 

1244 cbt_uuid = volume_metadata.get(CBTLOG_TAG) 

1245 if cbt_uuid in cbt_vdis: 

1246 vdi_ref = xenapi.VDI.get_by_uuid(vdi_uuid) 

1247 xenapi.VDI.set_cbt_enabled(vdi_ref, True) 

1248 # For existing VDIs, update local state too. 

1249 # Scan in base class SR updates existing VDIs 

1250 # again based on local states. 

1251 self.vdis[vdi_uuid].cbt_enabled = True 

1252 cbt_vdis.remove(cbt_uuid) 

1253 

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

1255 for src_uuid in vdi_to_snaps: 

1256 try: 

1257 src_ref = xenapi.VDI.get_by_uuid(src_uuid) 

1258 except Exception: 

1259 # The source VDI no longer exists, continue. 

1260 continue 

1261 

1262 for snap_uuid in vdi_to_snaps[src_uuid]: 

1263 try: 

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

1265 snap_ref = xenapi.VDI.get_by_uuid(snap_uuid) 

1266 xenapi.VDI.set_snapshot_of(snap_ref, src_ref) 

1267 except Exception as e: 

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

1269 

1270 # TODO: Check correctly how to use CBT. 

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

1272 

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

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

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

1276 for cbt_uuid in cbt_vdis: 

1277 new_vdi = self.vdi(cbt_uuid) 

1278 new_vdi.ty = 'cbt_metadata' 

1279 new_vdi.cbt_enabled = True 

1280 self.vdis[cbt_uuid] = new_vdi 

1281 

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

1283 self.virtual_allocation = 0 

1284 

1285 # 10. Build geneology. 

1286 geneology = {} 

1287 

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

1289 if vdi.parent: 

1290 if vdi.parent in self.vdis: 

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

1292 if vdi.parent in geneology: 

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

1294 else: 

1295 geneology[vdi.parent] = [vdi_uuid] 

1296 if not vdi.hidden: 

1297 self.virtual_allocation += vdi.size 

1298 

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

1300 # will be GC'ed. 

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

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

1303 util.SMlog( 

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

1305 ) 

1306 del self.vdis[vdi_uuid] 

1307 

1308 # -------------------------------------------------------------------------- 

1309 # Journals. 

1310 # -------------------------------------------------------------------------- 

1311 

1312 def _get_vdi_path_and_parent(self, vdi_uuid, volume_name): 

1313 try: 

1314 device_path = self._linstor.build_device_path(volume_name) 

1315 if not util.pathexists(device_path): 

1316 return (None, None) 

1317 

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

1319 volume_metadata = self._linstor.get_volume_metadata(vdi_uuid) 

1320 vdi_type = volume_metadata[VDI_TYPE_TAG] 

1321 if not VdiType.isCowImage(vdi_type): 

1322 return (device_path, None) 

1323 

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

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

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

1327 return (None, None) 

1328 

1329 image_info = linstorcowutil.get_info(vdi_uuid) 

1330 if image_info: 

1331 return (device_path, image_info.parentUuid) 

1332 except Exception as e: 

1333 util.SMlog( 

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

1335 .format(e) 

1336 ) 

1337 return (None, None) 

1338 

1339 def _undo_all_journal_transactions(self): 

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

1341 self.lock.acquire() 

1342 try: 

1343 self._handle_interrupted_inflate_ops() 

1344 self._handle_interrupted_clone_ops() 

1345 pass 

1346 finally: 

1347 self.lock.release() 

1348 

1349 def _handle_interrupted_inflate_ops(self): 

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

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

1352 self._handle_interrupted_inflate(vdi_uuid, old_size) 

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

1354 

1355 def _handle_interrupted_clone_ops(self): 

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

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

1358 self._handle_interrupted_clone(vdi_uuid, old_size) 

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

1360 

1361 def _handle_interrupted_inflate(self, vdi_uuid, old_size): 

1362 util.SMlog( 

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

1364 .format(vdi_uuid, old_size) 

1365 ) 

1366 

1367 vdi = self.vdis.get(vdi_uuid) 

1368 if not vdi: 

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

1370 return 

1371 

1372 assert not self._all_volume_info_cache 

1373 volume_info = self._linstor.get_volume_info(vdi_uuid) 

1374 

1375 current_size = volume_info.virtual_size 

1376 assert current_size > 0 

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

1378 

1379 def _handle_interrupted_clone( 

1380 self, vdi_uuid, clone_info, force_undo=False 

1381 ): 

1382 util.SMlog( 

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

1384 .format(vdi_uuid, clone_info) 

1385 ) 

1386 

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

1388 

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

1390 volume_names = self._linstor.get_volumes_with_name() 

1391 

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

1393 if base_uuid not in volume_names: 

1394 if vdi_uuid in volume_names: 

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

1396 return 

1397 raise util.SMException( 

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

1399 .format(base_uuid, vdi_uuid) 

1400 ) 

1401 

1402 if force_undo: 

1403 util.SMlog('Explicit revert') 

1404 self._undo_clone( 

1405 volume_names, vdi_uuid, base_uuid, snap_uuid 

1406 ) 

1407 return 

1408 

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

1410 if vdi_uuid not in volume_names or \ 

1411 (snap_uuid and snap_uuid not in volume_names): 

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

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

1414 return 

1415 

1416 vdi_path, vdi_parent_uuid = self._get_vdi_path_and_parent( 

1417 vdi_uuid, volume_names[vdi_uuid] 

1418 ) 

1419 snap_path, snap_parent_uuid = self._get_vdi_path_and_parent( 

1420 snap_uuid, volume_names[snap_uuid] 

1421 ) 

1422 

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

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

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

1426 return 

1427 

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

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

1430 

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

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

1433 base_metadata = self._linstor.get_volume_metadata(base_uuid) 

1434 base_type = base_metadata[VDI_TYPE_TAG] 

1435 

1436 if not util.pathexists(base_path): 

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

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

1439 return 

1440 

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

1442 

1443 # Un-hide the parent. 

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

1445 if VdiType.isCowImage(base_type): 

1446 image_info = linstorcowutil.get_info(base_uuid, False) 

1447 if image_info.hidden: 

1448 linstorcowutil.set_hidden(base_path, False) 

1449 elif base_metadata.get(HIDDEN_TAG): 

1450 self._linstor.update_volume_metadata( 

1451 base_uuid, {HIDDEN_TAG: False} 

1452 ) 

1453 

1454 # Remove the child nodes. 

1455 if snap_uuid and snap_uuid in volume_names: 

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

1457 

1458 try: 

1459 self._linstor.destroy_volume(snap_uuid) 

1460 except Exception as e: 

1461 util.SMlog( 

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

1463 .format(snap_uuid, e) 

1464 ) 

1465 

1466 if vdi_uuid in volume_names: 

1467 try: 

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

1469 self._linstor.destroy_volume(vdi_uuid) 

1470 except Exception as e: 

1471 util.SMlog( 

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

1473 .format(vdi_uuid, e) 

1474 ) 

1475 # We can get an exception like this: 

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

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

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

1479 # UUID of this bad VDI before. 

1480 self._linstor.update_volume_uuid( 

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

1482 ) 

1483 

1484 # Rename! 

1485 self._linstor.update_volume_uuid(base_uuid, vdi_uuid) 

1486 

1487 # Inflate to the right size. 

1488 if VdiType.isCowImage(base_type): 

1489 vdi = self.vdi(vdi_uuid) 

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

1491 volume_size = linstorcowutil.compute_volume_size(vdi.size) 

1492 linstorcowutil.inflate( 

1493 self._journaler, vdi_uuid, vdi.path, 

1494 volume_size, vdi.capacity 

1495 ) 

1496 self.vdis[vdi_uuid] = vdi 

1497 

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

1499 # flag to facilitate vm deactivate. 

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

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

1502 

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

1504 

1505 # -------------------------------------------------------------------------- 

1506 # Cache. 

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

1508 

1509 def _create_linstor_cache(self): 

1510 reconnect = False 

1511 

1512 def create_cache(): 

1513 nonlocal reconnect 

1514 try: 

1515 if reconnect: 

1516 self._reconnect() 

1517 return self._linstor.get_volumes_with_info() 

1518 except Exception as e: 

1519 reconnect = True 

1520 raise e 

1521 

1522 self._all_volume_metadata_cache = \ 

1523 self._linstor.get_volumes_with_metadata() 

1524 self._all_volume_info_cache = util.retry( 

1525 create_cache, 

1526 maxretry=10, 

1527 period=3 

1528 ) 

1529 

1530 def _destroy_linstor_cache(self): 

1531 self._all_volume_info_cache = None 

1532 self._all_volume_metadata_cache = None 

1533 

1534 # -------------------------------------------------------------------------- 

1535 # Misc. 

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

1537 

1538 def _reconnect(self): 

1539 controller_uri = get_controller_uri() 

1540 

1541 self._journaler = LinstorJournaler( 

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

1543 ) 

1544 

1545 # Try to open SR if exists. 

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

1547 # we are trying to execute an exclusive operation. 

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

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

1550 # the SR is locked. 

1551 self._linstor = LinstorVolumeManager( 

1552 controller_uri, 

1553 self._group_name, 

1554 repair=( 

1555 self.is_master() and 

1556 self.srcmd.cmd in self.ops_exclusive 

1557 ), 

1558 logger=util.SMlog 

1559 ) 

1560 

1561 def _ensure_space_available(self, amount_needed): 

1562 space_available = self._linstor.max_volume_size_allowed 

1563 if (space_available < amount_needed): 

1564 util.SMlog( 

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

1566 space_available, amount_needed 

1567 ) 

1568 ) 

1569 raise xs_errors.XenError('SRNoSpace') 

1570 

1571 def _kick_gc(self): 

1572 util.SMlog('Kicking GC') 

1573 cleanup.start_gc_service(self.uuid) 

1574 

1575 def database_backup(self, name="", *, delay=0): 

1576 if not self._linstor: 

1577 self._reconnect() 

1578 self._linstor.database_backup(name, delay=delay) 

1579 

1580# ============================================================================== 

1581# LinstorSr VDI 

1582# ============================================================================== 

1583 

1584 

1585class LinstorVDI(VDI.VDI): 

1586 # -------------------------------------------------------------------------- 

1587 # VDI methods. 

1588 # -------------------------------------------------------------------------- 

1589 

1590 @override 

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

1592 self._lock = self.sr.lock 

1593 self._exists = True 

1594 self._linstor = self.sr._linstor 

1595 

1596 # Update hidden parent property. 

1597 self.hidden = False 

1598 

1599 def raise_bad_load(e): 

1600 util.SMlog( 

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

1602 ) 

1603 util.SMlog(traceback.format_exc()) 

1604 raise xs_errors.XenError( 

1605 'VDIUnavailable', 

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

1607 ) 

1608 

1609 # Try to load VDI. 

1610 try: 

1611 if ( 

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

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

1614 ): 

1615 self._set_type(VdiType.RAW) 

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

1617 else: 

1618 self._determine_type_and_path() 

1619 self._load_this() 

1620 

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

1622 self.uuid, self.path, self.hidden 

1623 )) 

1624 except LinstorVolumeManagerError as e: 

1625 # 1. It may be a VDI deletion. 

1626 if e.code == LinstorVolumeManagerError.ERR_VOLUME_NOT_EXISTS: 

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

1628 self.deleted = True 

1629 return 

1630 

1631 # 2. Or maybe a creation. 

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

1633 image_format = None 

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

1635 

1636 self._exists = False 

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

1638 if vdi_sm_config: 

1639 image_format = self.sr.read_config_image_format(vdi_sm_config) 

1640 

1641 if not image_format: 

1642 image_format = self.sr.preferred_image_formats[0] 

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

1644 

1645 if VdiType.isCowImage(self.vdi_type): 

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

1647 

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

1649 self._update_device_name(None) 

1650 return 

1651 raise_bad_load(e) 

1652 except Exception as e: 

1653 raise_bad_load(e) 

1654 

1655 @override 

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

1657 # Usage example: 

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

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

1660 

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

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

1663 if self._exists: 

1664 raise xs_errors.XenError('VDIExists') 

1665 

1666 assert self.uuid 

1667 assert self.ty 

1668 assert self.vdi_type 

1669 

1670 # 2. Compute size and check space available. 

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

1672 volume_size = self.linstorcowutil.compute_volume_size(size) 

1673 util.SMlog( 

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

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

1676 ) 

1677 self.sr._ensure_space_available(volume_size) 

1678 

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

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

1681 

1682 # 4. Create! 

1683 failed = False 

1684 try: 

1685 volume_name = None 

1686 if self.ty == 'ha_statefile': 

1687 volume_name = HA_VOLUME_NAME 

1688 elif self.ty == 'redo_log': 

1689 volume_name = REDO_LOG_VOLUME_NAME 

1690 

1691 self._linstor.create_volume( 

1692 self.uuid, 

1693 volume_size, 

1694 persistent=False, 

1695 volume_name=volume_name, 

1696 high_availability=volume_name is not None 

1697 ) 

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

1699 

1700 self._update_device_name(volume_info.name) 

1701 

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

1703 self.size = volume_info.virtual_size 

1704 else: 

1705 self.linstorcowutil.create( 

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

1707 ) 

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

1709 

1710 if self._key_hash: 

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

1712 

1713 # Because cowutil commands modify the volume data, 

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

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

1716 

1717 volume_metadata = { 

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

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

1720 IS_A_SNAPSHOT_TAG: False, 

1721 SNAPSHOT_OF_TAG: '', 

1722 SNAPSHOT_TIME_TAG: '', 

1723 TYPE_TAG: self.ty, 

1724 VDI_TYPE_TAG: self.vdi_type, 

1725 READ_ONLY_TAG: bool(self.read_only), 

1726 METADATA_OF_POOL_TAG: '' 

1727 } 

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

1729 

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

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

1732 # an already opened volume. 

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

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

1735 

1736 self._linstor.mark_volume_as_persistent(self.uuid) 

1737 except util.CommandException as e: 

1738 failed = True 

1739 raise xs_errors.XenError( 

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

1741 ) 

1742 except Exception as e: 

1743 failed = True 

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

1745 finally: 

1746 if failed: 

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

1748 try: 

1749 self._linstor.destroy_volume(self.uuid) 

1750 except Exception as e: 

1751 util.SMlog( 

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

1753 '{}'.format(e) 

1754 ) 

1755 

1756 self.utilisation = volume_info.allocated_size 

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

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

1759 

1760 self.ref = self._db_introduce() 

1761 self.sr._update_stats(self.size) 

1762 

1763 self.sr.database_backup("create") 

1764 

1765 return VDI.VDI.get_params(self) 

1766 

1767 @override 

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

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

1770 if self.attached: 

1771 raise xs_errors.XenError('VDIInUse') 

1772 

1773 if self.deleted: 

1774 return super(LinstorVDI, self).delete( 

1775 sr_uuid, vdi_uuid, data_only 

1776 ) 

1777 

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

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

1780 raise xs_errors.XenError( 

1781 'VDIDelete', 

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

1783 ) 

1784 

1785 try: 

1786 # Remove from XAPI and delete from LINSTOR. 

1787 self._linstor.destroy_volume(self.uuid) 

1788 if not data_only: 

1789 self._db_forget() 

1790 

1791 self.sr.lock.cleanupAll(vdi_uuid) 

1792 except Exception as e: 

1793 util.SMlog( 

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

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

1796 ) 

1797 

1798 try: 

1799 raise e 

1800 except LinstorVolumeManagerError as e: 

1801 if e.code != LinstorVolumeManagerError.ERR_VOLUME_DESTROY: 

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

1803 

1804 return 

1805 

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

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

1808 

1809 # TODO: Check size after delete. 

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

1811 self.sr._kick_gc() 

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

1813 self.sr.database_backup("delete") 

1814 

1815 @override 

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

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

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

1819 if ( 

1820 not attach_from_config or 

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

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

1823 raise xs_errors.XenError( 

1824 'VDIUnavailable', 

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

1826 'scan SR first to trigger auto-repair' 

1827 ) 

1828 

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

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

1831 

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

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

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

1835 # than the COW size + bitmap size. 

1836 need_inflate = True 

1837 if ( 

1838 not VdiType.isCowImage(self.vdi_type) or 

1839 not writable or 

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

1841 ): 

1842 need_inflate = False 

1843 

1844 if need_inflate: 

1845 try: 

1846 self._prepare_thin(True) 

1847 except Exception as e: 

1848 raise xs_errors.XenError( 

1849 'VDIUnavailable', 

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

1851 .format(e) 

1852 ) 

1853 

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

1855 self.xenstore_data = {} 

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

1857 

1858 if ( 

1859 USE_HTTP_NBD_SERVERS and 

1860 attach_from_config and 

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

1862 ): 

1863 return self._attach_using_http_nbd() 

1864 

1865 # Ensure we have a path... 

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

1867 

1868 self.attached = True 

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

1870 

1871 @override 

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

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

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

1875 self.attached = False 

1876 

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

1878 return self._detach_using_http_nbd() 

1879 

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

1881 return 

1882 

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

1884 # equal to the LINSTOR volume size. 

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

1886 already_deflated = self.capacity <= volume_size 

1887 

1888 if already_deflated: 

1889 util.SMlog( 

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

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

1892 ) 

1893 

1894 need_deflate = True 

1895 if already_deflated: 

1896 need_deflate = False 

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

1898 need_deflate = False 

1899 

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

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

1902 need_deflate = True 

1903 

1904 if need_deflate: 

1905 try: 

1906 self._prepare_thin(False) 

1907 except Exception as e: 

1908 raise xs_errors.XenError( 

1909 'VDIUnavailable', 

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

1911 .format(e) 

1912 ) 

1913 

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

1915 if self.sr.is_master(): 

1916 return 

1917 

1918 while vdi_uuid: 

1919 try: 

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

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

1922 except Exception: 

1923 break 

1924 

1925 if util.pathexists(path): 

1926 try: 

1927 self._linstor.remove_volume_if_diskless(vdi_uuid) 

1928 except Exception as e: 

1929 # Ensure we can always detach properly. 

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

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

1932 vdi_uuid = parent_vdi_uuid 

1933 

1934 @override 

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

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

1937 if not self.sr.is_master(): 

1938 raise xs_errors.XenError( 

1939 'VDISize', 

1940 opterr='resize on slave not allowed' 

1941 ) 

1942 

1943 if self.hidden: 

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

1945 

1946 # Compute the virtual COW and DRBD volume size. 

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

1948 volume_size = self.linstorcowutil.compute_volume_size(size) 

1949 util.SMlog( 

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

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

1952 ) 

1953 

1954 if size < self.size: 

1955 util.SMlog( 

1956 'vdi_resize: shrinking not supported: ' 

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

1958 ) 

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

1960 

1961 if size == self.size: 

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

1963 

1964 # Compute VDI sizes 

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

1966 old_volume_size = self.size 

1967 new_volume_size = LinstorVolumeManager.round_up_volume_size(size) 

1968 else: 

1969 old_volume_size = self.utilisation 

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

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

1972 new_volume_size = old_volume_size 

1973 else: 

1974 new_volume_size = self.linstorcowutil.compute_volume_size(size) 

1975 assert new_volume_size >= old_volume_size 

1976 

1977 space_needed = new_volume_size - old_volume_size 

1978 self.sr._ensure_space_available(space_needed) 

1979 

1980 old_size = self.size 

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

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

1983 else: 

1984 if new_volume_size != old_volume_size: 

1985 self.linstorcowutil.inflate( 

1986 self.sr._journaler, self.uuid, self.path, 

1987 new_volume_size, old_volume_size 

1988 ) 

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

1990 

1991 # Reload size attributes. 

1992 self._load_this() 

1993 

1994 # Update metadata 

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

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

1997 self.session.xenapi.VDI.set_physical_utilisation( 

1998 vdi_ref, str(self.utilisation) 

1999 ) 

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

2001 return VDI.VDI.get_params(self) 

2002 

2003 @override 

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

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

2006 

2007 @override 

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

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

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

2011 raise xs_errors.XenError('Unimplemented') 

2012 

2013 parent_uuid = vdi1 

2014 parent_path = self._linstor.get_device_path(parent_uuid) 

2015 

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

2017 # have a readonly error. 

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

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

2020 

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

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

2023 try: 

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

2025 self.linstorcowutil.set_hidden(parent_path) 

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

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

2028 ) 

2029 finally: 

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

2031 

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

2033 raise util.SMException( 

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

2035 ) 

2036 

2037 util.SMlog('Compose done') 

2038 

2039 @override 

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

2041 """ 

2042 Generate the XML config required to attach and activate 

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

2044 activation is handled by vdi_attach_from_config below. 

2045 """ 

2046 

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

2048 

2049 resp = {} 

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

2051 resp['sr_uuid'] = sr_uuid 

2052 resp['vdi_uuid'] = self.uuid 

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

2054 resp['command'] = 'vdi_attach_from_config' 

2055 

2056 # By default, we generate a normal config. 

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

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

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

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

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

2062 # instead. 

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

2064 if not USE_HTTP_NBD_SERVERS or volume_name not in [ 

2065 HA_VOLUME_NAME, REDO_LOG_VOLUME_NAME 

2066 ]: 

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

2068 available = False 

2069 # Try to refresh symlink path... 

2070 try: 

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

2072 available = util.pathexists(self.path) 

2073 except Exception: 

2074 pass 

2075 if not available: 

2076 raise xs_errors.XenError('VDIUnavailable') 

2077 

2078 resp['vdi_path'] = self.path 

2079 else: 

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

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

2082 

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

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

2085 

2086 @override 

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

2088 """ 

2089 Attach and activate a VDI using config generated by 

2090 vdi_generate_config above. This is used for cases such as 

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

2092 """ 

2093 

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

2095 

2096 try: 

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

2098 self.sr.attach(sr_uuid) 

2099 

2100 if not DRIVER_CONFIG['ATTACH_FROM_CONFIG_WITH_TAPDISK']: 

2101 return self.attach(sr_uuid, vdi_uuid) 

2102 except Exception: 

2103 util.logException('LinstorVDI.attach_from_config') 

2104 raise xs_errors.XenError( 

2105 'SRUnavailable', 

2106 opterr='Unable to attach from config' 

2107 ) 

2108 return '' 

2109 

2110 def reset_leaf(self, sr_uuid, vdi_uuid): 

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

2112 raise xs_errors.XenError('Unimplemented') 

2113 

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

2115 raise util.SMException( 

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

2117 .format(self.uuid) 

2118 ) 

2119 

2120 self.linstorcowutil.kill_data(self.path) 

2121 

2122 def _load_this(self): 

2123 volume_metadata = None 

2124 if self.sr._all_volume_metadata_cache: 

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

2126 assert volume_metadata 

2127 else: 

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

2129 

2130 volume_info = None 

2131 if self.sr._all_volume_info_cache: 

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

2133 assert volume_info 

2134 else: 

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

2136 

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

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

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

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

2141 # be lower than virtual size at creation. 

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

2143 self.utilisation = volume_info.allocated_size 

2144 self.capacity = volume_info.virtual_size 

2145 

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

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

2148 self.size = volume_info.virtual_size 

2149 self.parent = '' 

2150 else: 

2151 if self.sr._multi_cowutil: 

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

2153 else: 

2154 cowutil_instance = self.linstorcowutil 

2155 

2156 image_info = cowutil_instance.get_info(self.uuid) 

2157 self.hidden = image_info.hidden 

2158 self.size = image_info.sizeVirt 

2159 self.parent = image_info.parentUuid 

2160 

2161 if self.hidden: 

2162 self.managed = False 

2163 

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

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

2166 

2167 # Update sm_config_override of VDI parent class. 

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

2169 

2170 def _mark_hidden(self, hidden=True): 

2171 if self.hidden == hidden: 

2172 return 

2173 

2174 if VdiType.isCowImage(self.vdi_type): 

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

2176 else: 

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

2178 HIDDEN_TAG: hidden 

2179 }) 

2180 self.hidden = hidden 

2181 

2182 @override 

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

2184 xenapi = self.session.xenapi 

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

2186 

2187 volume_metadata = { 

2188 NAME_LABEL_TAG: util.to_plain_string( 

2189 xenapi.VDI.get_name_label(vdi_ref) 

2190 ), 

2191 NAME_DESCRIPTION_TAG: util.to_plain_string( 

2192 xenapi.VDI.get_name_description(vdi_ref) 

2193 ) 

2194 } 

2195 

2196 try: 

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

2198 except LinstorVolumeManagerError as e: 

2199 if e.code == LinstorVolumeManagerError.ERR_VOLUME_NOT_EXISTS: 

2200 raise xs_errors.XenError( 

2201 'VDIUnavailable', 

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

2203 ) 

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

2205 

2206 # -------------------------------------------------------------------------- 

2207 # Thin provisioning. 

2208 # -------------------------------------------------------------------------- 

2209 

2210 def _prepare_thin(self, attach): 

2211 if self.sr.is_master(): 

2212 if attach: 

2213 attach_thin( 

2214 self.session, self.sr._journaler, self._linstor, 

2215 self.sr.uuid, self.uuid 

2216 ) 

2217 else: 

2218 detach_thin( 

2219 self.session, self._linstor, self.sr.uuid, self.uuid 

2220 ) 

2221 else: 

2222 fn = 'attach' if attach else 'detach' 

2223 

2224 master = util.get_master_ref(self.session) 

2225 

2226 args = { 

2227 'groupName': self.sr._group_name, 

2228 'srUuid': self.sr.uuid, 

2229 'vdiUuid': self.uuid 

2230 } 

2231 

2232 try: 

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

2234 except Exception: 

2235 if fn != 'detach': 

2236 raise 

2237 

2238 # Reload size attrs after inflate or deflate! 

2239 self._load_this() 

2240 self.sr._update_physical_size() 

2241 

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

2243 self.session.xenapi.VDI.set_physical_utilisation( 

2244 vdi_ref, str(self.utilisation) 

2245 ) 

2246 

2247 self.session.xenapi.SR.set_physical_utilisation( 

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

2249 ) 

2250 

2251 # -------------------------------------------------------------------------- 

2252 # Generic helpers. 

2253 # -------------------------------------------------------------------------- 

2254 

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

2256 self.vdi_type = vdi_type 

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

2258 

2259 def _determine_type_and_path(self): 

2260 """ 

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

2262 """ 

2263 

2264 if self.sr._all_volume_metadata_cache: 

2265 # We are currently loading all volumes. 

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

2267 if not volume_metadata: 

2268 raise xs_errors.XenError( 

2269 'VDIUnavailable', 

2270 opterr='failed to get metadata' 

2271 ) 

2272 else: 

2273 # Simple load. 

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

2275 

2276 # Set type and path. 

2277 vdi_type = volume_metadata.get(VDI_TYPE_TAG) 

2278 if not vdi_type: 

2279 raise xs_errors.XenError( 

2280 'VDIUnavailable', 

2281 opterr='failed to get vdi_type in metadata' 

2282 ) 

2283 self._set_type(vdi_type) 

2284 

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

2286 

2287 def _update_device_name(self, device_name): 

2288 self._device_name = device_name 

2289 

2290 # Mark path of VDI parent class. 

2291 if device_name: 

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

2293 else: 

2294 self.path = None 

2295 

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

2297 """ 

2298 Snapshot self and return the snapshot VDI object. 

2299 """ 

2300 

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

2302 snap_path = self._linstor.shallow_clone_volume( 

2303 self.uuid, snap_uuid, persistent=False 

2304 ) 

2305 

2306 # 2. Write the snapshot content. 

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

2308 self.linstorcowutil.snapshot( 

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

2310 ) 

2311 

2312 # 3. Get snapshot parent. 

2313 snap_parent = self.linstorcowutil.get_parent(snap_uuid) 

2314 

2315 # 4. Update metadata. 

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

2317 volume_metadata = { 

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

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

2320 IS_A_SNAPSHOT_TAG: bool(snap_of_uuid), 

2321 SNAPSHOT_OF_TAG: snap_of_uuid, 

2322 SNAPSHOT_TIME_TAG: '', 

2323 TYPE_TAG: self.ty, 

2324 VDI_TYPE_TAG: snap_vdi_type, 

2325 READ_ONLY_TAG: False, 

2326 METADATA_OF_POOL_TAG: '' 

2327 } 

2328 self._linstor.set_volume_metadata(snap_uuid, volume_metadata) 

2329 

2330 # 5. Set size. 

2331 snap_vdi = LinstorVDI(self.sr, snap_uuid) 

2332 if not snap_vdi._exists: 

2333 raise xs_errors.XenError('VDISnapshot') 

2334 

2335 volume_info = self._linstor.get_volume_info(snap_uuid) 

2336 

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

2338 snap_vdi.utilisation = volume_info.allocated_size 

2339 

2340 # 6. Update sm config. 

2341 snap_vdi.sm_config = {} 

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

2343 if snap_parent: 

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

2345 snap_vdi.parent = snap_parent 

2346 

2347 snap_vdi.label = self.label 

2348 snap_vdi.description = self.description 

2349 

2350 self._linstor.mark_volume_as_persistent(snap_uuid) 

2351 

2352 return snap_vdi 

2353 

2354 # -------------------------------------------------------------------------- 

2355 # Implement specific SR methods. 

2356 # -------------------------------------------------------------------------- 

2357 

2358 @override 

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

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

2361 volume_uuid = self._linstor.get_volume_uuid_from_device_path(oldpath) 

2362 self._linstor.update_volume_name(volume_uuid, newpath) 

2363 

2364 @override 

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

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

2367 # If cbt enabled, save file consistency state. 

2368 if cbtlog is not None: 

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

2370 consistency_state = False 

2371 else: 

2372 consistency_state = True 

2373 util.SMlog( 

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

2375 .format(consistency_state, vdi_uuid) 

2376 ) 

2377 else: 

2378 consistency_state = None 

2379 

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

2381 raise xs_errors.XenError('Unimplemented') 

2382 

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

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

2385 try: 

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

2387 finally: 

2388 self.disable_leaf_on_secondary(vdi_uuid, secondary=secondary) 

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

2390 self.sr.database_backup("snapshot") 

2391 

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

2393 util.SMlog( 

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

2395 .format(self.uuid, snap_type) 

2396 ) 

2397 

2398 # 1. Checks... 

2399 if self.hidden: 

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

2401 

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

2403 

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

2405 if depth == -1: 

2406 raise xs_errors.XenError( 

2407 'VDIUnavailable', 

2408 opterr='failed to get COW depth' 

2409 ) 

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

2411 raise xs_errors.XenError('SnapshotChainTooLong') 

2412 

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

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

2415 

2416 volume_path = self.path 

2417 if not util.pathexists(volume_path): 

2418 raise xs_errors.XenError( 

2419 'EIO', 

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

2421 ) 

2422 

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

2424 base_uuid = util.gen_uuid() 

2425 snap_uuid = None 

2426 

2427 if snap_type == VDI.SNAPSHOT_DOUBLE: 

2428 snap_uuid = util.gen_uuid() 

2429 

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

2431 

2432 active_uuid = self.uuid 

2433 self.sr._journaler.create( 

2434 LinstorJournaler.CLONE, active_uuid, clone_info 

2435 ) 

2436 

2437 try: 

2438 # 3. Self becomes the new base. 

2439 # The device path remains the same. 

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

2441 self.uuid = base_uuid 

2442 self.location = self.uuid 

2443 self.read_only = True 

2444 self.managed = False 

2445 

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

2447 active_vdi = self._create_snapshot(snap_vdi_type, active_uuid) 

2448 

2449 snap_vdi = None 

2450 if snap_type == VDI.SNAPSHOT_DOUBLE: 

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

2452 

2453 self.label = 'base copy' 

2454 self.description = '' 

2455 

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

2457 # in subsequent scans. 

2458 self._mark_hidden() 

2459 self._linstor.update_volume_metadata( 

2460 self.uuid, {READ_ONLY_TAG: True} 

2461 ) 

2462 

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

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

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

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

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

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

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

2470 active_vdi.sm_config[key] = sm_config[key] 

2471 

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

2473 # delete base if unused. 

2474 introduce_parent = True 

2475 try: 

2476 snap_parent = None 

2477 if snap_vdi: 

2478 snap_parent = snap_vdi.parent 

2479 

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

2481 snap_type == VDI.SNAPSHOT_SINGLE or 

2482 snap_type == VDI.SNAPSHOT_INTERNAL or 

2483 snap_parent != self.uuid 

2484 ): 

2485 util.SMlog( 

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

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

2488 ) 

2489 introduce_parent = False 

2490 self._linstor.destroy_volume(self.uuid) 

2491 except Exception as e: 

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

2493 pass 

2494 

2495 # 8. Introduce the new VDI records. 

2496 if snap_vdi: 

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

2498 # new snapshot disk. 

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

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

2501 # TODO: Maybe remove key_hash support. 

2502 if 'key_hash' in sm_config: 

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

2504 # If we have CBT enabled on the VDI, 

2505 # set CBT status for the new snapshot disk. 

2506 if cbtlog: 

2507 snap_vdi.cbt_enabled = True 

2508 

2509 if snap_vdi: 

2510 snap_vdi_ref = snap_vdi._db_introduce() 

2511 util.SMlog( 

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

2513 .format(snap_vdi_ref, snap_vdi.uuid) 

2514 ) 

2515 if introduce_parent: 

2516 base_vdi_ref = self._db_introduce() 

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

2518 util.SMlog( 

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

2520 .format(base_vdi_ref, self.uuid) 

2521 ) 

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

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

2524 NAME_DESCRIPTION_TAG: util.to_plain_string( 

2525 self.description 

2526 ), 

2527 READ_ONLY_TAG: True, 

2528 METADATA_OF_POOL_TAG: '' 

2529 }) 

2530 

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

2532 if snap_type == VDI.SNAPSHOT_DOUBLE and cbtlog: 

2533 try: 

2534 self._cbt_snapshot(snap_uuid, cbt_consistency) 

2535 except Exception: 

2536 # CBT operation failed. 

2537 # TODO: Implement me. 

2538 raise 

2539 

2540 if snap_type != VDI.SNAPSHOT_INTERNAL: 

2541 self.sr._update_stats(self.size) 

2542 

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

2544 ret_vdi = snap_vdi 

2545 if not ret_vdi: 

2546 ret_vdi = self 

2547 if not ret_vdi: 

2548 ret_vdi = active_vdi 

2549 

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

2551 self.session.xenapi.VDI.set_sm_config( 

2552 vdi_ref, active_vdi.sm_config 

2553 ) 

2554 except Exception as e: 

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

2556 try: 

2557 self.sr._handle_interrupted_clone( 

2558 active_uuid, clone_info, force_undo=True 

2559 ) 

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

2561 except Exception as clean_error: 

2562 util.SMlog( 

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

2564 .format(clean_error) 

2565 ) 

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

2567 

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

2569 

2570 return ret_vdi.get_params() 

2571 

2572 @staticmethod 

2573 def _start_persistent_http_server(volume_name): 

2574 pid_path = None 

2575 http_server = None 

2576 

2577 try: 

2578 if volume_name == HA_VOLUME_NAME: 

2579 port = '8076' 

2580 else: 

2581 port = '8077' 

2582 

2583 try: 

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

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

2586 # block indefinitely. 

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

2588 host_ip = util.get_this_host_address(session) 

2589 except: 

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

2591 host_ip, _ = get_ips_from_xha_config_file() 

2592 if not host_ip: 

2593 raise Exception( 

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

2595 ) 

2596 

2597 arguments = [ 

2598 'http-disk-server', 

2599 '--disk', 

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

2601 '--ip', 

2602 host_ip, 

2603 '--port', 

2604 port 

2605 ] 

2606 

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

2608 http_server = subprocess.Popen( 

2609 [FORK_LOG_DAEMON] + arguments, 

2610 stdout=subprocess.PIPE, 

2611 stderr=subprocess.STDOUT, 

2612 universal_newlines=True, 

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

2614 # touch the current one. 

2615 preexec_fn=os.setsid 

2616 ) 

2617 

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

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

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

2621 

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

2623 def is_ready(): 

2624 while http_server.poll() is None: 

2625 line = http_server.stdout.readline() 

2626 if reg_server_ready.search(line): 

2627 return True 

2628 return False 

2629 try: 

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

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

2632 except util.TimeoutException: 

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

2634 except Exception as e: 

2635 if pid_path: 

2636 try: 

2637 os.remove(pid_path) 

2638 except Exception: 

2639 pass 

2640 

2641 if http_server: 

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

2643 try: 

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

2645 except: 

2646 pass 

2647 

2648 raise xs_errors.XenError( 

2649 'VDIUnavailable', 

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

2651 ) 

2652 

2653 def _start_persistent_nbd_server(self, volume_name): 

2654 pid_path = None 

2655 nbd_path = None 

2656 nbd_server = None 

2657 

2658 try: 

2659 # We use a precomputed device size. 

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

2661 if volume_name == HA_VOLUME_NAME: 

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

2663 port = '8076' 

2664 device_size = 4 * 1024 * 1024 

2665 else: 

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

2667 port = '8077' 

2668 device_size = 256 * 1024 * 1024 

2669 

2670 try: 

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

2672 ips = util.get_host_addresses(session) 

2673 except Exception as e: 

2674 _, ips = get_ips_from_xha_config_file() 

2675 if not ips: 

2676 raise Exception( 

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

2678 ) 

2679 ips = ips.values() 

2680 

2681 arguments = [ 

2682 'nbd-http-server', 

2683 '--socket-path', 

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

2685 '--nbd-name', 

2686 volume_name, 

2687 '--urls', 

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

2689 '--device-size', 

2690 str(device_size) 

2691 ] 

2692 

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

2694 nbd_server = subprocess.Popen( 

2695 [FORK_LOG_DAEMON] + arguments, 

2696 stdout=subprocess.PIPE, 

2697 stderr=subprocess.STDOUT, 

2698 universal_newlines=True, 

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

2700 # touch the current one. 

2701 preexec_fn=os.setsid 

2702 ) 

2703 

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

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

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

2707 

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

2709 def get_nbd_path(): 

2710 while nbd_server.poll() is None: 

2711 line = nbd_server.stdout.readline() 

2712 match = reg_nbd_path.search(line) 

2713 if match: 

2714 return match.group(1) 

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

2716 try: 

2717 nbd_path = util.timeout_call(10, get_nbd_path) 

2718 if nbd_path is None: 

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

2720 except util.TimeoutException: 

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

2722 

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

2724 os.symlink(nbd_path, self.path) 

2725 except Exception as e: 

2726 if pid_path: 

2727 try: 

2728 os.remove(pid_path) 

2729 except Exception: 

2730 pass 

2731 

2732 if nbd_path: 

2733 try: 

2734 os.remove(nbd_path) 

2735 except Exception: 

2736 pass 

2737 

2738 if nbd_server: 

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

2740 try: 

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

2742 except: 

2743 pass 

2744 

2745 raise xs_errors.XenError( 

2746 'VDIUnavailable', 

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

2748 ) 

2749 

2750 @classmethod 

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

2752 try: 

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

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

2755 return 

2756 

2757 pid = None 

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

2759 try: 

2760 pid = int(pid_file.read()) 

2761 except Exception: 

2762 pass 

2763 

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

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

2766 try: 

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

2768 except Exception as e: 

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

2770 

2771 os.remove(path) 

2772 except: 

2773 pass 

2774 

2775 @classmethod 

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

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

2778 

2779 @classmethod 

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

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

2782 

2783 def _check_http_nbd_volume_name(self): 

2784 volume_name = self.path[14:] 

2785 if volume_name not in [ 

2786 HA_VOLUME_NAME, REDO_LOG_VOLUME_NAME 

2787 ]: 

2788 raise xs_errors.XenError( 

2789 'VDIUnavailable', 

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

2791 ) 

2792 return volume_name 

2793 

2794 def _attach_using_http_nbd(self): 

2795 volume_name = self._check_http_nbd_volume_name() 

2796 

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

2798 self._kill_persistent_nbd_server(volume_name) 

2799 self._kill_persistent_http_server(volume_name) 

2800 

2801 # 0. Fetch drbd path. 

2802 must_get_device_path = True 

2803 if not self.sr.is_master(): 

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

2805 try: 

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

2807 except Exception as e: 

2808 raise xs_errors.XenError( 

2809 'VDIUnavailable', 

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

2811 .format(self.uuid, e) 

2812 ) 

2813 

2814 hostname = socket.gethostname() 

2815 must_get_device_path = hostname in volume_info.diskful 

2816 

2817 drbd_path = None 

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

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

2820 # or diskful available to init HA. 

2821 # It also avoid this error in xensource.log 

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

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

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

2825 available = False 

2826 try: 

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

2828 available = util.pathexists(drbd_path) 

2829 except Exception: 

2830 pass 

2831 

2832 if not available: 

2833 raise xs_errors.XenError( 

2834 'VDIUnavailable', 

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

2836 ) 

2837 

2838 # 1. Prepare http-nbd folder. 

2839 try: 

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

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

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

2843 os.remove(self.path) 

2844 except OSError as e: 

2845 if e.errno != errno.EEXIST: 

2846 raise xs_errors.XenError( 

2847 'VDIUnavailable', 

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

2849 ) 

2850 

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

2852 http_service = None 

2853 if drbd_path: 

2854 assert(drbd_path in ( 

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

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

2857 )) 

2858 self._start_persistent_http_server(volume_name) 

2859 

2860 # 3. Start NBD server in all cases. 

2861 try: 

2862 self._start_persistent_nbd_server(volume_name) 

2863 except Exception as e: 

2864 if drbd_path: 

2865 self._kill_persistent_http_server(volume_name) 

2866 raise 

2867 

2868 self.attached = True 

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

2870 

2871 def _detach_using_http_nbd(self): 

2872 volume_name = self._check_http_nbd_volume_name() 

2873 self._kill_persistent_nbd_server(volume_name) 

2874 self._kill_persistent_http_server(volume_name) 

2875 

2876# ------------------------------------------------------------------------------ 

2877 

2878 

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

2880 def run(): 

2881 SRCommand.run(LinstorSR, DRIVER_INFO) 

2882 

2883 if not TRACE_PERFS: 

2884 run() 

2885 else: 

2886 util.make_profile('LinstorSR', run) 

2887else: 

2888 SR.registerSR(LinstorSR)