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 

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 PERSISTENT_PREFIX 

29 

30 LINSTOR_AVAILABLE = True 

31except ImportError: 

32 PERSISTENT_PREFIX = 'unknown' 

33 

34 LINSTOR_AVAILABLE = False 

35 

36from lock import Lock, LOCK_TYPE_GC_RUNNING 

37import blktap2 

38import cleanup 

39import errno 

40import functools 

41import lvutil 

42import os 

43import re 

44import scsiutil 

45import signal 

46import socket 

47import SR 

48import SRCommand 

49import subprocess 

50import sys 

51import time 

52import traceback 

53import util 

54import VDI 

55import vhdutil 

56import xml.etree.ElementTree as xml_parser 

57import xmlrpc.client 

58import xs_errors 

59 

60from srmetadata import \ 

61 NAME_LABEL_TAG, NAME_DESCRIPTION_TAG, IS_A_SNAPSHOT_TAG, SNAPSHOT_OF_TAG, \ 

62 TYPE_TAG, VDI_TYPE_TAG, READ_ONLY_TAG, SNAPSHOT_TIME_TAG, \ 

63 METADATA_OF_POOL_TAG 

64 

65HIDDEN_TAG = 'hidden' 

66 

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

68 

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

70 

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

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

73# specific conditions: 

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

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

76USE_HTTP_NBD_SERVERS = True 

77 

78# Useful flag to trace calls using cProfile. 

79TRACE_PERFS = False 

80 

81# Enable/Disable VHD key hash support. 

82USE_KEY_HASH = False 

83 

84# Special volumes. 

85HA_VOLUME_NAME = PERSISTENT_PREFIX + 'ha-statefile' 

86REDO_LOG_VOLUME_NAME = PERSISTENT_PREFIX + 'redo-log' 

87 

88# ============================================================================== 

89 

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

91# 'VDI_CONFIG_CBT', 'SR_PROBE' 

92 

93CAPABILITIES = [ 

94 'ATOMIC_PAUSE', 

95 'SR_UPDATE', 

96 'VDI_CREATE', 

97 'VDI_DELETE', 

98 'VDI_UPDATE', 

99 'VDI_ATTACH', 

100 'VDI_DETACH', 

101 'VDI_ACTIVATE', 

102 'VDI_DEACTIVATE', 

103 'VDI_CLONE', 

104 'VDI_MIRROR', 

105 'VDI_RESIZE', 

106 'VDI_SNAPSHOT', 

107 'VDI_GENERATE_CONFIG' 

108] 

109 

110CONFIGURATION = [ 

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

112 ['redundancy', 'replication count'], 

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

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

115] 

116 

117DRIVER_INFO = { 

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

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

120 'vendor': 'Vates', 

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

122 'driver_version': '1.0', 

123 'required_api_version': '1.0', 

124 'capabilities': CAPABILITIES, 

125 'configuration': CONFIGURATION 

126} 

127 

128DRIVER_CONFIG = {'ATTACH_FROM_CONFIG_WITH_TAPDISK': False} 

129 

130OPS_EXCLUSIVE = [ 

131 'sr_create', 'sr_delete', 'sr_attach', 'sr_detach', 'sr_scan', 

132 'sr_update', 'sr_probe', 'vdi_init', 'vdi_create', 'vdi_delete', 

133 'vdi_attach', 'vdi_detach', 'vdi_clone', 'vdi_snapshot', 

134] 

135 

136# ============================================================================== 

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

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

139 

140 

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

142 volume_metadata = linstor.get_volume_metadata(vdi_uuid) 

143 image_type = volume_metadata.get(VDI_TYPE_TAG) 

144 if image_type == vhdutil.VDI_TYPE_RAW: 

145 return 

146 

147 device_path = linstor.get_device_path(vdi_uuid) 

148 

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

150 # there is nothing to do. 

151 vhd_size = LinstorVhdUtil.compute_volume_size( 

152 # TODO: Replace pylint comment with this feature when possible: 

153 # https://github.com/PyCQA/pylint/pull/2926 

154 LinstorVhdUtil(session, linstor).get_size_virt(vdi_uuid), # pylint: disable = E1120 

155 image_type 

156 ) 

157 

158 volume_info = linstor.get_volume_info(vdi_uuid) 

159 volume_size = volume_info.virtual_size 

160 

161 if vhd_size > volume_size: 

162 LinstorVhdUtil(session, linstor).inflate( 

163 journaler, vdi_uuid, device_path, vhd_size, volume_size 

164 ) 

165 

166 

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

168 volume_metadata = linstor.get_volume_metadata(vdi_uuid) 

169 image_type = volume_metadata.get(VDI_TYPE_TAG) 

170 if image_type == vhdutil.VDI_TYPE_RAW: 

171 return 

172 

173 def check_vbd_count(): 

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

175 vbds = session.xenapi.VBD.get_all_records_where( 

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

177 ) 

178 

179 num_plugged = 0 

180 for vbd_rec in vbds.values(): 

181 if vbd_rec['currently_attached']: 

182 num_plugged += 1 

183 if num_plugged > 1: 

184 raise xs_errors.XenError( 

185 'VDIUnavailable', 

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

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

188 ) 

189 

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

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

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

193 

194 device_path = linstor.get_device_path(vdi_uuid) 

195 vhdutil_inst = LinstorVhdUtil(session, linstor) 

196 new_volume_size = LinstorVolumeManager.round_up_volume_size( 

197 # TODO: Replace pylint comment with this feature when possible: 

198 # https://github.com/PyCQA/pylint/pull/2926 

199 vhdutil_inst.get_size_phys(vdi_uuid) # pylint: disable = E1120 

200 ) 

201 

202 volume_info = linstor.get_volume_info(vdi_uuid) 

203 old_volume_size = volume_info.virtual_size 

204 vhdutil_inst.deflate(device_path, new_volume_size, old_volume_size) 

205 

206 

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

208 # This function must always return without errors. 

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

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

211 try: 

212 detach_thin_impl(session, linstor, sr_uuid, vdi_uuid) 

213 except Exception as e: 

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

215 

216 

217def get_ips_from_xha_config_file(): 

218 ips = dict() 

219 host_id = None 

220 try: 

221 # Ensure there is no dirty read problem. 

222 # For example if the HA is reloaded. 

223 tree = util.retry( 

224 lambda: xml_parser.parse(XHA_CONFIG_PATH), 

225 maxretry=10, 

226 period=1 

227 ) 

228 except: 

229 return (None, ips) 

230 

231 def parse_host_nodes(ips, node): 

232 current_id = None 

233 current_ip = None 

234 

235 for sub_node in node: 

236 if sub_node.tag == 'IPaddress': 

237 current_ip = sub_node.text 

238 elif sub_node.tag == 'HostID': 

239 current_id = sub_node.text 

240 else: 

241 continue 

242 

243 if current_id and current_ip: 

244 ips[current_id] = current_ip 

245 return 

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

247 

248 def parse_common_config(ips, node): 

249 for sub_node in node: 

250 if sub_node.tag == 'host': 

251 parse_host_nodes(ips, sub_node) 

252 

253 def parse_local_config(ips, node): 

254 for sub_node in node: 

255 if sub_node.tag == 'localhost': 

256 for host_node in sub_node: 

257 if host_node.tag == 'HostID': 

258 return host_node.text 

259 

260 for node in tree.getroot(): 

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

262 parse_common_config(ips, node) 

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

264 host_id = parse_local_config(ips, node) 

265 else: 

266 continue 

267 

268 if ips and host_id: 

269 break 

270 

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

272 

273 

274def activate_lvm_group(group_name): 

275 path = group_name.split('/') 

276 assert path and len(path) <= 2 

277 try: 

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

279 except Exception as e: 

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

281 

282# ============================================================================== 

283 

284# Usage example: 

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

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

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

288 

289 

290class LinstorSR(SR.SR): 

291 DRIVER_TYPE = 'linstor' 

292 

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

294 PROVISIONING_DEFAULT = 'thin' 

295 

296 MANAGER_PLUGIN = 'linstor-manager' 

297 

298 INIT_STATUS_NOT_SET = 0 

299 INIT_STATUS_IN_PROGRESS = 1 

300 INIT_STATUS_OK = 2 

301 INIT_STATUS_FAIL = 3 

302 

303 # -------------------------------------------------------------------------- 

304 # SR methods. 

305 # -------------------------------------------------------------------------- 

306 

307 @override 

308 @staticmethod 

309 def handles(type) -> bool: 

310 return type == LinstorSR.DRIVER_TYPE 

311 

312 @override 

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

314 if not LINSTOR_AVAILABLE: 

315 raise util.SMException( 

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

317 ) 

318 

319 # Check parameters. 

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

321 raise xs_errors.XenError('LinstorConfigGroupNameMissing') 

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

323 raise xs_errors.XenError('LinstorConfigRedundancyMissing') 

324 

325 self.driver_config = DRIVER_CONFIG 

326 

327 # Check provisioning config. 

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

329 if provisioning: 

330 if provisioning in self.PROVISIONING_TYPES: 

331 self._provisioning = provisioning 

332 else: 

333 raise xs_errors.XenError( 

334 'InvalidArg', 

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

336 self.PROVISIONING_TYPES 

337 ) 

338 ) 

339 else: 

340 self._provisioning = self.PROVISIONING_DEFAULT 

341 

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

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

344 util.strtobool(monitor_db_quorum) 

345 

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

347 # 'vdi_attach_from_config' command is executed. 

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

349 if self._has_session: 

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

