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 Optional, override 

18 

19from constants import CBTLOG_TAG 

20 

21try: 

22 from linstorjournaler import LinstorJournaler 

23 from linstorvhdutil import LinstorVhdUtil, MultiLinstorVhdUtil 

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 

37from lock import Lock 

38import blktap2 

39import cleanup 

40import errno 

41import functools 

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 vhdutil 

57import xml.etree.ElementTree as xml_parser 

58import xmlrpc.client 

59import xs_errors 

60 

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 

65 

66HIDDEN_TAG = 'hidden' 

67 

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

69 

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

71 

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

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

74# specific conditions: 

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

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

77USE_HTTP_NBD_SERVERS = True 

78 

79# Useful flag to trace calls using cProfile. 

80TRACE_PERFS = False 

81 

82# Enable/Disable VHD key hash support. 

83USE_KEY_HASH = False 

84 

85# Special volumes. 

86HA_VOLUME_NAME = PERSISTENT_PREFIX + 'ha-statefile' 

87REDO_LOG_VOLUME_NAME = PERSISTENT_PREFIX + 'redo-log' 

88 

89# ============================================================================== 

90 

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

92# 'VDI_CONFIG_CBT', 'SR_PROBE' 

93 

94CAPABILITIES = [ 

95 'ATOMIC_PAUSE', 

96 'SR_UPDATE', 

97 'VDI_CREATE', 

98 'VDI_DELETE', 

99 'VDI_UPDATE', 

100 'VDI_ATTACH', 

101 'VDI_DETACH', 

102 'VDI_ACTIVATE', 

103 'VDI_DEACTIVATE', 

104 'VDI_CLONE', 

105 'VDI_MIRROR', 

106 'VDI_RESIZE', 

107 'VDI_SNAPSHOT', 

108 'VDI_GENERATE_CONFIG' 

109] 

110 

111CONFIGURATION = [ 

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

113 ['redundancy', 'replication count'], 

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

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

116] 

117 

118DRIVER_INFO = { 

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

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

121 'vendor': 'Vates', 

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

123 'driver_version': '1.0', 

124 'required_api_version': '1.0', 

125 'capabilities': CAPABILITIES, 

126 'configuration': CONFIGURATION 

127} 

128 

129DRIVER_CONFIG = {'ATTACH_FROM_CONFIG_WITH_TAPDISK': False} 

130 

131OPS_EXCLUSIVE = [ 

132 'sr_create', 'sr_delete', 'sr_attach', 'sr_detach', 'sr_scan', 

133 'sr_update', 'sr_probe', 'vdi_init', 'vdi_create', 'vdi_delete', 

134 'vdi_attach', 'vdi_detach', 'vdi_clone', 'vdi_snapshot', 

135] 

136 

137# ============================================================================== 

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

139# ============================================================================== 

140 

141 

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

143 volume_metadata = linstor.get_volume_metadata(vdi_uuid) 

144 image_type = volume_metadata.get(VDI_TYPE_TAG) 

145 if image_type == vhdutil.VDI_TYPE_RAW: 

146 return 

147 

148 device_path = linstor.get_device_path(vdi_uuid) 

149 

150 # If the virtual VHD size is lower than the LINSTOR volume size, 

151 # there is nothing to do. 

152 vhd_size = LinstorVhdUtil.compute_volume_size( 

153 LinstorVhdUtil(session, linstor).get_size_virt(vdi_uuid), 

154 image_type 

155 ) 

156 

157 volume_info = linstor.get_volume_info(vdi_uuid) 

158 volume_size = volume_info.virtual_size 

159 

160 if vhd_size > volume_size: 

161 LinstorVhdUtil(session, linstor).inflate( 

162 journaler, vdi_uuid, device_path, vhd_size, volume_size 

163 ) 

164 

165 

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

167 volume_metadata = linstor.get_volume_metadata(vdi_uuid) 

168 image_type = volume_metadata.get(VDI_TYPE_TAG) 

169 if image_type == vhdutil.VDI_TYPE_RAW: 

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 vhdutil_inst = LinstorVhdUtil(session, linstor) 

195 new_volume_size = LinstorVolumeManager.round_up_volume_size( 

196 vhdutil_inst.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 vhdutil_inst.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 @override 

305 @staticmethod 

306 def handles(type) -> bool: 

307 return type == LinstorSR.DRIVER_TYPE 

308 

309 @override 

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

311 if not LINSTOR_AVAILABLE: 

312 raise util.SMException( 

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

314 ) 

315 

316 # Check parameters. 

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

318 raise xs_errors.XenError('LinstorConfigGroupNameMissing') 

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

320 raise xs_errors.XenError('LinstorConfigRedundancyMissing') 

321 

322 self.driver_config = DRIVER_CONFIG 

323 

324 # Check provisioning config. 

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

326 if provisioning: 

327 if provisioning in self.PROVISIONING_TYPES: 

328 self._provisioning = provisioning 

329 else: 

330 raise xs_errors.XenError( 

331 'InvalidArg', 

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

333 self.PROVISIONING_TYPES 

334 ) 

335 ) 

336 else: 

337 self._provisioning = self.PROVISIONING_DEFAULT 

338 

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

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

341 util.strtobool(monitor_db_quorum) 

342 

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

344 # 'vdi_attach_from_config' command is executed. 

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

346 if self._has_session: 

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

348 else: 

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

350 

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

352 if provisioning in self.PROVISIONING_TYPES: 

353 self._provisioning = provisioning 

354 

355 # Define properties for SR parent class. 

356 self.ops_exclusive = OPS_EXCLUSIVE 

357 self.path = LinstorVolumeManager.DEV_ROOT_PATH 

358 self.lock = Lock(vhdutil.LOCK_TYPE_SR, self.uuid) 

359 self.sr_vditype = SR.DEFAULT_TAP 

360 

361 if self.cmd == 'sr_create': 

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

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

364 self._journaler = None 

365 

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

367 

368 self._vdi_shared_time = 0 

369 

370 self._init_status = self.INIT_STATUS_NOT_SET 

371 

372 self._vdis_loaded = False 

373 self._all_volume_info_cache = None 

374 self._all_volume_metadata_cache = None 

375 self._multi_vhdutil = None 

376 

377 # To remove in python 3.10. 

378 # Use directly @staticmethod instead. 

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

380 def _locked_load(method): 

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

382 self._init_status = self.INIT_STATUS_OK 

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

384 

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

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

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

388 activate_lvm_group(self._group_name) 

389 

390 if not self._has_session: 

391 if self.srcmd.cmd in ( 

392 'vdi_attach_from_config', 

393 'vdi_detach_from_config', 

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

395 # empty command. 

396 None 

397 ): 

398 def create_linstor(uri, attempt_count=30): 

399 self._linstor = LinstorVolumeManager( 

400 uri, 

401 self._group_name, 

402 logger=util.SMlog, 

403 attempt_count=attempt_count 

404 ) 

405 # Only required if we are attaching from config using a non-special VDI. 

406 # I.e. not an HA volume. 

407 self._vhdutil = LinstorVhdUtil(self.session, self._linstor) 

408 

409 controller_uri = get_controller_uri() 

410 if controller_uri: 

411 create_linstor(controller_uri) 

412 else: 

413 def connect(): 

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

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

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

417 controller_uri = 'linstor://' + ip 

418 try: 

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

420 create_linstor(controller_uri, attempt_count=0) 

421 return controller_uri 

422 except: 

423 pass 

424 

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

426 if not controller_uri: 

427 raise xs_errors.XenError( 

428 'SRUnavailable', 

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

430 ) 

431 

432 self._journaler = LinstorJournaler( 

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

434 ) 

435 

436 if self.srcmd.cmd is None: 

437 # Only useful on on-slave plugin (is_open). 

438 self._vhdutil = LinstorVhdUtil(self.session, self._linstor) 

439 

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

441 

442 if not self.is_master(): 

443 if self.cmd in [ 

444 'sr_create', 'sr_delete', 'sr_update', 'sr_probe', 

445 'sr_scan', 'vdi_create', 'vdi_delete', 'vdi_resize', 

446 'vdi_snapshot', 'vdi_clone' 

447 ]: 

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

449 raise xs_errors.XenError('LinstorMaster') 

450 

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

452 # the VDI before the LinstorJournaler/LinstorVolumeManager 

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

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

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

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

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

458 self._vdi_shared_time = time.time() 

459 

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

461 try: 

462 self._reconnect() 

463 except Exception as e: 

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

465 

466 if self._linstor: 

467 try: 

468 hosts = self._linstor.disconnected_hosts 

469 except Exception as e: 

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

471 

472 if hosts: 

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

474 

475 # Ensure we use a non-locked volume when vhdutil is called. 

476 if ( 

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

478 self.cmd != 'vdi_create' 

479 ): 

480 self._linstor.ensure_volume_is_not_locked( 

481 self.srcmd.params['vdi_uuid'] 

482 ) 

483 

484 try: 

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

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

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

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

489 # 

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

491 # resourceless volumes. 

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

493 'vdi_attach', 'vdi_detach', 

494 'vdi_activate', 'vdi_deactivate', 

495 'vdi_epoch_begin', 'vdi_epoch_end', 

496 'vdi_update', 'vdi_destroy' 

497 ]: 

498 load_vdis = ( 

499 self.cmd == 'sr_scan' or 

500 self.cmd == 'sr_attach' 

501 ) or len( 

502 self._journaler.get_all(LinstorJournaler.INFLATE) 

503 ) or len( 

504 self._journaler.get_all(LinstorJournaler.CLONE) 

505 ) 