351 else: 

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

353 

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

355 if provisioning in self.PROVISIONING_TYPES: 

356 self._provisioning = provisioning 

357 

358 # Define properties for SR parent class. 

359 self.ops_exclusive = OPS_EXCLUSIVE 

360 self.path = LinstorVolumeManager.DEV_ROOT_PATH 

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

362 self.sr_vditype = SR.DEFAULT_TAP 

363 

364 if self.cmd == 'sr_create': 

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

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

367 self._journaler = None 

368 

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

370 

371 self._vdi_shared_time = 0 

372 

373 self._init_status = self.INIT_STATUS_NOT_SET 

374 

375 self._vdis_loaded = False 

376 self._all_volume_info_cache = None 

377 self._all_volume_metadata_cache = None 

378 

379 # To remove in python 3.10. 

380 # Use directly @staticmethod instead. 

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

382 def _locked_load(method): 

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

384 self._init_status = self.INIT_STATUS_OK 

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

386 

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

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

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

390 activate_lvm_group(self._group_name) 

391 

392 if not self._has_session: 

393 if self.srcmd.cmd in ( 

394 'vdi_attach_from_config', 

395 'vdi_detach_from_config', 

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

397 # empty command. 

398 None 

399 ): 

400 def create_linstor(uri, attempt_count=30): 

401 self._linstor = LinstorVolumeManager( 

402 uri, 

403 self._group_name, 

404 logger=util.SMlog, 

405 attempt_count=attempt_count 

406 ) 

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

408 # I.e. not an HA volume. 

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

410 

411 controller_uri = get_controller_uri() 

412 if controller_uri: 

413 create_linstor(controller_uri) 

414 else: 

415 def connect(): 

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

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

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

419 controller_uri = 'linstor://' + ip 

420 try: 

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

422 create_linstor(controller_uri, attempt_count=0) 

423 return controller_uri 

424 except: 

425 pass 

426 

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

428 if not controller_uri: 

429 raise xs_errors.XenError( 

430 'SRUnavailable', 

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

432 ) 

433 

434 self._journaler = LinstorJournaler( 

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

436 ) 

437 

438 if self.srcmd.cmd is None: 

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

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

441 

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

443 

444 if not self.is_master(): 

445 if self.cmd in [ 

446 'sr_create', 'sr_delete', 'sr_update', 'sr_probe', 

447 'sr_scan', 'vdi_create', 'vdi_delete', 'vdi_resize', 

448 'vdi_snapshot', 'vdi_clone' 

449 ]: 

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

451 raise xs_errors.XenError('LinstorMaster') 

452 

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

454 # the VDI before the LinstorJournaler/LinstorVolumeManager 

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

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

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

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

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

460 self._vdi_shared_time = time.time() 

461 

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

463 try: 

464 self._reconnect() 

465 except Exception as e: 

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

467 

468 if self._linstor: 

469 try: 

470 hosts = self._linstor.disconnected_hosts 

471 except Exception as e: 

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

473 

474 if hosts: 

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

476 

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

478 if ( 

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

480 self.cmd != 'vdi_create' 

481 ): 

482 self._linstor.ensure_volume_is_not_locked( 

483 self.srcmd.params['vdi_uuid'] 

484 ) 

485 

486 try: 

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

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

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

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

491 # 

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

493 # resourceless volumes. 

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

495 'vdi_attach', 'vdi_detach', 

496 'vdi_activate', 'vdi_deactivate', 

497 'vdi_epoch_begin', 'vdi_epoch_end', 

498 'vdi_update', 'vdi_destroy' 

499 ]: 

500 load_vdis = ( 

501 self.cmd == 'sr_scan' or 

502 self.cmd == 'sr_attach' 

503 ) or len( 

504 self._journaler.get_all(LinstorJournaler.INFLATE) 

505 ) or len( 

506 self._journaler.get_all(LinstorJournaler.CLONE) 

507 ) 

508 

509 if load_vdis: 

510 self._load_vdis() 

511 

512 self._linstor.remove_resourceless_volumes() 

513 

514 self._synchronize_metadata() 

515 except Exception as e: 

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

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

518 # from the XAPI database otherwise. 

519 raise e 

520 util.SMlog( 

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

522 ) 

523 util.SMlog(traceback.format_exc()) 

524 

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

526 

527 @functools.wraps(wrapped_method) 

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

529 if self._init_status in \ 

530 (self.INIT_STATUS_OK, self.INIT_STATUS_IN_PROGRESS): 

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

532 if self._init_status == self.INIT_STATUS_FAIL: 

533 util.SMlog( 

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

535 .format(method) 

536 ) 

537 else: 

538 try: 

539 self._init_status = self.INIT_STATUS_IN_PROGRESS 

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

541 except Exception: 

542 if self._init_status != self.INIT_STATUS_OK: 

543 self._init_status = self.INIT_STATUS_FAIL 

544 raise 

545 

546 return wrap 

547 

548 @override 

549 def cleanup(self) -> None: 

550 if self._vdi_shared_time: 

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

552 

553 @override 

554 @_locked_load 

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

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

557 

558 host_adresses = util.get_host_addresses(self.session) 

559 if self._redundancy > len(host_adresses): 

560 raise xs_errors.XenError( 

561 'LinstorSRCreate', 

562 opterr='Redundancy greater than host count' 

563 ) 

564 

565 xenapi = self.session.xenapi 

566 srs = xenapi.SR.get_all_records_where( 

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

568 ) 

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

570 

571 for sr in srs.values(): 

572 for pbd in sr['PBDs']: 

573 device_config = xenapi.PBD.get_device_config(pbd) 

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

575 if group_name and group_name == self._group_name: 

576 raise xs_errors.XenError( 

577 'LinstorSRCreate', 

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

579 xenapi.PBD.get_uuid(pbd) 

580 ) 

581 ) 

582 

583 if srs: 

584 raise xs_errors.XenError( 

585 'LinstorSRCreate', 

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

587 ) 

588 

589 online_hosts = util.get_online_hosts(self.session) 

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

591 raise xs_errors.XenError( 

592 'LinstorSRCreate', 

593 opterr='Not enough online hosts' 

594 ) 

595 

596 ips = {} 

597 for host_ref in online_hosts: 

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

599 hostname = record['hostname'] 

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

601 

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

603 raise xs_errors.XenError( 

604 'LinstorSRCreate', 

605 opterr='Multiple hosts with same hostname' 

606 ) 

607 

608 # Ensure ports are opened and LINSTOR satellites 

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

610 # must be stopped. 

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

612 

613 # Create SR. 

614 # Throw if the SR already exists. 

615 try: 

616 self._linstor = LinstorVolumeManager.create_sr( 

617 self._group_name, 

618 ips, 

619 self._redundancy, 

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

621 auto_quorum=self._monitor_db_quorum, 

622 logger=util.SMlog 

623 ) 

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

625 

626 util.SMlog( 

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

628 ) 

629 self._update_drbd_reactor_on_all_hosts(enabled=True) 

630 except Exception as e: 

631 if not self._linstor: 

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

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

634 

635 try: 

636 self._linstor.destroy() 

637 except Exception as e2: 

638 util.SMlog( 

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

640 .format(e2) 

641 ) 

642 raise e 

643 

644 @override 

645 @_locked_load 

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

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

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

649 

650 assert self._linstor 

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

652 raise xs_errors.XenError('SRNotEmpty') 

653 

654 node_name = get_controller_node_name() 

655 if not node_name: 

656 raise xs_errors.XenError( 

657 'LinstorSRDelete', 

658 opterr='Cannot get controller node name' 

659 ) 

660 

661 host_ref = None 

662 if node_name == 'localhost': 

663 host_ref = util.get_this_host_ref(self.session) 

664 else: 

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

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

667 if r_name == node_name: 

668 host_ref = slave 

669 break 

670 

671 if not host_ref: 

672 raise xs_errors.XenError( 

673 'LinstorSRDelete', 

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

675 node_name 

676 ) 

677 ) 

678 

679 try: 

680 self._update_drbd_reactor_on_all_hosts( 

681 controller_node_name=node_name, enabled=False 

682 ) 

683 

684 args = { 

685 'groupName': self._group_name, 

686 } 

687 self._exec_manager_command( 

688 host_ref, 'destroy', args, 'LinstorSRDelete' 

689 ) 

690 except Exception as e: 

691 try: 

692 self._update_drbd_reactor_on_all_hosts( 

693 controller_node_name=node_name, enabled=True 

694 ) 

695 except Exception as e2: 

696 util.SMlog( 

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

698 .format(e2) 

699 ) 

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

701 raise xs_errors.XenError( 

702 'LinstorSRDelete', 

703 opterr=str(e) 

704 ) 

705 

706 Lock.cleanupAll(self.uuid) 

707 

708 @override 

709 @_locked_load 

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

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

712 

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

714 if not self._linstor: 

715 raise xs_errors.XenError( 

716 'SRUnavailable', 

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

718 ) 

719 

720 self._update_stats(0) 

721 

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

723 xenapi = self.session.xenapi 

724 self._linstor.metadata = { 

725 NAME_LABEL_TAG: util.to_plain_string( 

726 xenapi.SR.get_name_label(self.sr_ref) 

727 ), 

728 NAME_DESCRIPTION_TAG: util.to_plain_string( 

729 xenapi.SR.get_name_description(self.sr_ref) 

730 ) 

731 } 

732 

733 @override 

734 @_locked_load 

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

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

737 

738 if not self._linstor: 

739 raise xs_errors.XenError( 

740 'SRUnavailable', 

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

742 ) 

743 

744 @override 

745 @_locked_load 

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

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

748 cleanup.abort(self.uuid) 

749 

750 @override 

751 @_locked_load 

752 def probe(self) -> str: 

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

754 # TODO 

755 return '' 

756 

757 @override 

758 @_locked_load 

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

760 if self._init_status == self.INIT_STATUS_FAIL: 

761 return 

762 

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

764 if not self._linstor: 

765 raise xs_errors.XenError( 

766 'SRUnavailable', 

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

768 ) 

769 

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

771 # are loaded. 

772 self._load_vdis() 

773 self._update_physical_size() 

774 

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

776 if self.vdis[vdi_uuid].deleted: 

777 del self.vdis[vdi_uuid] 

778 

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

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

781 try: 

782 self._linstor.get_database_path() 

783 except Exception as e: 

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

785 # VDIs in the XAPI database... 

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

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

788 ): 

789 raise xs_errors.XenError( 

790 'SRUnavailable', 

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

792 ) 

793 

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

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

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

797 self._kick_gc() 

798 

799 def is_master(self): 

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

801 if 'SRmaster' not in self.dconf: 

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

803 else: 

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

805 

806 return self._is_master 

807 

808 @override 

809 @_locked_load 

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

811 return LinstorVDI(self, uuid) 

812 

813 # To remove in python 3.10 

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

815 _locked_load = staticmethod(_locked_load) 

816 

817 # -------------------------------------------------------------------------- 

818 # Lock. 

819 # -------------------------------------------------------------------------- 

820 

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

822 master = util.get_master_ref(self.session) 

823 

824 command = 'lockVdi' 

825 args = { 

826 'groupName': self._group_name, 

827 'srUuid': self.uuid, 

828 'vdiUuid': vdi_uuid, 

829 'locked': str(locked) 

830 } 

831 

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

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

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

835 # 

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

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

838 # to implement without impacting performance. 

839 if not locked: 

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

841 timeout = LinstorVolumeManager.LOCKED_EXPIRATION_DELAY * 0.7 

842 if elapsed_time >= timeout: 

843 util.SMlog( 

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

845 .format(vdi_uuid) 

846 ) 

847 return 

848 

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

850 

851 # -------------------------------------------------------------------------- 

852 # Network. 

853 # -------------------------------------------------------------------------- 

854 

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

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

857 host_uuid = host_rec['uuid'] 

858 

859 try: 

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

861 host_ref, self.MANAGER_PLUGIN, command, args 

862 ) 

863 except Exception as e: 

864 util.SMlog( 

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

866 host_uuid, self.MANAGER_PLUGIN, command, args 

867 ) 

868 ) 

869 raise e 

870 

871 util.SMlog( 

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

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

874 ) 

875 ) 

876 if ret == 'False': 

877 raise xs_errors.XenError( 

878 error, 

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

880 ) 

881 

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

883 self._exec_manager_command( 

884 host, 

885 'prepareSr' if enabled else 'releaseSr', 

886 {'groupName': group_name}, 

887 'SRUnavailable' 

888 ) 

889 

890 def _prepare_sr_on_all_hosts(self, group_name, enabled): 

891 master = util.get_master_ref(self.session) 

892 self._prepare_sr(master, group_name, enabled) 

893 

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

895 self._prepare_sr(slave, group_name, enabled) 

896 

897 def _update_drbd_reactor(self, host, enabled): 

898 self._exec_manager_command( 

899 host, 

900 'updateDrbdReactor', 

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

902 'SRUnavailable' 

903 ) 

904 

905 def _update_drbd_reactor_on_all_hosts( 

906 self, enabled, controller_node_name=None 

907 ): 

908 if controller_node_name == 'localhost': 

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

910 util.get_this_host_ref(self.session) 

911 )['hostname'] 

912 assert controller_node_name 

913 assert controller_node_name != 'localhost' 

914 

915 controller_host = None 

916 secondary_hosts = [] 

917 

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

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

920 hostname = host_rec['hostname'] 

921 if controller_node_name == hostname: 

922 controller_host = host_ref 

923 else: 

924 secondary_hosts.append((host_ref, hostname)) 

925 

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

927 if controller_node_name and not controller_host: 

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

929 controller_node_name 

930 )) 

931 

932 if enabled and controller_host: 

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

934 action_name, controller_node_name 

935 )) 

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

937 # node name first. 

938 self._update_drbd_reactor(controller_host, enabled) 

939 

940 for host_ref, hostname in secondary_hosts: 

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

942 action_name, hostname 

943 )) 

944 self._update_drbd_reactor(host_ref, enabled) 

945 

946 if not enabled and controller_host: 

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

948 action_name, controller_node_name 

949 )) 

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

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

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

953 self._update_drbd_reactor(controller_host, enabled) 

954 

955 # -------------------------------------------------------------------------- 

956 # Metadata. 

957 # -------------------------------------------------------------------------- 

958 

959 def _synchronize_metadata_and_xapi(self): 

960 try: 

961 # First synch SR parameters. 

962 self.update(self.uuid) 

963 

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

965 xenapi = self.session.xenapi 

966 volumes_metadata = self._linstor.get_volumes_with_metadata() 

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

968 try: 

969 vdi_ref = xenapi.VDI.get_by_uuid(vdi_uuid) 

970 except Exception: 

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

972 continue 

973 

974 label = util.to_plain_string( 

975 xenapi.VDI.get_name_label(vdi_ref) 

976 ) 

977 description = util.to_plain_string( 

978 xenapi.VDI.get_name_description(vdi_ref) 

979 ) 

980 

981 if ( 

982 volume_metadata.get(NAME_LABEL_TAG) != label or 

983 volume_metadata.get(NAME_DESCRIPTION_TAG) != description 

984 ): 

985 self._linstor.update_volume_metadata(vdi_uuid, { 

986 NAME_LABEL_TAG: label, 

987 NAME_DESCRIPTION_TAG: description 

988 }) 

989 except Exception as e: 

990 raise xs_errors.XenError( 

991 'MetadataError', 

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

993 ) 

994 

995 def _synchronize_metadata(self): 

996 if not self.is_master(): 

997 return 

998 

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

1000 if self.cmd == 'sr_attach': 

1001 try: 

1002 util.SMlog( 

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

1004 ) 

1005 self._synchronize_metadata_and_xapi() 

1006 except Exception as e: 

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

1008 

1009 # -------------------------------------------------------------------------- 

1010 # Stats. 

1011 # -------------------------------------------------------------------------- 

1012 

1013 def _update_stats(self, virt_alloc_delta): 

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

1015 self.sr_ref 

1016 )) 

1017 

1018 # Update size attributes of the SR parent class. 

1019 self.virtual_allocation = valloc + virt_alloc_delta 

1020 

1021 self._update_physical_size() 

1022 

1023 # Notify SR parent class. 

1024 self._db_update() 

1025 

1026 def _update_physical_size(self): 

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

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

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

1030 self.physical_size = min_physical_size * pool_count // \ 

1031 self._linstor.redundancy 

1032 

1033 self.physical_utilisation = self._linstor.allocated_volume_size 

1034 

1035 # -------------------------------------------------------------------------- 

1036 # VDIs. 

1037 # -------------------------------------------------------------------------- 

1038 

1039 def _load_vdis(self): 

1040 if self._vdis_loaded: 

1041 return 

1042 

1043 assert self.is_master() 

1044 

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

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

1047 # enjoy it with a few lines. 

1048 self._create_linstor_cache() 

1049 self._load_vdis_ex() 

1050 self._destroy_linstor_cache() 

1051 

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

1053 self._vdis_loaded = True 

1054 

1055 self._undo_all_journal_transactions() 

1056 

1057 def _load_vdis_ex(self): 

1058 # 1. Get existing VDIs in XAPI. 

1059 xenapi = self.session.xenapi 

1060 xapi_vdi_uuids = set() 

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

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

1063 

1064 # 2. Get volumes info. 

1065 all_volume_info = self._all_volume_info_cache 

1066 volumes_metadata = self._all_volume_metadata_cache 

1067 

1068 # 3. Get CBT vdis. 

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

1070 cbt_vdis = set() 

1071 for volume_metadata in volumes_metadata.values(): 

1072 cbt_uuid = volume_metadata.get(CBTLOG_TAG) 

1073 if cbt_uuid: 

1074 cbt_vdis.add(cbt_uuid) 

1075 

1076 introduce = False 

1077 

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

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

1080 has_clone_entries = list(self._journaler.get_all( 

1081 LinstorJournaler.CLONE 

1082 ).items()) 

1083 

1084 if has_clone_entries: 

1085 util.SMlog( 

1086 'Cannot introduce VDIs during scan because it exists ' 

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

1088 ) 

1089 else: 

1090 introduce = True 

1091 

1092 # 4. Now check all volume info. 

1093 vdi_to_snaps = {} 

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

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

1096 continue 

1097 

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

1099 if vdi_uuid not in xapi_vdi_uuids: 

1100 if not introduce: 

1101 continue 

1102 

1103 if vdi_uuid.startswith('DELETED_'): 

1104 continue 

1105 

1106 volume_metadata = volumes_metadata.get(vdi_uuid) 

1107 if not volume_metadata: 

1108 util.SMlog( 

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

1110 .format(vdi_uuid) 

1111 ) 

1112 continue 

1113 

1114 util.SMlog( 

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

1116 'LINSTOR and not in XAPI...' 

1117 .format(vdi_uuid) 

1118 ) 