506 

507 if load_vdis: 

508 self._load_vdis() 

509 

510 self._linstor.remove_resourceless_volumes() 

511 

512 self._synchronize_metadata() 

513 except Exception as e: 

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

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

516 # from the XAPI database otherwise. 

517 raise e 

518 util.SMlog( 

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

520 ) 

521 util.SMlog(traceback.format_exc()) 

522 

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

524 

525 @functools.wraps(wrapped_method) 

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

527 if self._init_status in \ 

528 (self.INIT_STATUS_OK, self.INIT_STATUS_IN_PROGRESS): 

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

530 if self._init_status == self.INIT_STATUS_FAIL: 

531 util.SMlog( 

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

533 .format(method) 

534 ) 

535 else: 

536 try: 

537 self._init_status = self.INIT_STATUS_IN_PROGRESS 

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

539 except Exception: 

540 if self._init_status != self.INIT_STATUS_OK: 

541 self._init_status = self.INIT_STATUS_FAIL 

542 raise 

543 

544 return wrap 

545 

546 @override 

547 def cleanup(self) -> None: 

548 if self._vdi_shared_time: 

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

550 

551 @override 

552 @_locked_load 

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

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

555 

556 host_adresses = util.get_host_addresses(self.session) 

557 if self._redundancy > len(host_adresses): 

558 raise xs_errors.XenError( 

559 'LinstorSRCreate', 

560 opterr='Redundancy greater than host count' 

561 ) 

562 

563 xenapi = self.session.xenapi 

564 srs = xenapi.SR.get_all_records_where( 

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

566 ) 

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

568 

569 for sr in srs.values(): 

570 for pbd in sr['PBDs']: 

571 device_config = xenapi.PBD.get_device_config(pbd) 

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

573 if group_name and group_name == self._group_name: 

574 raise xs_errors.XenError( 

575 'LinstorSRCreate', 

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

577 xenapi.PBD.get_uuid(pbd) 

578 ) 

579 ) 

580 

581 if srs: 

582 raise xs_errors.XenError( 

583 'LinstorSRCreate', 

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

585 ) 

586 

587 online_hosts = util.get_enabled_hosts(self.session) 

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

589 raise xs_errors.XenError( 

590 'LinstorSRCreate', 

591 opterr='Not enough online hosts' 

592 ) 

593 

594 ips = {} 

595 for host_ref in online_hosts: 

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

597 hostname = record['hostname'] 

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

599 

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

601 raise xs_errors.XenError( 

602 'LinstorSRCreate', 

603 opterr='Multiple hosts with same hostname' 

604 ) 

605 

606 # Ensure ports are opened and LINSTOR satellites 

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

608 # must be stopped. 

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

610 

611 # Create SR. 

612 # Throw if the SR already exists. 

613 try: 

614 self._linstor = LinstorVolumeManager.create_sr( 

615 self._group_name, 

616 ips, 

617 self._redundancy, 

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

619 logger=util.SMlog 

620 ) 

621 self._vhdutil = LinstorVhdUtil(self.session, self._linstor) 

622 

623 util.SMlog( 

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

625 ) 

626 self._update_drbd_reactor_on_all_hosts(enabled=True) 

627 except Exception as e: 

628 if not self._linstor: 

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

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

631 

632 try: 

633 self._linstor.destroy() 

634 except Exception as e2: 

635 util.SMlog( 

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

637 .format(e2) 

638 ) 

639 raise e 

640 

641 @override 

642 @_locked_load 

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

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

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

646 

647 assert self._linstor 

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

649 raise xs_errors.XenError('SRNotEmpty') 

650 

651 node_name = get_controller_node_name() 

652 if not node_name: 

653 raise xs_errors.XenError( 

654 'LinstorSRDelete', 

655 opterr='Cannot get controller node name' 

656 ) 

657 

658 host_ref = None 

659 if node_name == 'localhost': 

660 host_ref = util.get_this_host_ref(self.session) 

661 else: 

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

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

664 if r_name == node_name: 

665 host_ref = slave 

666 break 

667 

668 if not host_ref: 

669 raise xs_errors.XenError( 

670 'LinstorSRDelete', 

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

672 node_name 

673 ) 

674 ) 

675 

676 try: 

677 if self._monitor_db_quorum: 

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

679 self._update_drbd_reactor_on_all_hosts( 

680 controller_node_name=node_name, enabled=False 

681 ) 

682 

683 args = { 

684 'groupName': self._group_name, 

685 } 

686 self._exec_manager_command( 

687 host_ref, 'destroy', args, 'LinstorSRDelete' 

688 ) 

689 except Exception as e: 

690 try: 

691 self._update_drbd_reactor_on_all_hosts( 

692 controller_node_name=node_name, enabled=True 

693 ) 

694 if self._monitor_db_quorum: 

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

696 except Exception as e2: 

697 util.SMlog( 

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

699 .format(e2) 

700 ) 

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

702 raise xs_errors.XenError( 

703 'LinstorSRDelete', 

704 opterr=str(e) 

705 ) 

706 

707 Lock.cleanupAll(self.uuid) 

708 

709 @override 

710 @_locked_load 

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

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

713 

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

715 if not self._linstor: 

716 raise xs_errors.XenError( 

717 'SRUnavailable', 

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

719 ) 

720 

721 self._update_stats(0) 

722 

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

724 xenapi = self.session.xenapi 

725 self._linstor.metadata = { 

726 NAME_LABEL_TAG: util.to_plain_string( 

727 xenapi.SR.get_name_label(self.sr_ref) 

728 ), 

729 NAME_DESCRIPTION_TAG: util.to_plain_string( 

730 xenapi.SR.get_name_description(self.sr_ref) 

731 ) 

732 } 

733 

734 @override 

735 @_locked_load 

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

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

738 

739 if not self._linstor: 

740 raise xs_errors.XenError( 

741 'SRUnavailable', 

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

743 ) 

744 

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

746 self._linstor.set_drbd_ha_properties(DATABASE_VOLUME_NAME) 

747 

748 @override 

749 @_locked_load 

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

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

752 cleanup.abort(self.uuid) 

753 

754 @override 

755 @_locked_load 

756 def probe(self) -> str: 

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

758 # TODO 

759 return '' 

760 

761 @override 

762 @_locked_load 

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

764 if self._init_status == self.INIT_STATUS_FAIL: 

765 return 

766 

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

768 if not self._linstor: 

769 raise xs_errors.XenError( 

770 'SRUnavailable', 

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

772 ) 

773 

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

775 # are loaded. 

776 self._load_vdis() 

777 self._update_physical_size() 

778 

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

780 if self.vdis[vdi_uuid].deleted: 

781 del self.vdis[vdi_uuid] 

782 

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

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

785 try: 

786 self._linstor.get_database_path() 

787 except Exception as e: 

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

789 # VDIs in the XAPI database... 

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

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

792 ): 

793 raise xs_errors.XenError( 

794 'SRUnavailable', 

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

796 ) 

797 

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

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

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

801 self._kick_gc() 

802 

803 def is_master(self): 

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

805 if 'SRmaster' not in self.dconf: 

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

807 else: 

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

809 

810 return self._is_master 

811 

812 @override 

813 @_locked_load 

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

815 return LinstorVDI(self, uuid) 

816 

817 # To remove in python 3.10 

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

819 _locked_load = staticmethod(_locked_load) 

820 

821 # -------------------------------------------------------------------------- 

822 # Lock. 

823 # -------------------------------------------------------------------------- 

824 

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

826 master = util.get_master_ref(self.session) 

827 

828 command = 'lockVdi' 

829 args = { 

830 'groupName': self._group_name, 

831 'srUuid': self.uuid, 

832 'vdiUuid': vdi_uuid, 

833 'locked': str(locked) 

834 } 

835 

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

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

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

839 # 

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

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

842 # to implement without impacting performance. 

843 if not locked: 

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

845 timeout = LinstorVolumeManager.LOCKED_EXPIRATION_DELAY * 0.7 

846 if elapsed_time >= timeout: 

847 util.SMlog( 

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

849 .format(vdi_uuid) 

850 ) 

851 return 

852 

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

854 

855 # -------------------------------------------------------------------------- 

856 # Network. 

857 # -------------------------------------------------------------------------- 

858 

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

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

861 host_uuid = host_rec['uuid'] 

862 

863 try: 

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

865 host_ref, self.MANAGER_PLUGIN, command, args 

866 ) 

867 except Exception as e: 

868 util.SMlog( 

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

870 host_uuid, self.MANAGER_PLUGIN, command, args 

871 ) 

872 ) 

873 raise e 

874 

875 util.SMlog( 

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

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

878 ) 

879 ) 

880 if ret == 'False': 

881 raise xs_errors.XenError( 

882 error, 

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

884 ) 

885 

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

887 self._exec_manager_command( 

888 host, 

889 'prepareSr' if enabled else 'releaseSr', 

890 {'groupName': group_name}, 

891 'SRUnavailable' 

892 ) 

893 

894 def _prepare_sr_on_all_hosts(self, group_name, enabled): 

895 master = util.get_master_ref(self.session) 

896 self._prepare_sr(master, group_name, enabled) 

897 

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

899 self._prepare_sr(slave, group_name, enabled) 

900 

901 def _update_drbd_reactor(self, host, enabled): 

902 self._exec_manager_command( 

903 host, 

904 'updateDrbdReactor', 

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

906 'SRUnavailable' 

907 ) 