1119 

1120 try: 

1121 self._linstor.get_device_path(vdi_uuid) 

1122 except Exception as e: 

1123 util.SMlog( 

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

1125 .format(vdi_uuid, e) 

1126 ) 

1127 continue 

1128 

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

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

1131 vdi_type = volume_metadata.get(VDI_TYPE_TAG) 

1132 

1133 if not vdi_type: 

1134 util.SMlog( 

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

1136 'without vdi_type' 

1137 ) 

1138 continue 

1139 

1140 sm_config = { 

1141 'vdi_type': vdi_type 

1142 } 

1143 

1144 if vdi_type == vhdutil.VDI_TYPE_RAW: 

1145 managed = not volume_metadata.get(HIDDEN_TAG) 

1146 elif vdi_type == vhdutil.VDI_TYPE_VHD: 

1147 vhd_info = self._vhdutil.get_vhd_info(vdi_uuid) 

1148 managed = not vhd_info.hidden 

1149 if vhd_info.parentUuid: 

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

1151 else: 

1152 util.SMlog( 

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

1154 .format(vdi_uuid, vdi_type) 

1155 ) 

1156 continue 

1157 

1158 util.SMlog( 

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

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

1161 name_label, 

1162 volume_info.virtual_size, 

1163 volume_info.allocated_size 

1164 ) 

1165 ) 

1166 

1167 vdi_ref = xenapi.VDI.db_introduce( 

1168 vdi_uuid, 

1169 name_label, 

1170 volume_metadata.get(NAME_DESCRIPTION_TAG) or '', 

1171 self.sr_ref, 

1172 type, 

1173 False, # sharable 

1174 bool(volume_metadata.get(READ_ONLY_TAG)), 

1175 {}, # other_config 

1176 vdi_uuid, # location 

1177 {}, # xenstore_data 

1178 sm_config, 

1179 managed, 

1180 str(volume_info.virtual_size), 

1181 str(volume_info.allocated_size) 

1182 ) 

1183 

1184 is_a_snapshot = volume_metadata.get(IS_A_SNAPSHOT_TAG) 

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

1186 if is_a_snapshot: 

1187 xenapi.VDI.set_snapshot_time( 

1188 vdi_ref, 

1189 xmlrpc.client.DateTime( 

1190 volume_metadata[SNAPSHOT_TIME_TAG] or 

1191 '19700101T00:00:00Z' 

1192 ) 

1193 ) 

1194 

1195 snap_uuid = volume_metadata[SNAPSHOT_OF_TAG] 

1196 if snap_uuid in vdi_to_snaps: 

1197 vdi_to_snaps[snap_uuid].append(vdi_uuid) 

1198 else: 

1199 vdi_to_snaps[snap_uuid] = [vdi_uuid] 

1200 

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

1202 vdi = self.vdi(vdi_uuid) 

1203 self.vdis[vdi_uuid] = vdi 

1204 

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

1206 # TODO: Replace pylint comment with this feature when possible: 

1207 # https://github.com/PyCQA/pylint/pull/2926 

1208 vdi.sm_config_override['key_hash'] = \ 

1209 self._vhdutil.get_key_hash(vdi_uuid) # pylint: disable = E1120 

1210 

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

1212 # or already in XAPI. 

1213 cbt_uuid = volume_metadata.get(CBTLOG_TAG) 

1214 if cbt_uuid in cbt_vdis: 

1215 vdi_ref = xenapi.VDI.get_by_uuid(vdi_uuid) 

1216 xenapi.VDI.set_cbt_enabled(vdi_ref, True) 

1217 # For existing VDIs, update local state too. 

1218 # Scan in base class SR updates existing VDIs 

1219 # again based on local states. 

1220 self.vdis[vdi_uuid].cbt_enabled = True 

1221 cbt_vdis.remove(cbt_uuid) 

1222 

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

1224 for src_uuid in vdi_to_snaps: 

1225 try: 

1226 src_ref = xenapi.VDI.get_by_uuid(src_uuid) 

1227 except Exception: 

1228 # The source VDI no longer exists, continue. 

1229 continue 

1230 

1231 for snap_uuid in vdi_to_snaps[src_uuid]: 

1232 try: 

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

1234 snap_ref = xenapi.VDI.get_by_uuid(snap_uuid) 

1235 xenapi.VDI.set_snapshot_of(snap_ref, src_ref) 

1236 except Exception as e: 

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

1238 

1239 # TODO: Check correctly how to use CBT. 

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

1241 

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

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

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

1245 for cbt_uuid in cbt_vdis: 

1246 new_vdi = self.vdi(cbt_uuid) 

1247 new_vdi.ty = 'cbt_metadata' 

1248 new_vdi.cbt_enabled = True 

1249 self.vdis[cbt_uuid] = new_vdi 

1250 

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

1252 self.virtual_allocation = 0 

1253 

1254 # 8. Build geneology. 

1255 geneology = {} 

1256 

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

1258 if vdi.parent: 

1259 if vdi.parent in self.vdis: 

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

1261 if vdi.parent in geneology: 

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

1263 else: 

1264 geneology[vdi.parent] = [vdi_uuid] 

1265 if not vdi.hidden: 

1266 self.virtual_allocation += vdi.size 

1267 

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

1269 # will be GC'ed. 

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

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

1272 util.SMlog( 

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

1274 ) 

1275 del self.vdis[vdi_uuid] 

1276 

1277 # -------------------------------------------------------------------------- 

1278 # Journals. 

1279 # -------------------------------------------------------------------------- 

1280 

1281 def _get_vdi_path_and_parent(self, vdi_uuid, volume_name): 

1282 try: 

1283 device_path = self._linstor.build_device_path(volume_name) 

1284 if not util.pathexists(device_path): 

1285 return (None, None) 

1286 

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

1288 volume_metadata = self._linstor.get_volume_metadata(vdi_uuid) 

1289 vdi_type = volume_metadata[VDI_TYPE_TAG] 

1290 if vdi_type == vhdutil.VDI_TYPE_RAW: 

1291 return (device_path, None) 

1292 

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

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

1295 return (None, None) 

1296 

1297 vhd_info = self._vhdutil.get_vhd_info(vdi_uuid) 

1298 if vhd_info: 

1299 return (device_path, vhd_info.parentUuid) 

1300 except Exception as e: 

1301 util.SMlog( 

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

1303 .format(e) 

1304 ) 

1305 return (None, None) 

1306 

1307 def _undo_all_journal_transactions(self): 

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

1309 self.lock.acquire() 

1310 try: 

1311 self._handle_interrupted_inflate_ops() 

1312 self._handle_interrupted_clone_ops() 

1313 pass 

1314 finally: 

1315 self.lock.release() 

1316 

1317 def _handle_interrupted_inflate_ops(self): 

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

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

1320 self._handle_interrupted_inflate(vdi_uuid, old_size) 

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

1322 

1323 def _handle_interrupted_clone_ops(self): 

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

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

1326 self._handle_interrupted_clone(vdi_uuid, old_size) 

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

1328 

1329 def _handle_interrupted_inflate(self, vdi_uuid, old_size): 

1330 util.SMlog( 

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

1332 .format(vdi_uuid, old_size) 

1333 ) 

1334 

1335 vdi = self.vdis.get(vdi_uuid) 

1336 if not vdi: 

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

1338 return 

1339 

1340 assert not self._all_volume_info_cache 

1341 volume_info = self._linstor.get_volume_info(vdi_uuid) 

1342 

1343 current_size = volume_info.virtual_size 

1344 assert current_size > 0 

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

1346 

1347 def _handle_interrupted_clone( 

1348 self, vdi_uuid, clone_info, force_undo=False 

1349 ): 

1350 util.SMlog( 

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

1352 .format(vdi_uuid, clone_info) 

1353 ) 

1354 

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

1356 

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

1358 volume_names = self._linstor.get_volumes_with_name() 

1359 

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

1361 if base_uuid not in volume_names: 

1362 if vdi_uuid in volume_names: 

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

1364 return 

1365 raise util.SMException( 

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

1367 .format(base_uuid, vdi_uuid) 

1368 ) 

1369 

1370 if force_undo: 

1371 util.SMlog('Explicit revert') 

1372 self._undo_clone( 

1373 volume_names, vdi_uuid, base_uuid, snap_uuid 

1374 ) 

1375 return 

1376 

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

1378 if vdi_uuid not in volume_names or \ 

1379 (snap_uuid and snap_uuid not in volume_names): 

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

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

1382 return 

1383 

1384 vdi_path, vdi_parent_uuid = self._get_vdi_path_and_parent( 

1385 vdi_uuid, volume_names[vdi_uuid] 

1386 ) 

1387 snap_path, snap_parent_uuid = self._get_vdi_path_and_parent( 

1388 snap_uuid, volume_names[snap_uuid] 

1389 ) 

1390 

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

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

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

1394 return 

1395 

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

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

1398 

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

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

1401 base_metadata = self._linstor.get_volume_metadata(base_uuid) 

1402 base_type = base_metadata[VDI_TYPE_TAG] 

1403 

1404 if not util.pathexists(base_path): 

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

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

1407 return 

1408 

1409 # Un-hide the parent. 

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

1411 if base_type == vhdutil.VDI_TYPE_VHD: 

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

1413 if vhd_info.hidden: 

1414 self._vhdutil.set_hidden(base_path, False) 

1415 elif base_type == vhdutil.VDI_TYPE_RAW and \ 

1416 base_metadata.get(HIDDEN_TAG): 