908 

909 def _update_drbd_reactor_on_all_hosts( 

910 self, enabled, controller_node_name=None 

911 ): 

912 if controller_node_name == 'localhost': 

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

914 util.get_this_host_ref(self.session) 

915 )['hostname'] 

916 assert controller_node_name 

917 assert controller_node_name != 'localhost' 

918 

919 controller_host = None 

920 secondary_hosts = [] 

921 

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

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

924 hostname = host_rec['hostname'] 

925 if controller_node_name == hostname: 

926 controller_host = host_ref 

927 else: 

928 secondary_hosts.append((host_ref, hostname)) 

929 

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

931 if controller_node_name and not controller_host: 

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

933 controller_node_name 

934 )) 

935 

936 if enabled and controller_host: 

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

938 action_name, controller_node_name 

939 )) 

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

941 # node name first. 

942 self._update_drbd_reactor(controller_host, enabled) 

943 

944 for host_ref, hostname in secondary_hosts: 

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

946 action_name, hostname 

947 )) 

948 self._update_drbd_reactor(host_ref, enabled) 

949 

950 if not enabled and controller_host: 

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

952 action_name, controller_node_name 

953 )) 

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

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

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

957 self._update_drbd_reactor(controller_host, enabled) 

958 

959 # -------------------------------------------------------------------------- 

960 # Metadata. 

961 # -------------------------------------------------------------------------- 

962 

963 def _synchronize_metadata_and_xapi(self): 

964 try: 

965 # First synch SR parameters. 

966 self.update(self.uuid) 

967 

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

969 xenapi = self.session.xenapi 

970 volumes_metadata = self._linstor.get_volumes_with_metadata() 

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

972 try: 

973 vdi_ref = xenapi.VDI.get_by_uuid(vdi_uuid) 

974 except Exception: 

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

976 continue 

977 

978 label = util.to_plain_string( 

979 xenapi.VDI.get_name_label(vdi_ref) 

980 ) 

981 description = util.to_plain_string( 

982 xenapi.VDI.get_name_description(vdi_ref) 

983 ) 

984 

985 if ( 

986 volume_metadata.get(NAME_LABEL_TAG) != label or 

987 volume_metadata.get(NAME_DESCRIPTION_TAG) != description 

988 ): 

989 self._linstor.update_volume_metadata(vdi_uuid, { 

990 NAME_LABEL_TAG: label, 

991 NAME_DESCRIPTION_TAG: description 

992 }) 

993 except Exception as e: 

994 raise xs_errors.XenError( 

995 'MetadataError', 

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

997 ) 

998 

999 def _synchronize_metadata(self): 

1000 if not self.is_master(): 

1001 return 

1002 

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

1004 if self.cmd == 'sr_attach': 

1005 try: 

1006 util.SMlog( 

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

1008 ) 

1009 self._synchronize_metadata_and_xapi() 

1010 except Exception as e: 

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

1012 

1013 # -------------------------------------------------------------------------- 

1014 # Stats. 

1015 # -------------------------------------------------------------------------- 

1016 

1017 def _update_stats(self, virt_alloc_delta): 

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

1019 self.sr_ref 

1020 )) 

1021 

1022 # Update size attributes of the SR parent class. 

1023 self.virtual_allocation = valloc + virt_alloc_delta 

1024 

1025 self._update_physical_size() 

1026 

1027 # Notify SR parent class. 

1028 self._db_update() 

1029 

1030 def _update_physical_size(self): 

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

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

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

1034 self.physical_size = min_physical_size * pool_count // \ 

1035 self._linstor.redundancy 

1036 

1037 self.physical_utilisation = self._linstor.allocated_volume_size 

1038 

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

1040 # VDIs. 

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

1042 

1043 def _load_vdis(self): 

1044 if self._vdis_loaded: 

1045 return 

1046 

1047 assert self.is_master() 

1048 

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

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

1051 # enjoy it with a few lines. 

1052 self._create_linstor_cache() 

1053 self._load_vdis_ex() 

1054 self._destroy_linstor_cache() 

1055 

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

1057 self._vdis_loaded = True 

1058 

1059 self._undo_all_journal_transactions() 

1060 

1061 def _load_vdis_ex(self): 

1062 # 1. Get existing VDIs in XAPI. 

1063 xenapi = self.session.xenapi 

1064 xapi_vdi_uuids = set() 

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

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

1067 

1068 # 2. Get volumes info. 

1069 all_volume_info = self._all_volume_info_cache 

1070 volumes_metadata = self._all_volume_metadata_cache 

1071 

1072 # 3. Get CBT vdis. 

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

1074 cbt_vdis = set() 

1075 for volume_metadata in volumes_metadata.values(): 

1076 cbt_uuid = volume_metadata.get(CBTLOG_TAG) 

1077 if cbt_uuid: 

1078 cbt_vdis.add(cbt_uuid) 

1079 

1080 introduce = False 

1081 

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

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

1084 has_clone_entries = list(self._journaler.get_all( 

1085 LinstorJournaler.CLONE 

1086 ).items()) 

1087 

1088 if has_clone_entries: 

1089 util.SMlog( 

1090 'Cannot introduce VDIs during scan because it exists ' 

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

1092 ) 

1093 else: 

1094 introduce = True 

1095 

1096 # 4. Now process all volume info. 

1097 vdi_to_snaps = {} 

1098 vdi_uuids = [] 

1099 

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

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

1102 continue 

1103 

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

1105 if vdi_uuid not in xapi_vdi_uuids: 

1106 if not introduce: 

1107 continue 

1108 

1109 if vdi_uuid.startswith('DELETED_'): 

1110 continue 

1111 

1112 volume_metadata = volumes_metadata.get(vdi_uuid) 

1113 if not volume_metadata: 

1114 util.SMlog( 

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

1116 .format(vdi_uuid) 

1117 ) 

1118 continue 

1119 

1120 util.SMlog( 

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

1122 'LINSTOR and not in XAPI...' 

1123 .format(vdi_uuid) 

1124 ) 

1125 

1126 try: 

1127 self._linstor.get_device_path(vdi_uuid) 

1128 except Exception as e: 

1129 util.SMlog( 

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

1131 .format(vdi_uuid, e) 

1132 ) 

1133 continue 

1134 

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

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

1137 vdi_type = volume_metadata.get(VDI_TYPE_TAG) 

1138 

1139 if not vdi_type: 

1140 util.SMlog( 

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

1142 'without vdi_type' 

1143 ) 

1144 continue 

1145 

1146 sm_config = { 

1147 'vdi_type': vdi_type 

1148 } 

1149 

1150 if vdi_type == vhdutil.VDI_TYPE_RAW: 

1151 managed = not volume_metadata.get(HIDDEN_TAG) 

1152 elif vdi_type == vhdutil.VDI_TYPE_VHD: 

1153 vhd_info = self._vhdutil.get_vhd_info(vdi_uuid) 

1154 managed = not vhd_info.hidden 

1155 if vhd_info.parentUuid: 

1156 sm_config['vhd-parent'] = vhd_info.parentUuid 

1157 else: 

1158 util.SMlog( 

1159 'Cannot introduce {} with invalid VDI type {}' 

1160 .format(vdi_uuid, vdi_type) 

1161 ) 

1162 continue 

1163 

1164 util.SMlog( 

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

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

1167 name_label, 

1168 volume_info.virtual_size, 

1169 volume_info.allocated_size 

1170 ) 

1171 ) 

1172 

1173 vdi_ref = xenapi.VDI.db_introduce( 

1174 vdi_uuid, 

1175 name_label, 

1176 volume_metadata.get(NAME_DESCRIPTION_TAG) or '', 

1177 self.sr_ref, 

1178 type, 

1179 False, # sharable 

1180 bool(volume_metadata.get(READ_ONLY_TAG)), 

1181 {}, # other_config 

1182 vdi_uuid, # location 

1183 {}, # xenstore_data 

1184 sm_config, 

1185 managed, 

1186 str(volume_info.virtual_size), 

1187 str(volume_info.allocated_size) 

1188 ) 

1189 

1190 is_a_snapshot = volume_metadata.get(IS_A_SNAPSHOT_TAG) 

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

1192 if is_a_snapshot: 

1193 xenapi.VDI.set_snapshot_time( 

1194 vdi_ref, 

1195 xmlrpc.client.DateTime( 

1196 volume_metadata[SNAPSHOT_TIME_TAG] or 

1197 '19700101T00:00:00Z' 

1198 ) 

1199 ) 

1200 

1201 snap_uuid = volume_metadata[SNAPSHOT_OF_TAG] 

1202 if snap_uuid in vdi_to_snaps: 

1203 vdi_to_snaps[snap_uuid].append(vdi_uuid) 

1204 else: 

1205 vdi_to_snaps[snap_uuid] = [vdi_uuid] 

1206 

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

1208 vdi_uuids.append(vdi_uuid) 

1209 

1210 # 5. Create VDIs. 

1211 self._multi_vhdutil = MultiLinstorVhdUtil(self._linstor.uri, self._group_name) 

1212 

1213 def load_vdi(vdi_uuid, vhdutil_instance): 

1214 vdi = self.vdi(vdi_uuid) 

1215 

1216 if USE_KEY_HASH and vdi.vdi_type == vhdutil.VDI_TYPE_VHD: 

1217 vdi.sm_config_override['key_hash'] = vhdutil_instance.get_key_hash(vdi_uuid) 