1417 self._linstor.update_volume_metadata( 

1418 base_uuid, {HIDDEN_TAG: False} 

1419 ) 

1420 

1421 # Remove the child nodes. 

1422 if snap_uuid and snap_uuid in volume_names: 

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

1424 

1425 try: 

1426 self._linstor.destroy_volume(snap_uuid) 

1427 except Exception as e: 

1428 util.SMlog( 

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

1430 .format(snap_uuid, e) 

1431 ) 

1432 

1433 if vdi_uuid in volume_names: 

1434 try: 

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

1436 self._linstor.destroy_volume(vdi_uuid) 

1437 except Exception as e: 

1438 util.SMlog( 

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

1440 .format(vdi_uuid, e) 

1441 ) 

1442 # We can get an exception like this: 

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

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

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

1446 # UUID of this bad VDI before. 

1447 self._linstor.update_volume_uuid( 

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

1449 ) 

1450 

1451 # Rename! 

1452 self._linstor.update_volume_uuid(base_uuid, vdi_uuid) 

1453 

1454 # Inflate to the right size. 

1455 if base_type == vhdutil.VDI_TYPE_VHD: 

1456 vdi = self.vdi(vdi_uuid) 

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

1458 self._vhdutil.inflate( 

1459 self._journaler, vdi_uuid, vdi.path, 

1460 volume_size, vdi.capacity 

1461 ) 

1462 self.vdis[vdi_uuid] = vdi 

1463 

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

1465 # flag to facilitate vm deactivate. 

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

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

1468 

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

1470 

1471 # -------------------------------------------------------------------------- 

1472 # Cache. 

1473 # -------------------------------------------------------------------------- 

1474 

1475 def _create_linstor_cache(self): 

1476 reconnect = False 

1477 

1478 def create_cache(): 

1479 nonlocal reconnect 

1480 try: 

1481 if reconnect: 

1482 self._reconnect() 

1483 return self._linstor.get_volumes_with_info() 

1484 except Exception as e: 

1485 reconnect = True 

1486 raise e 

1487 

1488 self._all_volume_metadata_cache = \ 

1489 self._linstor.get_volumes_with_metadata() 

1490 self._all_volume_info_cache = util.retry( 

1491 create_cache, 

1492 maxretry=10, 

1493 period=3 

1494 ) 

1495 

1496 def _destroy_linstor_cache(self): 

1497 self._all_volume_info_cache = None 

1498 self._all_volume_metadata_cache = None 

1499 

1500 # -------------------------------------------------------------------------- 

1501 # Misc. 

1502 # -------------------------------------------------------------------------- 

1503 

1504 def _reconnect(self): 

1505 controller_uri = get_controller_uri() 

1506 

1507 self._journaler = LinstorJournaler( 

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

1509 ) 

1510 

1511 # Try to open SR if exists. 

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

1513 # we are trying to execute an exclusive operation. 

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

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

1516 # the SR is locked. 

1517 self._linstor = LinstorVolumeManager( 

1518 controller_uri, 

1519 self._group_name, 

1520 repair=( 

1521 self.is_master() and 

1522 self.srcmd.cmd in self.ops_exclusive 

1523 ), 

1524 logger=util.SMlog 

1525 ) 

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

1527 

1528 def _ensure_space_available(self, amount_needed): 

1529 space_available = self._linstor.max_volume_size_allowed 

1530 if (space_available < amount_needed): 

1531 util.SMlog( 

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

1533 space_available, amount_needed 

1534 ) 

1535 ) 

1536 raise xs_errors.XenError('SRNoSpace') 

1537 

1538 def _kick_gc(self): 

1539 # Don't bother if an instance already running. This is just an 

1540 # optimization to reduce the overhead of forking a new process if we 

1541 # don't have to, but the process will check the lock anyways. 

1542 lock = Lock(LOCK_TYPE_GC_RUNNING, self.uuid) 

1543 if not lock.acquireNoblock(): 

1544 if not cleanup.should_preempt(self.session, self.uuid): 

1545 util.SMlog('A GC instance already running, not kicking') 

1546 return 

1547 

1548 util.SMlog('Aborting currently-running coalesce of garbage VDI') 

1549 try: 

1550 if not cleanup.abort(self.uuid, soft=True): 

1551 util.SMlog('The GC has already been scheduled to re-start') 

1552 except util.CommandException as e: 

1553 if e.code != errno.ETIMEDOUT: 

1554 raise 

1555 util.SMlog('Failed to abort the GC') 

1556 else: 

1557 lock.release() 

1558 

1559 util.SMlog('Kicking GC') 

1560 cleanup.gc(self.session, self.uuid, True) 

1561 

1562# ============================================================================== 

1563# LinstorSr VDI 

1564# ============================================================================== 

1565 

1566 

1567class LinstorVDI(VDI.VDI): 

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

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

1570 TYPE_RAW = 'raw' 

1571 TYPE_VHD = 'vhd' 

1572 

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

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

1575 # Increase the performance when resize is called. 

1576 MAX_METADATA_VIRT_SIZE = 2 * 1024 * 1024 

1577 

1578 # -------------------------------------------------------------------------- 

1579 # VDI methods. 

1580 # -------------------------------------------------------------------------- 

1581 

1582 @override 

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

1584 self._lock = self.sr.lock 

1585 self._exists = True 

1586 self._linstor = self.sr._linstor 

1587 

1588 # Update hidden parent property. 

1589 self.hidden = False 

1590 

1591 def raise_bad_load(e): 

1592 util.SMlog( 

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

1594 ) 

1595 util.SMlog(traceback.format_exc()) 

1596 raise xs_errors.XenError( 

1597 'VDIUnavailable', 

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

1599 ) 

1600 

1601 # Try to load VDI. 

1602 try: 

1603 if ( 

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

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

1606 ): 

1607 self.vdi_type = vhdutil.VDI_TYPE_RAW 

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

1609 else: 

1610 self._determine_type_and_path() 

1611 self._load_this() 

1612 

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

1614 self.uuid, self.path, self.hidden 

1615 )) 

1616 except LinstorVolumeManagerError as e: 

1617 # 1. It may be a VDI deletion. 

1618 if e.code == LinstorVolumeManagerError.ERR_VOLUME_NOT_EXISTS: 

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

1620 self.deleted = True 

1621 return 

1622 

1623 # 2. Or maybe a creation. 

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

1625 # Set type attribute of VDI parent class. 

1626 # We use VHD by default. 

1627 self.vdi_type = vhdutil.VDI_TYPE_VHD 

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

1629 

1630 self._exists = False 

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

1632 if vdi_sm_config is not None: 

1633 type = vdi_sm_config.get('type') 

1634 if type is not None: 

1635 if type == self.TYPE_RAW: 

1636 self.vdi_type = vhdutil.VDI_TYPE_RAW 

1637 elif type == self.TYPE_VHD: 

1638 self.vdi_type = vhdutil.VDI_TYPE_VHD 

1639 else: 

1640 raise xs_errors.XenError( 

1641 'VDICreate', 

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

1643 ) 

1644 if self.vdi_type == vhdutil.VDI_TYPE_VHD: 

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

1646 

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

1648 self._update_device_name(None) 

1649 return 

1650 raise_bad_load(e) 

1651 except Exception as e: 

1652 raise_bad_load(e) 

1653 

1654 @override 

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

1656 # Usage example: 

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

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

1659 

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

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

1662 if self._exists: 

1663 raise xs_errors.XenError('VDIExists') 

1664 

1665 assert self.uuid 

1666 assert self.ty 

1667 assert self.vdi_type 

1668 

1669 # 2. Compute size and check space available. 

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

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

1672 util.SMlog( 

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

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

1675 ) 

1676 self.sr._ensure_space_available(volume_size) 

1677 

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

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

1680 

1681 # 4. Create! 

1682 failed = False 

1683 try: 

1684 volume_name = None 

1685 if self.ty == 'ha_statefile': 

1686 volume_name = HA_VOLUME_NAME 

1687 elif self.ty == 'redo_log': 

1688 volume_name = REDO_LOG_VOLUME_NAME 

1689 

1690 self._linstor.create_volume( 

1691 self.uuid, 

1692 volume_size, 

1693 persistent=False, 

1694 volume_name=volume_name, 

1695 high_availability=volume_name is not None 

1696 ) 

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

1698 

1699 self._update_device_name(volume_info.name) 

1700 

1701 if self.vdi_type == vhdutil.VDI_TYPE_RAW: 

1702 self.size = volume_info.virtual_size 

1703 else: 

1704 self.sr._vhdutil.create( 

1705 self.path, size, False, self.MAX_METADATA_VIRT_SIZE 

1706 ) 

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

1708 

1709 if self._key_hash: 

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

1711 

1712 # Because vhdutil commands modify the volume data, 

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

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

1715 

1716 volume_metadata = { 

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

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

1719 IS_A_SNAPSHOT_TAG: False, 

1720 SNAPSHOT_OF_TAG: '', 

1721 SNAPSHOT_TIME_TAG: '', 

1722 TYPE_TAG: self.ty, 

1723 VDI_TYPE_TAG: self.vdi_type, 

1724 READ_ONLY_TAG: bool(self.read_only), 

1725 METADATA_OF_POOL_TAG: '' 

1726 } 

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

1728 

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

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

1731 # an already opened volume. 

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

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

1734 

1735 self._linstor.mark_volume_as_persistent(self.uuid) 

1736 except util.CommandException as e: 

1737 failed = True 

1738 raise xs_errors.XenError( 

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

1740 ) 

1741 except Exception as e: 

1742 failed = True 

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

1744 finally: 

1745 if failed: 

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

1747 try: 

1748 self._linstor.destroy_volume(self.uuid) 

1749 except Exception as e: 

1750 util.SMlog( 

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

1752 '{}'.format(e) 

1753 ) 

1754 

1755 self.utilisation = volume_info.allocated_size 

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

1757 

1758 self.ref = self._db_introduce() 

1759 self.sr._update_stats(self.size) 

1760 

1761 return VDI.VDI.get_params(self) 

1762 

1763 @override 

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

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

1766 if self.attached: 

1767 raise xs_errors.XenError('VDIInUse') 

1768 

1769 if self.deleted: 

1770 return super(LinstorVDI, self).delete( 

1771 sr_uuid, vdi_uuid, data_only 

1772 ) 

1773 

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

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

1776 raise xs_errors.XenError( 

1777 'VDIDelete', 

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

1779 ) 

1780 

1781 try: 

1782 # Remove from XAPI and delete from LINSTOR. 

1783 self._linstor.destroy_volume(self.uuid) 

1784 if not data_only: 

1785 self._db_forget() 

1786 

1787 self.sr.lock.cleanupAll(vdi_uuid) 

1788 except Exception as e: 

1789 util.SMlog( 

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

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

1792 ) 

1793 

1794 try: 

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

1796 except LinstorVolumeManagerError as e: 

1797 if e.code != LinstorVolumeManagerError.ERR_VOLUME_DESTROY: 

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

1799 

1800 return 

1801 

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

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

1804 

1805 # TODO: Check size after delete. 

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

1807 self.sr._kick_gc() 

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

1809 

1810 @override 

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

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

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

1814 if ( 

1815 not attach_from_config or 

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

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

1818 raise xs_errors.XenError( 

1819 'VDIUnavailable', 

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

1821 'scan SR first to trigger auto-repair' 

1822 ) 

1823 

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

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

1826 

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

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

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

1830 # than the VHD size + bitmap size. 

1831 need_inflate = True 

1832 if ( 

1833 self.vdi_type == vhdutil.VDI_TYPE_RAW or 

1834 not writable or 

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

1836 ): 

1837 need_inflate = False 

1838 

1839 if need_inflate: 

1840 try: 

1841 self._prepare_thin(True) 

1842 except Exception as e: 

1843 raise xs_errors.XenError( 

1844 'VDIUnavailable', 

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

1846 .format(e) 

1847 ) 

1848 

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

1850 self.xenstore_data = {} 

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

1852 

1853 if ( 

1854 USE_HTTP_NBD_SERVERS and 

1855 attach_from_config and 

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

1857 ): 

1858 return self._attach_using_http_nbd() 

1859 

1860 # Ensure we have a path... 

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

1862 

1863 self.attached = True 

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

1865 

1866 @override 

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

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

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

1870 self.attached = False 

1871 

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

1873 return self._detach_using_http_nbd() 

1874 

1875 if self.vdi_type == vhdutil.VDI_TYPE_RAW: 

1876 return 

1877 

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

1879 # equal to the LINSTOR volume size. 

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

1881 already_deflated = self.capacity <= volume_size 

1882 

1883 if already_deflated: 

1884 util.SMlog( 

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

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

1887 ) 

1888 

1889 need_deflate = True 

1890 if already_deflated: 

1891 need_deflate = False 

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

1893 need_deflate = False 

1894 

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

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

1897 need_deflate = True 

1898 

1899 if need_deflate: 

1900 try: 

1901 self._prepare_thin(False) 

1902 except Exception as e: 

1903 raise xs_errors.XenError( 

1904 'VDIUnavailable', 

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

1906 .format(e) 

1907 ) 

1908 

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

1910 if self.sr.is_master(): 

1911 return 

1912 

1913 while vdi_uuid: 

1914 try: 

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

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

1917 except Exception: 

1918 break 

1919 

1920 if util.pathexists(path): 

1921 try: 

1922 self._linstor.remove_volume_if_diskless(vdi_uuid) 

1923 except Exception as e: 

1924 # Ensure we can always detach properly. 

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

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

1927 vdi_uuid = parent_vdi_uuid 

1928 

1929 @override 

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

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

1932 if not self.sr.is_master(): 

1933 raise xs_errors.XenError( 

1934 'VDISize', 

1935 opterr='resize on slave not allowed' 

1936 ) 

1937 

1938 if self.hidden: 

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

1940 

1941 # Compute the virtual VHD and DRBD volume size. 

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

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

1944 util.SMlog( 

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

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

1947 ) 

1948 

1949 if size < self.size: 

1950 util.SMlog( 

1951 'vdi_resize: shrinking not supported: ' 

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

1953 ) 

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

1955 

1956 if size == self.size: 

1957 return VDI.VDI.get_params(self) 

1958 

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 if self.vdi_type == vhdutil.VDI_TYPE_RAW: 

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

1977 else: 

1978 if new_volume_size != old_volume_size: 

1979 self.sr._vhdutil.inflate( 

1980 self.sr._journaler, self.uuid, self.path, 

1981 new_volume_size, old_volume_size 

1982 ) 

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

1984 

1985 # Reload size attributes. 

1986 self._load_this() 

1987 

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

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

1990 self.session.xenapi.VDI.set_physical_utilisation( 

1991 vdi_ref, str(self.utilisation) 

1992 ) 

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

1994 return VDI.VDI.get_params(self) 

1995 

1996 @override 

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

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

1999 

2000 @override 

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

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

2003 if self.vdi_type != vhdutil.VDI_TYPE_VHD: 

2004 raise xs_errors.XenError('Unimplemented') 

2005 

2006 parent_uuid = vdi1 

2007 parent_path = self._linstor.get_device_path(parent_uuid) 

2008 

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

2010 # have a readonly error. 

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

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

2013 

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

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

2016 try: 

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

2018 self.sr._vhdutil.set_hidden(parent_path) 

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

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

2021 ) 

2022 finally: 

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

2024 

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

2026 raise util.SMException( 

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

2028 ) 

2029 

2030 util.SMlog('Compose done') 

2031 

2032 @override 

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

2034 """ 

2035 Generate the XML config required to attach and activate 

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

2037 activation is handled by vdi_attach_from_config below. 

2038 """ 

2039 

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

2041 

2042 resp = {} 

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

2044 resp['sr_uuid'] = sr_uuid 

2045 resp['vdi_uuid'] = self.uuid 

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

2047 resp['command'] = 'vdi_attach_from_config' 

2048 

2049 # By default, we generate a normal config. 

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

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

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

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

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

2055 # instead. 

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

2057 if not USE_HTTP_NBD_SERVERS or volume_name not in [ 

2058 HA_VOLUME_NAME, REDO_LOG_VOLUME_NAME 

2059 ]: 

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

2061 available = False 

2062 # Try to refresh symlink path... 

2063 try: 

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

2065 available = util.pathexists(self.path) 

2066 except Exception: 

2067 pass 

2068 if not available: 

2069 raise xs_errors.XenError('VDIUnavailable') 

2070 

2071 resp['vdi_path'] = self.path 

2072 else: 

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

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

2075 

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

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

2078 

2079 @override 

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

2081 """ 

2082 Attach and activate a VDI using config generated by 

2083 vdi_generate_config above. This is used for cases such as 

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

2085 """ 

2086 

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

2088 

2089 try: 

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

2091 self.sr.attach(sr_uuid) 

2092 

2093 if not DRIVER_CONFIG['ATTACH_FROM_CONFIG_WITH_TAPDISK']: 

2094 return self.attach(sr_uuid, vdi_uuid) 

2095 except Exception: 

2096 util.logException('LinstorVDI.attach_from_config') 

2097 raise xs_errors.XenError( 

2098 'SRUnavailable', 

2099 opterr='Unable to attach from config' 

2100 ) 

2101 return '' 

2102 

2103 def reset_leaf(self, sr_uuid, vdi_uuid): 

2104 if self.vdi_type != vhdutil.VDI_TYPE_VHD: 

2105 raise xs_errors.XenError('Unimplemented') 

2106 

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

2108 raise util.SMException( 

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

2110 .format(self.uuid) 

2111 ) 

2112 

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

2114 

2115 def _load_this(self): 

2116 volume_metadata = None 

2117 if self.sr._all_volume_metadata_cache: 

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

2119 if volume_metadata is None: 

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

2121 

2122 volume_info = None 

2123 if self.sr._all_volume_info_cache: 

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

2125 if volume_info is None: 

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

2127 

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

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

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

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

2132 # be lower than virtual size at creation. 

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

2134 self.utilisation = volume_info.allocated_size 

2135 self.capacity = volume_info.virtual_size 

2136 

2137 if self.vdi_type == vhdutil.VDI_TYPE_RAW: 

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

2139 self.size = volume_info.virtual_size 

2140 self.parent = '' 

2141 else: 

2142 vhd_info = self.sr._vhdutil.get_vhd_info(self.uuid) 

2143 self.hidden = vhd_info.hidden 

2144 self.size = vhd_info.sizeVirt 

2145 self.parent = vhd_info.parentUuid 

2146 

2147 if self.hidden: 

2148 self.managed = False 

2149 

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

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

2152 

2153 # Update sm_config_override of VDI parent class. 

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

2155 

2156 def _mark_hidden(self, hidden=True): 

2157 if self.hidden == hidden: 

2158 return 

2159 

2160 if self.vdi_type == vhdutil.VDI_TYPE_VHD: 

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