1218 

1219 return vdi 

1220 

1221 try: 

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

1223 finally: 

1224 multi_vhdutil = self._multi_vhdutil 

1225 self._multi_vhdutil = None 

1226 del multi_vhdutil 

1227 

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

1229 # or already in XAPI. 

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

1231 volume_metadata = volumes_metadata.get(vdi.uuid) 

1232 cbt_uuid = volume_metadata.get(CBTLOG_TAG) 

1233 if cbt_uuid in cbt_vdis: 

1234 vdi_ref = xenapi.VDI.get_by_uuid(vdi_uuid) 

1235 xenapi.VDI.set_cbt_enabled(vdi_ref, True) 

1236 # For existing VDIs, update local state too. 

1237 # Scan in base class SR updates existing VDIs 

1238 # again based on local states. 

1239 self.vdis[vdi_uuid].cbt_enabled = True 

1240 cbt_vdis.remove(cbt_uuid) 

1241 

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

1243 for src_uuid in vdi_to_snaps: 

1244 try: 

1245 src_ref = xenapi.VDI.get_by_uuid(src_uuid) 

1246 except Exception: 

1247 # The source VDI no longer exists, continue. 

1248 continue 

1249 

1250 for snap_uuid in vdi_to_snaps[src_uuid]: 

1251 try: 

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

1253 snap_ref = xenapi.VDI.get_by_uuid(snap_uuid) 

1254 xenapi.VDI.set_snapshot_of(snap_ref, src_ref) 

1255 except Exception as e: 

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

1257 

1258 # TODO: Check correctly how to use CBT. 

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

1260 

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

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

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

1264 for cbt_uuid in cbt_vdis: 

1265 new_vdi = self.vdi(cbt_uuid) 

1266 new_vdi.ty = 'cbt_metadata' 

1267 new_vdi.cbt_enabled = True 

1268 self.vdis[cbt_uuid] = new_vdi 

1269 

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

1271 self.virtual_allocation = 0 

1272 

1273 # 10. Build geneology. 

1274 geneology = {} 

1275 

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

1277 if vdi.parent: 

1278 if vdi.parent in self.vdis: 

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

1280 if vdi.parent in geneology: 

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

1282 else: 

1283 geneology[vdi.parent] = [vdi_uuid] 

1284 if not vdi.hidden: 

1285 self.virtual_allocation += vdi.size 

1286 

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

1288 # will be GC'ed. 

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

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

1291 util.SMlog( 

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

1293 ) 

1294 del self.vdis[vdi_uuid] 

1295 

1296 # -------------------------------------------------------------------------- 

1297 # Journals. 

1298 # -------------------------------------------------------------------------- 

1299 

1300 def _get_vdi_path_and_parent(self, vdi_uuid, volume_name): 

1301 try: 

1302 device_path = self._linstor.build_device_path(volume_name) 

1303 if not util.pathexists(device_path): 

1304 return (None, None) 

1305 

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

1307 volume_metadata = self._linstor.get_volume_metadata(vdi_uuid) 

1308 vdi_type = volume_metadata[VDI_TYPE_TAG] 

1309 if vdi_type == vhdutil.VDI_TYPE_RAW: 

1310 return (device_path, None) 

1311 

1312 # Otherwise it's a VHD and a parent can exist. 

1313 if not self._vhdutil.check(vdi_uuid): 

1314 return (None, None) 

1315 

1316 vhd_info = self._vhdutil.get_vhd_info(vdi_uuid) 

1317 if vhd_info: 

1318 return (device_path, vhd_info.parentUuid) 

1319 except Exception as e: 

1320 util.SMlog( 

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

1322 .format(e) 

1323 ) 

1324 return (None, None) 

1325 

1326 def _undo_all_journal_transactions(self): 

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

1328 self.lock.acquire() 

1329 try: 

1330 self._handle_interrupted_inflate_ops() 

1331 self._handle_interrupted_clone_ops() 

1332 pass 

1333 finally: 

1334 self.lock.release() 

1335 

1336 def _handle_interrupted_inflate_ops(self): 

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

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

1339 self._handle_interrupted_inflate(vdi_uuid, old_size) 

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

1341 

1342 def _handle_interrupted_clone_ops(self): 

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

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

1345 self._handle_interrupted_clone(vdi_uuid, old_size) 

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

1347 

1348 def _handle_interrupted_inflate(self, vdi_uuid, old_size): 

1349 util.SMlog( 

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

1351 .format(vdi_uuid, old_size) 

1352 ) 

1353 

1354 vdi = self.vdis.get(vdi_uuid) 

1355 if not vdi: 

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

1357 return 

1358 

1359 assert not self._all_volume_info_cache 

1360 volume_info = self._linstor.get_volume_info(vdi_uuid) 

1361 

1362 current_size = volume_info.virtual_size 

1363 assert current_size > 0 

1364 self._vhdutil.force_deflate(vdi.path, old_size, current_size, zeroize=True) 

1365 

1366 def _handle_interrupted_clone( 

1367 self, vdi_uuid, clone_info, force_undo=False 

1368 ): 

1369 util.SMlog( 

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

1371 .format(vdi_uuid, clone_info) 

1372 ) 

1373 

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

1375 

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

1377 volume_names = self._linstor.get_volumes_with_name() 

1378 

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

1380 if base_uuid not in volume_names: 

1381 if vdi_uuid in volume_names: 

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

1383 return 

1384 raise util.SMException( 

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

1386 .format(base_uuid, vdi_uuid) 

1387 ) 

1388 

1389 if force_undo: 

1390 util.SMlog('Explicit revert') 

1391 self._undo_clone( 

1392 volume_names, vdi_uuid, base_uuid, snap_uuid 

1393 ) 

1394 return 

1395 

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

1397 if vdi_uuid not in volume_names or \ 

1398 (snap_uuid and snap_uuid not in volume_names): 

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

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

1401 return 

1402 

1403 vdi_path, vdi_parent_uuid = self._get_vdi_path_and_parent( 

1404 vdi_uuid, volume_names[vdi_uuid] 

1405 ) 

1406 snap_path, snap_parent_uuid = self._get_vdi_path_and_parent( 

1407 snap_uuid, volume_names[snap_uuid] 

1408 ) 

1409 

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

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

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

1413 return 

1414 

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

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

1417 

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

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

1420 base_metadata = self._linstor.get_volume_metadata(base_uuid) 

1421 base_type = base_metadata[VDI_TYPE_TAG] 

1422 

1423 if not util.pathexists(base_path): 

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

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

1426 return 

1427 

1428 # Un-hide the parent. 

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

1430 if base_type == vhdutil.VDI_TYPE_VHD: 

1431 vhd_info = self._vhdutil.get_vhd_info(base_uuid, False) 

1432 if vhd_info.hidden: 

1433 self._vhdutil.set_hidden(base_path, False) 

1434 elif base_type == vhdutil.VDI_TYPE_RAW and \ 

1435 base_metadata.get(HIDDEN_TAG): 

1436 self._linstor.update_volume_metadata( 

1437 base_uuid, {HIDDEN_TAG: False} 

1438 ) 

1439 

1440 # Remove the child nodes. 

1441 if snap_uuid and snap_uuid in volume_names: 

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

1443 

1444 try: 

1445 self._linstor.destroy_volume(snap_uuid) 

1446 except Exception as e: 

1447 util.SMlog( 

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

1449 .format(snap_uuid, e) 

1450 ) 

1451 

1452 if vdi_uuid in volume_names: 

1453 try: 

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

1455 self._linstor.destroy_volume(vdi_uuid) 

1456 except Exception as e: 

1457 util.SMlog( 

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

1459 .format(vdi_uuid, e) 

1460 ) 

1461 # We can get an exception like this: 

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

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

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

1465 # UUID of this bad VDI before. 

1466 self._linstor.update_volume_uuid( 

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

1468 ) 

1469 

1470 # Rename! 

1471 self._linstor.update_volume_uuid(base_uuid, vdi_uuid) 

1472 

1473 # Inflate to the right size. 

1474 if base_type == vhdutil.VDI_TYPE_VHD: 

1475 vdi = self.vdi(vdi_uuid) 

1476 volume_size = LinstorVhdUtil.compute_volume_size(vdi.size, vdi.vdi_type) 

1477 self._vhdutil.inflate( 

1478 self._journaler, vdi_uuid, vdi.path, 

1479 volume_size, vdi.capacity 

1480 ) 

1481 self.vdis[vdi_uuid] = vdi 

1482 

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

1484 # flag to facilitate vm deactivate. 

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

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

1487 

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

1489 

1490 # -------------------------------------------------------------------------- 

1491 # Cache. 

1492 # -------------------------------------------------------------------------- 

1493 

1494 def _create_linstor_cache(self): 

1495 reconnect = False 

1496 

1497 def create_cache(): 

1498 nonlocal reconnect 

1499 try: 

1500 if reconnect: 

1501 self._reconnect() 

1502 return self._linstor.get_volumes_with_info() 

1503 except Exception as e: 

1504 reconnect = True 

1505 raise e 

1506 

1507 self._all_volume_metadata_cache = \ 

1508 self._linstor.get_volumes_with_metadata() 

1509 self._all_volume_info_cache = util.retry( 

1510 create_cache, 

1511 maxretry=10, 

1512 period=3 

1513 ) 

1514 

1515 def _destroy_linstor_cache(self): 

1516 self._all_volume_info_cache = None 

1517 self._all_volume_metadata_cache = None 