2162 else: 

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

2164 HIDDEN_TAG: hidden 

2165 }) 

2166 self.hidden = hidden 

2167 

2168 @override 

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

2170 xenapi = self.session.xenapi 

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

2172 

2173 volume_metadata = { 

2174 NAME_LABEL_TAG: util.to_plain_string( 

2175 xenapi.VDI.get_name_label(vdi_ref) 

2176 ), 

2177 NAME_DESCRIPTION_TAG: util.to_plain_string( 

2178 xenapi.VDI.get_name_description(vdi_ref) 

2179 ) 

2180 } 

2181 

2182 try: 

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

2184 except LinstorVolumeManagerError as e: 

2185 if e.code == LinstorVolumeManagerError.ERR_VOLUME_NOT_EXISTS: 

2186 raise xs_errors.XenError( 

2187 'VDIUnavailable', 

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

2189 ) 

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

2191 

2192 # -------------------------------------------------------------------------- 

2193 # Thin provisioning. 

2194 # -------------------------------------------------------------------------- 

2195 

2196 def _prepare_thin(self, attach): 

2197 if self.sr.is_master(): 

2198 if attach: 

2199 attach_thin( 

2200 self.session, self.sr._journaler, self._linstor, 

2201 self.sr.uuid, self.uuid 

2202 ) 

2203 else: 

2204 detach_thin( 

2205 self.session, self._linstor, self.sr.uuid, self.uuid 

2206 ) 

2207 else: 

2208 fn = 'attach' if attach else 'detach' 

2209 

2210 master = util.get_master_ref(self.session) 

2211 

2212 args = { 

2213 'groupName': self.sr._group_name, 

2214 'srUuid': self.sr.uuid, 

2215 'vdiUuid': self.uuid 

2216 } 

2217 

2218 try: 

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

2220 except Exception: 

2221 if fn != 'detach': 

2222 raise 

2223 

2224 # Reload size attrs after inflate or deflate! 

2225 self._load_this() 

2226 self.sr._update_physical_size() 

2227 

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

2229 self.session.xenapi.VDI.set_physical_utilisation( 

2230 vdi_ref, str(self.utilisation) 

2231 ) 

2232 

2233 self.session.xenapi.SR.set_physical_utilisation( 

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

2235 ) 

2236 

2237 # -------------------------------------------------------------------------- 

2238 # Generic helpers. 

2239 # -------------------------------------------------------------------------- 

2240 

2241 def _determine_type_and_path(self): 

2242 """ 

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

2244 """ 

2245 

2246 # 1. Check vdi_ref and vdi_type in config. 

2247 try: 

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

2249 if vdi_ref: 

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

2251 vdi_type = sm_config.get('vdi_type') 

2252 if vdi_type: 

2253 # Update parent fields. 

2254 self.vdi_type = vdi_type 

2255 self.sm_config_override = sm_config 

2256 self._update_device_name( 

2257 self._linstor.get_volume_name(self.uuid) 

2258 ) 

2259 return 

2260 except Exception: 

2261 pass 

2262 

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

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

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

2266 self.vdi_type = volume_metadata.get(VDI_TYPE_TAG) 

2267 if not self.vdi_type: 

2268 raise xs_errors.XenError( 

2269 'VDIUnavailable', 

2270 opterr='failed to get vdi_type in metadata' 

2271 ) 

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

2273 

2274 def _update_device_name(self, device_name): 

2275 self._device_name = device_name 

2276 

2277 # Mark path of VDI parent class. 

2278 if device_name: 

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

2280 else: 

2281 self.path = None 

2282 

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

2284 """ 

2285 Snapshot self and return the snapshot VDI object. 

2286 """ 

2287 

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

2289 snap_path = self._linstor.shallow_clone_volume( 

2290 self.uuid, snap_uuid, persistent=False 

2291 ) 

2292 

2293 # 2. Write the snapshot content. 

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

2295 self.sr._vhdutil.snapshot( 

2296 snap_path, self.path, is_raw, self.MAX_METADATA_VIRT_SIZE 

2297 ) 

2298 

2299 # 3. Get snapshot parent. 

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

2301 

2302 # 4. Update metadata. 

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

2304 volume_metadata = { 

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

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

2307 IS_A_SNAPSHOT_TAG: bool(snap_of_uuid), 

2308 SNAPSHOT_OF_TAG: snap_of_uuid, 

2309 SNAPSHOT_TIME_TAG: '', 

2310 TYPE_TAG: self.ty, 

2311 VDI_TYPE_TAG: vhdutil.VDI_TYPE_VHD, 

2312 READ_ONLY_TAG: False, 

2313 METADATA_OF_POOL_TAG: '' 

2314 } 

2315 self._linstor.set_volume_metadata(snap_uuid, volume_metadata) 

2316 

2317 # 5. Set size. 

2318 snap_vdi = LinstorVDI(self.sr, snap_uuid) 

2319 if not snap_vdi._exists: 

2320 raise xs_errors.XenError('VDISnapshot') 

2321 

2322 volume_info = self._linstor.get_volume_info(snap_uuid) 

2323 

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

2325 snap_vdi.utilisation = volume_info.allocated_size 

2326 

2327 # 6. Update sm config. 

2328 snap_vdi.sm_config = {} 

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

2330 if snap_parent: 

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

2332 snap_vdi.parent = snap_parent 

2333 

2334 snap_vdi.label = self.label 

2335 snap_vdi.description = self.description 

2336 

2337 self._linstor.mark_volume_as_persistent(snap_uuid) 

2338 

2339 return snap_vdi 

2340 

2341 # -------------------------------------------------------------------------- 

2342 # Implement specific SR methods. 

2343 # -------------------------------------------------------------------------- 

2344 

2345 @override 

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

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

2348 volume_uuid = self._linstor.get_volume_uuid_from_device_path(oldpath) 

2349 self._linstor.update_volume_name(volume_uuid, newpath) 

2350 

2351 @override 

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

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

2354 # If cbt enabled, save file consistency state. 

2355 if cbtlog is not None: 

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

2357 consistency_state = False 

2358 else: 

2359 consistency_state = True 

2360 util.SMlog( 

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

2362 .format(consistency_state, vdi_uuid) 

2363 ) 

2364 else: 

2365 consistency_state = None 

2366 

2367 if self.vdi_type != vhdutil.VDI_TYPE_VHD: 

2368 raise xs_errors.XenError('Unimplemented') 

2369 

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

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

2372 try: 

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

2374 finally: 

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

2376 

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

2378 util.SMlog( 

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

2380 .format(self.uuid, snap_type) 

2381 ) 

2382 

2383 # 1. Checks... 

2384 if self.hidden: 

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

2386 

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

2388 if depth == -1: 

2389 raise xs_errors.XenError( 

2390 'VDIUnavailable', 

2391 opterr='failed to get VHD depth' 

2392 ) 

2393 elif depth >= vhdutil.MAX_CHAIN_SIZE: 

2394 raise xs_errors.XenError('SnapshotChainTooLong') 

2395 

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

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

2398 

2399 volume_path = self.path 

2400 if not util.pathexists(volume_path): 

2401 raise xs_errors.XenError( 

2402 'EIO', 

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

2404 ) 

2405 

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

2407 base_uuid = util.gen_uuid() 

2408 snap_uuid = None 

2409 

2410 if snap_type == VDI.SNAPSHOT_DOUBLE: 

2411 snap_uuid = util.gen_uuid() 

2412 

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

2414 

2415 active_uuid = self.uuid 

2416 self.sr._journaler.create( 

2417 LinstorJournaler.CLONE, active_uuid, clone_info 

2418 ) 

2419 

2420 try: 

2421 # 3. Self becomes the new base. 

2422 # The device path remains the same. 

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

2424 self.uuid = base_uuid 

2425 self.location = self.uuid 

2426 self.read_only = True 

2427 self.managed = False 

2428 

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

2430 active_vdi = self._create_snapshot(active_uuid) 

2431 

2432 snap_vdi = None 

2433 if snap_type == VDI.SNAPSHOT_DOUBLE: 

2434 snap_vdi = self._create_snapshot(snap_uuid, active_uuid) 

2435 

2436 self.label = 'base copy' 

2437 self.description = '' 

2438 

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

2440 # in subsequent scans. 

2441 self._mark_hidden() 

2442 self._linstor.update_volume_metadata( 

2443 self.uuid, {READ_ONLY_TAG: True} 

2444 ) 

2445 

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

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

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

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

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

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

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

2453 active_vdi.sm_config[key] = sm_config[key] 

2454 

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

2456 # delete base if unused. 

2457 introduce_parent = True 

2458 try: 

2459 snap_parent = None 

2460 if snap_vdi: 

2461 snap_parent = snap_vdi.parent 

2462 

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

2464 snap_type == VDI.SNAPSHOT_SINGLE or 

2465 snap_type == VDI.SNAPSHOT_INTERNAL or 

2466 snap_parent != self.uuid 

2467 ): 

2468 util.SMlog( 

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

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

2471 ) 

2472 introduce_parent = False 

2473 self._linstor.destroy_volume(self.uuid) 

2474 except Exception as e: 

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

2476 pass 

2477 

2478 # 8. Introduce the new VDI records. 

2479 if snap_vdi: 

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

2481 # new snapshot disk. 

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

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

2484 # TODO: Maybe remove key_hash support. 

2485 if 'key_hash' in sm_config: 

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

2487 # If we have CBT enabled on the VDI, 

2488 # set CBT status for the new snapshot disk. 

2489 if cbtlog: 