1518 

1519 # -------------------------------------------------------------------------- 

1520 # Misc. 

1521 # -------------------------------------------------------------------------- 

1522 

1523 def _reconnect(self): 

1524 controller_uri = get_controller_uri() 

1525 

1526 self._journaler = LinstorJournaler( 

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

1528 ) 

1529 

1530 # Try to open SR if exists. 

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

1532 # we are trying to execute an exclusive operation. 

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

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

1535 # the SR is locked. 

1536 self._linstor = LinstorVolumeManager( 

1537 controller_uri, 

1538 self._group_name, 

1539 repair=( 

1540 self.is_master() and 

1541 self.srcmd.cmd in self.ops_exclusive 

1542 ), 

1543 logger=util.SMlog 

1544 ) 

1545 self._vhdutil = LinstorVhdUtil(self.session, self._linstor) 

1546 

1547 def _ensure_space_available(self, amount_needed): 

1548 space_available = self._linstor.max_volume_size_allowed 

1549 if (space_available < amount_needed): 

1550 util.SMlog( 

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

1552 space_available, amount_needed 

1553 ) 

1554 ) 

1555 raise xs_errors.XenError('SRNoSpace') 

1556 

1557 def _kick_gc(self): 

1558 util.SMlog('Kicking GC') 

1559 cleanup.start_gc_service(self.uuid) 

1560 

1561# ============================================================================== 

1562# LinstorSr VDI 

1563# ============================================================================== 

1564 

1565 

1566class LinstorVDI(VDI.VDI): 

1567 # Warning: Not the same values than vhdutil.VDI_TYPE_*. 

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

1569 TYPE_RAW = 'raw' 

1570 TYPE_VHD = 'vhd' 

1571 

1572 # Metadata size given to the "S" param of vhd-util create. 

1573 # "-S size (MB) for metadata preallocation". 

1574 # Increase the performance when resize is called. 

1575 MAX_METADATA_VIRT_SIZE = 2 * 1024 * 1024 

1576 

1577 # -------------------------------------------------------------------------- 

1578 # VDI methods. 

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

1580 

1581 @override 

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

1583 self._lock = self.sr.lock 

1584 self._exists = True 

1585 self._linstor = self.sr._linstor 

1586 

1587 # Update hidden parent property. 

1588 self.hidden = False 

1589 

1590 def raise_bad_load(e): 

1591 util.SMlog( 

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

1593 ) 

1594 util.SMlog(traceback.format_exc()) 

1595 raise xs_errors.XenError( 

1596 'VDIUnavailable', 

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

1598 ) 

1599 

1600 # Try to load VDI. 

1601 try: 

1602 if ( 

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

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

1605 ): 

1606 self.vdi_type = vhdutil.VDI_TYPE_RAW 

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

1608 else: 

1609 self._determine_type_and_path() 

1610 self._load_this() 

1611 

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

1613 self.uuid, self.path, self.hidden 

1614 )) 

1615 except LinstorVolumeManagerError as e: 

1616 # 1. It may be a VDI deletion. 

1617 if e.code == LinstorVolumeManagerError.ERR_VOLUME_NOT_EXISTS: 

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

1619 self.deleted = True 

1620 return 

1621 

1622 # 2. Or maybe a creation. 

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

1624 # Set type attribute of VDI parent class. 

1625 # We use VHD by default. 

1626 self.vdi_type = vhdutil.VDI_TYPE_VHD 

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

1628 

1629 self._exists = False 

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

1631 if vdi_sm_config is not None: 

1632 type = vdi_sm_config.get('type') 

1633 if type is not None: 

1634 if type == self.TYPE_RAW: 

1635 self.vdi_type = vhdutil.VDI_TYPE_RAW 

1636 elif type == self.TYPE_VHD: 

1637 self.vdi_type = vhdutil.VDI_TYPE_VHD 

1638 else: 

1639 raise xs_errors.XenError( 

1640 'VDICreate', 

1641 opterr='Invalid VDI type {}'.format(type) 

1642 ) 

1643 if self.vdi_type == vhdutil.VDI_TYPE_VHD: 

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

1645 

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

1647 self._update_device_name(None) 

1648 return 

1649 raise_bad_load(e) 

1650 except Exception as e: 

1651 raise_bad_load(e) 

1652 

1653 @override 

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

1655 # Usage example: 

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

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

1658 

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

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

1661 if self._exists: 

1662 raise xs_errors.XenError('VDIExists') 

1663 

1664 assert self.uuid 

1665 assert self.ty 

1666 assert self.vdi_type 

1667 

1668 # 2. Compute size and check space available. 

1669 size = vhdutil.validate_and_round_vhd_size(int(size)) 

1670 volume_size = LinstorVhdUtil.compute_volume_size(size, self.vdi_type) 

1671 util.SMlog( 

1672 'LinstorVDI.create: type={}, vhd-size={}, volume-size={}' 

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

1674 ) 

1675 self.sr._ensure_space_available(volume_size) 

1676 

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

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

1679 

1680 # 4. Create! 

1681 failed = False 

1682 try: 

1683 volume_name = None 

1684 if self.ty == 'ha_statefile': 

1685 volume_name = HA_VOLUME_NAME 

1686 elif self.ty == 'redo_log': 

1687 volume_name = REDO_LOG_VOLUME_NAME 

1688 

1689 self._linstor.create_volume( 

1690 self.uuid, 

1691 volume_size, 

1692 persistent=False, 

1693 volume_name=volume_name, 

1694 high_availability=volume_name is not None 

1695 ) 

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

1697 

1698 self._update_device_name(volume_info.name) 

1699 

1700 if self.vdi_type == vhdutil.VDI_TYPE_RAW: 

1701 self.size = volume_info.virtual_size 

1702 else: 

1703 self.sr._vhdutil.create( 

1704 self.path, size, False, self.MAX_METADATA_VIRT_SIZE 

1705 ) 

1706 self.size = self.sr._vhdutil.get_size_virt(self.uuid) 

1707 

1708 if self._key_hash: 

1709 self.sr._vhdutil.set_key(self.path, self._key_hash) 

1710 

1711 # Because vhdutil commands modify the volume data, 

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

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

1714 

1715 volume_metadata = { 

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

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

1718 IS_A_SNAPSHOT_TAG: False, 

1719 SNAPSHOT_OF_TAG: '', 

1720 SNAPSHOT_TIME_TAG: '', 

1721 TYPE_TAG: self.ty, 

1722 VDI_TYPE_TAG: self.vdi_type, 

1723 READ_ONLY_TAG: bool(self.read_only), 

1724 METADATA_OF_POOL_TAG: '' 

1725 } 

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

1727 

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

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

1730 # an already opened volume. 

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

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

1733 

1734 self._linstor.mark_volume_as_persistent(self.uuid) 

1735 except util.CommandException as e: 

1736 failed = True 

1737 raise xs_errors.XenError( 

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

1739 ) 

1740 except Exception as e: 

1741 failed = True 

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

1743 finally: 

1744 if failed: 

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

1746 try: 

1747 self._linstor.destroy_volume(self.uuid) 

1748 except Exception as e: 

1749 util.SMlog( 

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

1751 '{}'.format(e) 

1752 ) 

1753 

1754 self.utilisation = volume_info.allocated_size 

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

1756 

1757 self.ref = self._db_introduce() 

1758 self.sr._update_stats(self.size) 

1759 

1760 return VDI.VDI.get_params(self) 

1761 

1762 @override 

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

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

1765 if self.attached: 

1766 raise xs_errors.XenError('VDIInUse') 

1767 

1768 if self.deleted: 

1769 return super(LinstorVDI, self).delete( 

1770 sr_uuid, vdi_uuid, data_only 

1771 ) 

1772 

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

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

1775 raise xs_errors.XenError( 

1776 'VDIDelete', 

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

1778 ) 

1779 

1780 try: 

1781 # Remove from XAPI and delete from LINSTOR. 

1782 self._linstor.destroy_volume(self.uuid) 

1783 if not data_only: 

1784 self._db_forget() 

1785 

1786 self.sr.lock.cleanupAll(vdi_uuid) 

1787 except Exception as e: 

1788 util.SMlog( 

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

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

1791 ) 

1792 

1793 try: 

1794 raise e 

1795 except LinstorVolumeManagerError as e: 

1796 if e.code != LinstorVolumeManagerError.ERR_VOLUME_DESTROY: 

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

1798 

1799 return 

1800 

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

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

1803 

1804 # TODO: Check size after delete. 

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

1806 self.sr._kick_gc() 

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

1808 

1809 @override 

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

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

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

1813 if ( 

1814 not attach_from_config or 

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

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

1817 raise xs_errors.XenError( 

1818 'VDIUnavailable', 

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

1820 'scan SR first to trigger auto-repair' 

1821 ) 

1822 

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

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

1825 

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

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

1828 # to mount the VHD image. I.e. the volume capacity must be greater 

1829 # than the VHD size + bitmap size. 

1830 need_inflate = True 

1831 if ( 

1832 self.vdi_type == vhdutil.VDI_TYPE_RAW or 

1833 not writable or 

1834 self.capacity >= LinstorVhdUtil.compute_volume_size(self.size, self.vdi_type) 

1835 ): 

1836 need_inflate = False 

1837 

1838 if need_inflate: 

1839 try: 

1840 self._prepare_thin(True) 

1841 except Exception as e: 

1842 raise xs_errors.XenError( 

1843 'VDIUnavailable', 

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

1845 .format(e) 

1846 ) 

1847 

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

1849 self.xenstore_data = {} 

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

1851 

1852 if ( 

1853 USE_HTTP_NBD_SERVERS and 

1854 attach_from_config and 

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

1856 ): 

1857 return self._attach_using_http_nbd() 

1858 

1859 # Ensure we have a path... 

1860 self.sr._vhdutil.create_chain_paths(self.uuid, readonly=not writable) 

1861 

1862 self.attached = True 

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

1864 

1865 @override 

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

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

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

1869 self.attached = False 

1870 

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

1872 return self._detach_using_http_nbd() 

1873 

1874 if self.vdi_type == vhdutil.VDI_TYPE_RAW: 

1875 return 

1876 

1877 # The VDI is already deflated if the VHD image size + metadata is 

1878 # equal to the LINSTOR volume size. 

1879 volume_size = LinstorVhdUtil.compute_volume_size(self.size, self.vdi_type) 

1880 already_deflated = self.capacity <= volume_size 

1881 

1882 if already_deflated: 

1883 util.SMlog( 

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

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

1886 ) 

1887 

1888 need_deflate = True 

1889 if already_deflated: 

1890 need_deflate = False 

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

1892 need_deflate = False 

1893 

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

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

1896 need_deflate = True 

1897 

1898 if need_deflate: 

1899 try: 

1900 self._prepare_thin(False) 

1901 except Exception as e: 

1902 raise xs_errors.XenError( 

1903 'VDIUnavailable', 

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

1905 .format(e) 

1906 ) 

1907 

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

1909 if self.sr.is_master(): 

1910 return 

1911 

1912 while vdi_uuid: 

1913 try: 

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

1915 parent_vdi_uuid = self.sr._vhdutil.get_vhd_info(vdi_uuid).parentUuid 

1916 except Exception: 

1917 break 

1918 

1919 if util.pathexists(path): 

1920 try: 

1921 self._linstor.remove_volume_if_diskless(vdi_uuid) 

1922 except Exception as e: 

1923 # Ensure we can always detach properly. 

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

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

1926 vdi_uuid = parent_vdi_uuid 

1927 

1928 @override 

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

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

1931 if not self.sr.is_master(): 

1932 raise xs_errors.XenError( 

1933 'VDISize', 

1934 opterr='resize on slave not allowed' 

1935 ) 

1936 

1937 if self.hidden: 

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

1939 

1940 # Compute the virtual VHD and DRBD volume size. 

1941 size = vhdutil.validate_and_round_vhd_size(int(size)) 

1942 volume_size = LinstorVhdUtil.compute_volume_size(size, self.vdi_type) 

1943 util.SMlog( 

1944 'LinstorVDI.resize: type={}, vhd-size={}, volume-size={}' 

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

1946 ) 

1947 

1948 if size < self.size: 

1949 util.SMlog( 

1950 'vdi_resize: shrinking not supported: ' 

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

1952 ) 

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

1954 

1955 if size == self.size: 

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

1957 

1958 # Compute VDI sizes 

1959 if self.vdi_type == vhdutil.VDI_TYPE_RAW: 

1960 old_volume_size = self.size 

1961 new_volume_size = LinstorVolumeManager.round_up_volume_size(size) 

1962 else: 

1963 old_volume_size = self.utilisation 

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

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

1966 new_volume_size = old_volume_size 

1967 else: 

1968 new_volume_size = LinstorVhdUtil.compute_volume_size(size, self.vdi_type) 

1969 assert new_volume_size >= old_volume_size 

1970 

1971 space_needed = new_volume_size - old_volume_size 

1972 self.sr._ensure_space_available(space_needed) 

1973 

1974 old_size = self.size 

1975 

1976 # Resize VDI 

1977 if self.vdi_type == vhdutil.VDI_TYPE_RAW: 

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

1979 else: 

1980 if new_volume_size != old_volume_size: 

1981 self.sr._vhdutil.inflate( 

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

1983 new_volume_size, old_volume_size 

1984 ) 

1985 self.sr._vhdutil.set_size_virt_fast(self.path, size) 

1986 

1987 # Reload size attributes. 

1988 self._load_this() 

1989 

1990 # Update metadata 

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

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

1993 self.session.xenapi.VDI.set_physical_utilisation( 

1994 vdi_ref, str(self.utilisation) 

1995 ) 

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

1997 return VDI.VDI.get_params(self) 

1998 

1999 @override 

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

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

2002 

2003 @override 

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

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

2006 if self.vdi_type != vhdutil.VDI_TYPE_VHD: 

2007 raise xs_errors.XenError('Unimplemented') 

2008 

2009 parent_uuid = vdi1 

2010 parent_path = self._linstor.get_device_path(parent_uuid) 

2011 

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

2013 # have a readonly error. 

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

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

2016 

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

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

2019 try: 

2020 self.sr._vhdutil.set_parent(self.path, parent_path, False) 

2021 self.sr._vhdutil.set_hidden(parent_path) 

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

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

2024 ) 

2025 finally: 

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

2027 

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

2029 raise util.SMException( 

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

2031 ) 

2032 

2033 util.SMlog('Compose done') 

2034 

2035 @override 

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

2037 """ 

2038 Generate the XML config required to attach and activate 

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

2040 activation is handled by vdi_attach_from_config below. 

2041 """ 

2042 

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

2044 

2045 resp = {} 

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

2047 resp['sr_uuid'] = sr_uuid 

2048 resp['vdi_uuid'] = self.uuid 

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

2050 resp['command'] = 'vdi_attach_from_config' 

2051 

2052 # By default, we generate a normal config. 

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

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

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

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

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

2058 # instead. 

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

2060 if not USE_HTTP_NBD_SERVERS or volume_name not in [ 

2061 HA_VOLUME_NAME, REDO_LOG_VOLUME_NAME 

2062 ]: 

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

2064 available = False 

2065 # Try to refresh symlink path... 

2066 try: 

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

2068 available = util.pathexists(self.path) 

2069 except Exception: 

2070 pass 

2071 if not available: 

2072 raise xs_errors.XenError('VDIUnavailable') 

2073 

2074 resp['vdi_path'] = self.path 

2075 else: 

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

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

2078 

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

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

2081 

2082 @override 

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

2084 """ 

2085 Attach and activate a VDI using config generated by 

2086 vdi_generate_config above. This is used for cases such as 

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

2088 """ 

2089 

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

2091 

2092 try: 

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

2094 self.sr.attach(sr_uuid) 

2095 

2096 if not DRIVER_CONFIG['ATTACH_FROM_CONFIG_WITH_TAPDISK']: 

2097 return self.attach(sr_uuid, vdi_uuid) 

2098 except Exception: 

2099 util.logException('LinstorVDI.attach_from_config') 

2100 raise xs_errors.XenError( 

2101 'SRUnavailable', 

2102 opterr='Unable to attach from config' 

2103 ) 

2104 return '' 

2105 

2106 def reset_leaf(self, sr_uuid, vdi_uuid): 

2107 if self.vdi_type != vhdutil.VDI_TYPE_VHD: 

2108 raise xs_errors.XenError('Unimplemented') 

2109 

2110 if not self.sr._vhdutil.has_parent(self.uuid): 

2111 raise util.SMException( 

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

2113 .format(self.uuid) 

2114 ) 

2115 

2116 self.sr._vhdutil.kill_data(self.path) 

2117 

2118 def _load_this(self): 

2119 volume_metadata = None 

2120 if self.sr._all_volume_metadata_cache: 

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

2122 assert volume_metadata 

2123 else: 

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

2125 

2126 volume_info = None 

2127 if self.sr._all_volume_info_cache: 

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

2129 assert volume_info 

2130 else: 

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

2132 

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

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

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

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

2137 # be lower than virtual size at creation. 

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

2139 self.utilisation = volume_info.allocated_size 

2140 self.capacity = volume_info.virtual_size 

2141 

2142 if self.vdi_type == vhdutil.VDI_TYPE_RAW: 

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

2144 self.size = volume_info.virtual_size 

2145 self.parent = '' 

2146 else: 

2147 if self.sr._multi_vhdutil: 

2148 vhdutil_instance = self.sr._multi_vhdutil.local_vhdutil 

2149 else: 

2150 vhdutil_instance = self.sr._vhdutil 

2151 

2152 vhd_info = vhdutil_instance.get_vhd_info(self.uuid) 

2153 

2154 self.hidden = vhd_info.hidden 

2155 self.size = vhd_info.sizeVirt 

2156 self.parent = vhd_info.parentUuid 

2157 

2158 if self.hidden: 

2159 self.managed = False 

2160 

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

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

2163 

2164 # Update sm_config_override of VDI parent class. 

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

2166 

2167 def _mark_hidden(self, hidden=True): 

2168 if self.hidden == hidden: 

2169 return 

2170 

2171 if self.vdi_type == vhdutil.VDI_TYPE_VHD: 

2172 self.sr._vhdutil.set_hidden(self.path, hidden) 

2173 else: 

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

2175 HIDDEN_TAG: hidden 

2176 }) 

2177 self.hidden = hidden 

2178 

2179 @override 

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

2181 xenapi = self.session.xenapi 

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

2183 

2184 volume_metadata = { 

2185 NAME_LABEL_TAG: util.to_plain_string( 

2186 xenapi.VDI.get_name_label(vdi_ref) 

2187 ), 

2188 NAME_DESCRIPTION_TAG: util.to_plain_string( 

2189 xenapi.VDI.get_name_description(vdi_ref) 

2190 ) 

2191 } 