2490 snap_vdi.cbt_enabled = True 

2491 

2492 if snap_vdi: 

2493 snap_vdi_ref = snap_vdi._db_introduce() 

2494 util.SMlog( 

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

2496 .format(snap_vdi_ref, snap_vdi.uuid) 

2497 ) 

2498 if introduce_parent: 

2499 base_vdi_ref = self._db_introduce() 

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

2501 util.SMlog( 

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

2503 .format(base_vdi_ref, self.uuid) 

2504 ) 

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

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

2507 NAME_DESCRIPTION_TAG: util.to_plain_string( 

2508 self.description 

2509 ), 

2510 READ_ONLY_TAG: True, 

2511 METADATA_OF_POOL_TAG: '' 

2512 }) 

2513 

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

2515 if snap_type == VDI.SNAPSHOT_DOUBLE and cbtlog: 

2516 try: 

2517 self._cbt_snapshot(snap_uuid, cbt_consistency) 

2518 except Exception: 

2519 # CBT operation failed. 

2520 # TODO: Implement me. 

2521 raise 

2522 

2523 if snap_type != VDI.SNAPSHOT_INTERNAL: 

2524 self.sr._update_stats(self.size) 

2525 

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

2527 ret_vdi = snap_vdi 

2528 if not ret_vdi: 

2529 ret_vdi = self 

2530 if not ret_vdi: 

2531 ret_vdi = active_vdi 

2532 

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

2534 self.session.xenapi.VDI.set_sm_config( 

2535 vdi_ref, active_vdi.sm_config 

2536 ) 

2537 except Exception: 

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

2539 try: 

2540 self.sr._handle_interrupted_clone( 

2541 active_uuid, clone_info, force_undo=True 

2542 ) 

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

2544 except Exception as clean_error: 

2545 util.SMlog( 

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

2547 .format(clean_error) 

2548 ) 

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

2550 

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

2552 

2553 return ret_vdi.get_params() 

2554 

2555 @staticmethod 

2556 def _start_persistent_http_server(volume_name): 

2557 pid_path = None 

2558 http_server = None 

2559 

2560 try: 

2561 if volume_name == HA_VOLUME_NAME: 

2562 port = '8076' 

2563 else: 

2564 port = '8077' 

2565 

2566 try: 

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

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

2569 # block indefinitely. 

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

2571 host_ip = util.get_this_host_address(session) 

2572 except: 

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

2574 host_ip, _ = get_ips_from_xha_config_file() 

2575 if not host_ip: 

2576 raise Exception( 

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

2578 ) 

2579 

2580 arguments = [ 

2581 'http-disk-server', 

2582 '--disk', 

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

2584 '--ip', 

2585 host_ip, 

2586 '--port', 

2587 port 

2588 ] 

2589 

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

2591 http_server = subprocess.Popen( 

2592 [FORK_LOG_DAEMON] + arguments, 

2593 stdout=subprocess.PIPE, 

2594 stderr=subprocess.STDOUT, 

2595 universal_newlines=True, 

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

2597 # touch the current one. 

2598 preexec_fn=os.setsid 

2599 ) 

2600 

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

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

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

2604 

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

2606 def is_ready(): 

2607 while http_server.poll() is None: 

2608 line = http_server.stdout.readline() 

2609 if reg_server_ready.search(line): 

2610 return True 

2611 return False 

2612 try: 

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

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

2615 except util.TimeoutException: 

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

2617 except Exception as e: 

2618 if pid_path: 

2619 try: 

2620 os.remove(pid_path) 

2621 except Exception: 

2622 pass 

2623 

2624 if http_server: 

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

2626 try: 

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

2628 except: 

2629 pass 

2630 

2631 raise xs_errors.XenError( 

2632 'VDIUnavailable', 

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

2634 ) 

2635 

2636 def _start_persistent_nbd_server(self, volume_name): 

2637 pid_path = None 

2638 nbd_path = None 

2639 nbd_server = None 

2640 

2641 try: 

2642 # We use a precomputed device size. 

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

2644 if volume_name == HA_VOLUME_NAME: 

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

2646 port = '8076' 

2647 device_size = 4 * 1024 * 1024 

2648 else: 

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

2650 port = '8077' 

2651 device_size = 256 * 1024 * 1024 

2652 

2653 try: 

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

2655 ips = util.get_host_addresses(session) 

2656 except Exception as e: 

2657 _, ips = get_ips_from_xha_config_file() 

2658 if not ips: 

2659 raise Exception( 

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

2661 ) 

2662 ips = ips.values() 

2663 

2664 arguments = [ 

2665 'nbd-http-server', 

2666 '--socket-path', 

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

2668 '--nbd-name', 

2669 volume_name, 

2670 '--urls', 

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

2672 '--device-size', 

2673 str(device_size) 

2674 ] 

2675 

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

2677 nbd_server = subprocess.Popen( 

2678 [FORK_LOG_DAEMON] + arguments, 

2679 stdout=subprocess.PIPE, 

2680 stderr=subprocess.STDOUT, 

2681 universal_newlines=True, 

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

2683 # touch the current one. 

2684 preexec_fn=os.setsid 

2685 ) 

2686 

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

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

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

2690 

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

2692 def get_nbd_path(): 

2693 while nbd_server.poll() is None: 

2694 line = nbd_server.stdout.readline() 

2695 match = reg_nbd_path.search(line) 

2696 if match: 

2697 return match.group(1) 

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

2699 try: 

2700 nbd_path = util.timeout_call(10, get_nbd_path) 

2701 if nbd_path is None: 

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

2703 except util.TimeoutException: 

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

2705 

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

2707 os.symlink(nbd_path, self.path) 

2708 except Exception as e: 

2709 if pid_path: 

2710 try: 

2711 os.remove(pid_path) 

2712 except Exception: 

2713 pass 

2714 

2715 if nbd_path: 

2716 try: 

2717 os.remove(nbd_path) 

2718 except Exception: 

2719 pass 

2720 

2721 if nbd_server: 

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

2723 try: 

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

2725 except: 

2726 pass 

2727 

2728 raise xs_errors.XenError( 

2729 'VDIUnavailable', 

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

2731 ) 

2732 

2733 @classmethod 

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

2735 try: 

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

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

2738 return 

2739 

2740 pid = None 

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

2742 try: 

2743 pid = int(pid_file.read()) 

2744 except Exception: 

2745 pass 

2746 

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

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

2749 try: 

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

2751 except Exception as e: 

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

2753 

2754 os.remove(path) 

2755 except: 

2756 pass 

2757 

2758 @classmethod 

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

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

2761 

2762 @classmethod 

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

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

2765 

2766 def _check_http_nbd_volume_name(self): 

2767 volume_name = self.path[14:] 

2768 if volume_name not in [ 

2769 HA_VOLUME_NAME, REDO_LOG_VOLUME_NAME 

2770 ]: 

2771 raise xs_errors.XenError( 

2772 'VDIUnavailable', 

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

2774 ) 

2775 return volume_name 

2776 

2777 def _attach_using_http_nbd(self): 

2778 volume_name = self._check_http_nbd_volume_name() 

2779 

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

2781 self._kill_persistent_nbd_server(volume_name) 

2782 self._kill_persistent_http_server(volume_name) 

2783 

2784 # 0. Fetch drbd path. 

2785 must_get_device_path = True 

2786 if not self.sr.is_master(): 

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

2788 try: 

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

2790 except Exception as e: 

2791 raise xs_errors.XenError( 

2792 'VDIUnavailable', 

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

2794 .format(self.uuid, e) 

2795 ) 

2796 

2797 hostname = socket.gethostname() 

2798 must_get_device_path = hostname in volume_info.diskful 

2799 

2800 drbd_path = None 

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

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

2803 # or diskful available to init HA. 

2804 # It also avoid this error in xensource.log 

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

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

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

2808 available = False 

2809 try: 

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

2811 available = util.pathexists(drbd_path) 

2812 except Exception: 

2813 pass 

2814 

2815 if not available: 

2816 raise xs_errors.XenError( 

2817 'VDIUnavailable', 

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

2819 ) 

2820 

2821 # 1. Prepare http-nbd folder. 

2822 try: 

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

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

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

2826 os.remove(self.path) 

2827 except OSError as e: 

2828 if e.errno != errno.EEXIST: 

2829 raise xs_errors.XenError( 

2830 'VDIUnavailable', 

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

2832 ) 

2833 

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

2835 http_service = None 

2836 if drbd_path: 

2837 assert(drbd_path in ( 

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

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

2840 )) 

2841 self._start_persistent_http_server(volume_name) 

2842 

2843 # 3. Start NBD server in all cases. 

2844 try: 

2845 self._start_persistent_nbd_server(volume_name) 

2846 except Exception as e: 

2847 if drbd_path: 

2848 self._kill_persistent_http_server(volume_name) 

2849 raise 

2850 

2851 self.attached = True 

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

2853 

2854 def _detach_using_http_nbd(self): 

2855 volume_name = self._check_http_nbd_volume_name() 

2856 self._kill_persistent_nbd_server(volume_name) 

2857 self._kill_persistent_http_server(volume_name) 

2858 

2859# ------------------------------------------------------------------------------ 

2860 

2861 

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

2863 def run(): 

2864 SRCommand.run(LinstorSR, DRIVER_INFO) 

2865 

2866 if not TRACE_PERFS: 

2867 run() 

2868 else: 

2869 util.make_profile('LinstorSR', run) 

2870else: 

2871 SR.registerSR(LinstorSR)