2192 

2193 try: 

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

2195 except LinstorVolumeManagerError as e: 

2196 if e.code == LinstorVolumeManagerError.ERR_VOLUME_NOT_EXISTS: 

2197 raise xs_errors.XenError( 

2198 'VDIUnavailable', 

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

2200 ) 

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

2202 

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

2204 # Thin provisioning. 

2205 # -------------------------------------------------------------------------- 

2206 

2207 def _prepare_thin(self, attach): 

2208 if self.sr.is_master(): 

2209 if attach: 

2210 attach_thin( 

2211 self.session, self.sr._journaler, self._linstor, 

2212 self.sr.uuid, self.uuid 

2213 ) 

2214 else: 

2215 detach_thin( 

2216 self.session, self._linstor, self.sr.uuid, self.uuid 

2217 ) 

2218 else: 

2219 fn = 'attach' if attach else 'detach' 

2220 

2221 master = util.get_master_ref(self.session) 

2222 

2223 args = { 

2224 'groupName': self.sr._group_name, 

2225 'srUuid': self.sr.uuid, 

2226 'vdiUuid': self.uuid 

2227 } 

2228 

2229 try: 

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

2231 except Exception: 

2232 if fn != 'detach': 

2233 raise 

2234 

2235 # Reload size attrs after inflate or deflate! 

2236 self._load_this() 

2237 self.sr._update_physical_size() 

2238 

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

2240 self.session.xenapi.VDI.set_physical_utilisation( 

2241 vdi_ref, str(self.utilisation) 

2242 ) 

2243 

2244 self.session.xenapi.SR.set_physical_utilisation( 

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

2246 ) 

2247 

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

2249 # Generic helpers. 

2250 # -------------------------------------------------------------------------- 

2251 

2252 def _determine_type_and_path(self): 

2253 """ 

2254 Determine whether this is a RAW or a VHD VDI. 

2255 """ 

2256 

2257 if self.sr._all_volume_metadata_cache: 

2258 # We are currently loading all volumes. 

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

2260 if not volume_metadata: 

2261 raise xs_errors.XenError( 

2262 'VDIUnavailable', 

2263 opterr='failed to get metadata' 

2264 ) 

2265 else: 

2266 # Simple load. 

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

2268 

2269 # Set type and path. 

2270 self.vdi_type = volume_metadata.get(VDI_TYPE_TAG) 

2271 if not self.vdi_type: 

2272 raise xs_errors.XenError( 

2273 'VDIUnavailable', 

2274 opterr='failed to get vdi_type in metadata' 

2275 ) 

2276 

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

2278 

2279 def _update_device_name(self, device_name): 

2280 self._device_name = device_name 

2281 

2282 # Mark path of VDI parent class. 

2283 if device_name: 

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

2285 else: 

2286 self.path = None 

2287 

2288 def _create_snapshot(self, snap_uuid, snap_of_uuid=None): 

2289 """ 

2290 Snapshot self and return the snapshot VDI object. 

2291 """ 

2292 

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

2294 snap_path = self._linstor.shallow_clone_volume( 

2295 self.uuid, snap_uuid, persistent=False 

2296 ) 

2297 

2298 # 2. Write the snapshot content. 

2299 is_raw = (self.vdi_type == vhdutil.VDI_TYPE_RAW) 

2300 self.sr._vhdutil.snapshot( 

2301 snap_path, self.path, is_raw, self.MAX_METADATA_VIRT_SIZE 

2302 ) 

2303 

2304 # 3. Get snapshot parent. 

2305 snap_parent = self.sr._vhdutil.get_parent(snap_uuid) 

2306 

2307 # 4. Update metadata. 

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

2309 volume_metadata = { 

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

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

2312 IS_A_SNAPSHOT_TAG: bool(snap_of_uuid), 

2313 SNAPSHOT_OF_TAG: snap_of_uuid, 

2314 SNAPSHOT_TIME_TAG: '', 

2315 TYPE_TAG: self.ty, 

2316 VDI_TYPE_TAG: vhdutil.VDI_TYPE_VHD, 

2317 READ_ONLY_TAG: False, 

2318 METADATA_OF_POOL_TAG: '' 

2319 } 

2320 self._linstor.set_volume_metadata(snap_uuid, volume_metadata) 

2321 

2322 # 5. Set size. 

2323 snap_vdi = LinstorVDI(self.sr, snap_uuid) 

2324 if not snap_vdi._exists: 

2325 raise xs_errors.XenError('VDISnapshot') 

2326 

2327 volume_info = self._linstor.get_volume_info(snap_uuid) 

2328 

2329 snap_vdi.size = self.sr._vhdutil.get_size_virt(snap_uuid) 

2330 snap_vdi.utilisation = volume_info.allocated_size 

2331 

2332 # 6. Update sm config. 

2333 snap_vdi.sm_config = {} 

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

2335 if snap_parent: 

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

2337 snap_vdi.parent = snap_parent 

2338 

2339 snap_vdi.label = self.label 

2340 snap_vdi.description = self.description 

2341 

2342 self._linstor.mark_volume_as_persistent(snap_uuid) 

2343 

2344 return snap_vdi 

2345 

2346 # -------------------------------------------------------------------------- 

2347 # Implement specific SR methods. 

2348 # -------------------------------------------------------------------------- 

2349 

2350 @override 

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

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

2353 volume_uuid = self._linstor.get_volume_uuid_from_device_path(oldpath) 

2354 self._linstor.update_volume_name(volume_uuid, newpath) 

2355 

2356 @override 

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

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

2359 # If cbt enabled, save file consistency state. 

2360 if cbtlog is not None: 

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

2362 consistency_state = False 

2363 else: 

2364 consistency_state = True 

2365 util.SMlog( 

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

2367 .format(consistency_state, vdi_uuid) 

2368 ) 

2369 else: 

2370 consistency_state = None 

2371 

2372 if self.vdi_type != vhdutil.VDI_TYPE_VHD: 

2373 raise xs_errors.XenError('Unimplemented') 

2374 

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

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

2377 try: 

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

2379 finally: 

2380 self.disable_leaf_on_secondary(vdi_uuid, secondary=secondary) 

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

2382 

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

2384 util.SMlog( 

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

2386 .format(self.uuid, snap_type) 

2387 ) 

2388 

2389 # 1. Checks... 

2390 if self.hidden: 

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

2392 

2393 depth = self.sr._vhdutil.get_depth(self.uuid) 

2394 if depth == -1: 

2395 raise xs_errors.XenError( 

2396 'VDIUnavailable', 

2397 opterr='failed to get VHD depth' 

2398 ) 

2399 elif depth >= vhdutil.MAX_CHAIN_SIZE: 

2400 raise xs_errors.XenError('SnapshotChainTooLong') 

2401 

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

2403 self.sr._vhdutil.create_chain_paths(self.uuid, readonly=True) 

2404 

2405 volume_path = self.path 

2406 if not util.pathexists(volume_path): 

2407 raise xs_errors.XenError( 

2408 'EIO', 

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

2410 ) 

2411 

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

2413 base_uuid = util.gen_uuid() 

2414 snap_uuid = None 

2415 

2416 if snap_type == VDI.SNAPSHOT_DOUBLE: 

2417 snap_uuid = util.gen_uuid() 

2418 

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

2420 

2421 active_uuid = self.uuid 

2422 self.sr._journaler.create( 

2423 LinstorJournaler.CLONE, active_uuid, clone_info 

2424 ) 

2425 

2426 try: 

2427 # 3. Self becomes the new base. 

2428 # The device path remains the same. 

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

2430 self.uuid = base_uuid 

2431 self.location = self.uuid 

2432 self.read_only = True 

2433 self.managed = False 

2434 

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

2436 active_vdi = self._create_snapshot(active_uuid) 

2437 

2438 snap_vdi = None 

2439 if snap_type == VDI.SNAPSHOT_DOUBLE: 

2440 snap_vdi = self._create_snapshot(snap_uuid, active_uuid) 

2441 

2442 self.label = 'base copy' 

2443 self.description = '' 

2444 

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

2446 # in subsequent scans. 

2447 self._mark_hidden() 

2448 self._linstor.update_volume_metadata( 

2449 self.uuid, {READ_ONLY_TAG: True} 

2450 ) 

2451 

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

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

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

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

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

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

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

2459 active_vdi.sm_config[key] = sm_config[key] 

2460 

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

2462 # delete base if unused. 

2463 introduce_parent = True 

2464 try: 

2465 snap_parent = None 

2466 if snap_vdi: 

2467 snap_parent = snap_vdi.parent 

2468 

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

2470 snap_type == VDI.SNAPSHOT_SINGLE or 

2471 snap_type == VDI.SNAPSHOT_INTERNAL or 

2472 snap_parent != self.uuid 

2473 ): 

2474 util.SMlog( 

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

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

2477 ) 

2478 introduce_parent = False 

2479 self._linstor.destroy_volume(self.uuid) 

2480 except Exception as e: 

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

2482 pass 

2483 

2484 # 8. Introduce the new VDI records. 

2485 if snap_vdi: 

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

2487 # new snapshot disk. 

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

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

2490 # TODO: Maybe remove key_hash support. 

2491 if 'key_hash' in sm_config: 

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

2493 # If we have CBT enabled on the VDI, 

2494 # set CBT status for the new snapshot disk. 

2495 if cbtlog: 

2496 snap_vdi.cbt_enabled = True 

2497 

2498 if snap_vdi: 

2499 snap_vdi_ref = snap_vdi._db_introduce() 

2500 util.SMlog( 

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

2502 .format(snap_vdi_ref, snap_vdi.uuid) 

2503 ) 

2504 if introduce_parent: 

2505 base_vdi_ref = self._db_introduce() 

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

2507 util.SMlog( 

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

2509 .format(base_vdi_ref, self.uuid) 

2510 ) 

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

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

2513 NAME_DESCRIPTION_TAG: util.to_plain_string( 

2514 self.description 

2515 ), 

2516 READ_ONLY_TAG: True, 

2517 METADATA_OF_POOL_TAG: '' 

2518 }) 

2519 

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

2521 if snap_type == VDI.SNAPSHOT_DOUBLE and cbtlog: 

2522 try: 

2523 self._cbt_snapshot(snap_uuid, cbt_consistency) 

2524 except Exception: 

2525 # CBT operation failed. 

2526 # TODO: Implement me. 

2527 raise 

2528 

2529 if snap_type != VDI.SNAPSHOT_INTERNAL: 

2530 self.sr._update_stats(self.size) 

2531 

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

2533 ret_vdi = snap_vdi 

2534 if not ret_vdi: 

2535 ret_vdi = self 

2536 if not ret_vdi: 

2537 ret_vdi = active_vdi 

2538 

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

2540 self.session.xenapi.VDI.set_sm_config( 

2541 vdi_ref, active_vdi.sm_config 

2542 ) 

2543 except Exception as e: 

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

2545 try: 

2546 self.sr._handle_interrupted_clone( 

2547 active_uuid, clone_info, force_undo=True 

2548 ) 

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

2550 except Exception as clean_error: 

2551 util.SMlog( 

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

2553 .format(clean_error) 

2554 ) 

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

2556 

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

2558 

2559 return ret_vdi.get_params() 

2560 

2561 @staticmethod 

2562 def _start_persistent_http_server(volume_name): 

2563 pid_path = None 

2564 http_server = None 

2565 

2566 try: 

2567 if volume_name == HA_VOLUME_NAME: 

2568 port = '8076' 

2569 else: 

2570 port = '8077' 

2571 

2572 try: 

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

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

2575 # block indefinitely. 

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

2577 host_ip = util.get_this_host_address(session) 

2578 except: 

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

2580 host_ip, _ = get_ips_from_xha_config_file() 

2581 if not host_ip: 

2582 raise Exception( 

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

2584 ) 

2585 

2586 arguments = [ 

2587 'http-disk-server', 

2588 '--disk', 

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

2590 '--ip', 

2591 host_ip, 

2592 '--port', 

2593 port 

2594 ] 

2595 

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

2597 http_server = subprocess.Popen( 

2598 [FORK_LOG_DAEMON] + arguments, 

2599 stdout=subprocess.PIPE, 

2600 stderr=subprocess.STDOUT, 

2601 universal_newlines=True, 

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

2603 # touch the current one. 

2604 preexec_fn=os.setsid 

2605 ) 

2606 

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

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

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

2610 

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

2612 def is_ready(): 

2613 while http_server.poll() is None: 

2614 line = http_server.stdout.readline() 

2615 if reg_server_ready.search(line): 

2616 return True 

2617 return False 

2618 try: 

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

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

2621 except util.TimeoutException: 

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

2623 except Exception as e: 

2624 if pid_path: 

2625 try: 

2626 os.remove(pid_path) 

2627 except Exception: 

2628 pass 

2629 

2630 if http_server: 

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

2632 try: 

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

2634 except: 

2635 pass 

2636 

2637 raise xs_errors.XenError( 

2638 'VDIUnavailable', 

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

2640 ) 

2641 

2642 def _start_persistent_nbd_server(self, volume_name): 

2643 pid_path = None 

2644 nbd_path = None 

2645 nbd_server = None 

2646 

2647 try: 

2648 # We use a precomputed device size. 

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

2650 if volume_name == HA_VOLUME_NAME: 

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

2652 port = '8076' 

2653 device_size = 4 * 1024 * 1024 

2654 else: 

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

2656 port = '8077' 

2657 device_size = 256 * 1024 * 1024 

2658 

2659 try: 

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

2661 ips = util.get_host_addresses(session) 

2662 except Exception as e: 

2663 _, ips = get_ips_from_xha_config_file() 

2664 if not ips: 

2665 raise Exception( 

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

2667 ) 

2668 ips = ips.values() 

2669 

2670 arguments = [ 

2671 'nbd-http-server', 

2672 '--socket-path', 

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

2674 '--nbd-name', 

2675 volume_name, 

2676 '--urls', 

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

2678 '--device-size', 

2679 str(device_size) 

2680 ] 

2681 

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

2683 nbd_server = subprocess.Popen( 

2684 [FORK_LOG_DAEMON] + arguments, 

2685 stdout=subprocess.PIPE, 

2686 stderr=subprocess.STDOUT, 

2687 universal_newlines=True, 

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

2689 # touch the current one. 

2690 preexec_fn=os.setsid 

2691 ) 

2692 

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

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

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

2696 

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

2698 def get_nbd_path(): 

2699 while nbd_server.poll() is None: 

2700 line = nbd_server.stdout.readline() 

2701 match = reg_nbd_path.search(line) 

2702 if match: 

2703 return match.group(1) 

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

2705 try: 

2706 nbd_path = util.timeout_call(10, get_nbd_path) 

2707 if nbd_path is None: 

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

2709 except util.TimeoutException: 

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

2711 

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

2713 os.symlink(nbd_path, self.path) 

2714 except Exception as e: 

2715 if pid_path: 

2716 try: 

2717 os.remove(pid_path) 

2718 except Exception: 

2719 pass 

2720 

2721 if nbd_path: 

2722 try: 

2723 os.remove(nbd_path) 

2724 except Exception: 

2725 pass 

2726 

2727 if nbd_server: 

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

2729 try: 

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

2731 except: 

2732 pass 

2733 

2734 raise xs_errors.XenError( 

2735 'VDIUnavailable', 

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

2737 ) 

2738 

2739 @classmethod 

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

2741 try: 

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

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

2744 return 

2745 

2746 pid = None 

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

2748 try: 

2749 pid = int(pid_file.read()) 

2750 except Exception: 

2751 pass 

2752 

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

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

2755 try: 

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

2757 except Exception as e: 

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

2759 

2760 os.remove(path) 

2761 except: 

2762 pass 

2763 

2764 @classmethod 

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

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

2767 

2768 @classmethod 

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

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

2771 

2772 def _check_http_nbd_volume_name(self): 

2773 volume_name = self.path[14:] 

2774 if volume_name not in [ 

2775 HA_VOLUME_NAME, REDO_LOG_VOLUME_NAME 

2776 ]: 

2777 raise xs_errors.XenError( 

2778 'VDIUnavailable', 

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

2780 ) 

2781 return volume_name 

2782 

2783 def _attach_using_http_nbd(self): 

2784 volume_name = self._check_http_nbd_volume_name() 

2785 

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

2787 self._kill_persistent_nbd_server(volume_name) 

2788 self._kill_persistent_http_server(volume_name) 

2789 

2790 # 0. Fetch drbd path. 

2791 must_get_device_path = True 

2792 if not self.sr.is_master(): 

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

2794 try: 

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

2796 except Exception as e: 

2797 raise xs_errors.XenError( 

2798 'VDIUnavailable', 

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

2800 .format(self.uuid, e) 

2801 ) 

2802 

2803 hostname = socket.gethostname() 

2804 must_get_device_path = hostname in volume_info.diskful 

2805 

2806 drbd_path = None 

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

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

2809 # or diskful available to init HA. 

2810 # It also avoid this error in xensource.log 

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

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

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

2814 available = False 

2815 try: 

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

2817 available = util.pathexists(drbd_path) 

2818 except Exception: 

2819 pass 

2820 

2821 if not available: 

2822 raise xs_errors.XenError( 

2823 'VDIUnavailable', 

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

2825 ) 

2826 

2827 # 1. Prepare http-nbd folder. 

2828 try: 

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

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

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

2832 os.remove(self.path) 

2833 except OSError as e: 

2834 if e.errno != errno.EEXIST: 

2835 raise xs_errors.XenError( 

2836 'VDIUnavailable', 

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

2838 ) 

2839 

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

2841 http_service = None 

2842 if drbd_path: 

2843 assert(drbd_path in ( 

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

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

2846 )) 

2847 self._start_persistent_http_server(volume_name) 

2848 

2849 # 3. Start NBD server in all cases. 

2850 try: 

2851 self._start_persistent_nbd_server(volume_name) 

2852 except Exception as e: 

2853 if drbd_path: 

2854 self._kill_persistent_http_server(volume_name) 

2855 raise 

2856 

2857 self.attached = True 

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

2859 

2860 def _detach_using_http_nbd(self): 

2861 volume_name = self._check_http_nbd_volume_name() 

2862 self._kill_persistent_nbd_server(volume_name) 

2863 self._kill_persistent_http_server(volume_name) 

2864 

2865# ------------------------------------------------------------------------------ 

2866 

2867 

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

2869 def run(): 

2870 SRCommand.run(LinstorSR, DRIVER_INFO) 

2871 

2872 if not TRACE_PERFS: 

2873 run() 

2874 else: 

2875 util.make_profile('LinstorSR', run) 

2876else: 

2877 SR.registerSR(LinstorSR)