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 constants import CBTLOG_TAG 

18 

19try: 

20 from linstorjournaler import LinstorJournaler 

21 from linstorvhdutil import LinstorVhdUtil 

22 from linstorvolumemanager import get_controller_uri 

23 from linstorvolumemanager import get_controller_node_name 

24 from linstorvolumemanager import LinstorVolumeManager 

25 from linstorvolumemanager import LinstorVolumeManagerError 

26 from linstorvolumemanager import PERSISTENT_PREFIX 

27 

28 LINSTOR_AVAILABLE = True 

29except ImportError: 

30 PERSISTENT_PREFIX = 'unknown' 

31 

32 LINSTOR_AVAILABLE = False 

33 

34from lock import Lock, LOCK_TYPE_GC_RUNNING 

35import blktap2 

36import cleanup 

37import distutils 

38import errno 

39import functools 

40import lvutil 

41import os 

42import re 

43import scsiutil 

44import signal 

45import socket 

46import SR 

47import SRCommand 

48import subprocess 

49import time 

50import traceback 

51import util 

52import VDI 

53import vhdutil 

54import xml.etree.ElementTree as xml_parser 

55import xmlrpc.client 

56import xs_errors 

57 

58from srmetadata import \ 

59 NAME_LABEL_TAG, NAME_DESCRIPTION_TAG, IS_A_SNAPSHOT_TAG, SNAPSHOT_OF_TAG, \ 

60 TYPE_TAG, VDI_TYPE_TAG, READ_ONLY_TAG, SNAPSHOT_TIME_TAG, \ 

61 METADATA_OF_POOL_TAG 

62 

63HIDDEN_TAG = 'hidden' 

64 

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

66 

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

68 

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

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

71# specific conditions: 

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

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

74USE_HTTP_NBD_SERVERS = True 

75 

76# Useful flag to trace calls using cProfile. 

77TRACE_PERFS = False 

78 

79# Enable/Disable VHD key hash support. 

80USE_KEY_HASH = False 

81 

82# Special volumes. 

83HA_VOLUME_NAME = PERSISTENT_PREFIX + 'ha-statefile' 

84REDO_LOG_VOLUME_NAME = PERSISTENT_PREFIX + 'redo-log' 

85 

86# ============================================================================== 

87 

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

89# 'VDI_CONFIG_CBT', 'SR_PROBE' 

90 

91CAPABILITIES = [ 

92 'ATOMIC_PAUSE', 

93 'SR_UPDATE', 

94 'VDI_CREATE', 

95 'VDI_DELETE', 

96 'VDI_UPDATE', 

97 'VDI_ATTACH', 

98 'VDI_DETACH', 

99 'VDI_ACTIVATE', 

100 'VDI_DEACTIVATE', 

101 'VDI_CLONE', 

102 'VDI_MIRROR', 

103 'VDI_RESIZE', 

104 'VDI_SNAPSHOT', 

105 'VDI_GENERATE_CONFIG' 

106] 

107 

108CONFIGURATION = [ 

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

110 ['redundancy', 'replication count'], 

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

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

113] 

114 

115DRIVER_INFO = { 

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

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

118 'vendor': 'Vates', 

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

120 'driver_version': '1.0', 

121 'required_api_version': '1.0', 

122 'capabilities': CAPABILITIES, 

123 'configuration': CONFIGURATION 

124} 

125 

126DRIVER_CONFIG = {'ATTACH_FROM_CONFIG_WITH_TAPDISK': False} 

127 

128OPS_EXCLUSIVE = [ 

129 'sr_create', 'sr_delete', 'sr_attach', 'sr_detach', 'sr_scan', 

130 'sr_update', 'sr_probe', 'vdi_init', 'vdi_create', 'vdi_delete', 

131 'vdi_attach', 'vdi_detach', 'vdi_clone', 'vdi_snapshot', 

132] 

133 

134# ============================================================================== 

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

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

137 

138 

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

140 volume_metadata = linstor.get_volume_metadata(vdi_uuid) 

141 image_type = volume_metadata.get(VDI_TYPE_TAG) 

142 if image_type == vhdutil.VDI_TYPE_RAW: 

143 return 

144 

145 device_path = linstor.get_device_path(vdi_uuid) 

146 

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

148 # there is nothing to do. 

149 vhd_size = LinstorVhdUtil.compute_volume_size( 

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

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

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

153 image_type 

154 ) 

155 

156 volume_info = linstor.get_volume_info(vdi_uuid) 

157 volume_size = volume_info.virtual_size 

158 

159 if vhd_size > volume_size: 

160 LinstorVhdUtil(session, linstor).inflate( 

161 journaler, vdi_uuid, device_path, vhd_size, volume_size 

162 ) 

163 

164 

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

166 volume_metadata = linstor.get_volume_metadata(vdi_uuid) 

167 image_type = volume_metadata.get(VDI_TYPE_TAG) 

168 if image_type == vhdutil.VDI_TYPE_RAW: 

169 return 

170 

171 def check_vbd_count(): 

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

173 vbds = session.xenapi.VBD.get_all_records_where( 

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

175 ) 

176 

177 num_plugged = 0 

178 for vbd_rec in vbds.values(): 

179 if vbd_rec['currently_attached']: 

180 num_plugged += 1 

181 if num_plugged > 1: 

182 raise xs_errors.XenError( 

183 'VDIUnavailable', 

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

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

186 ) 

187 

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

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

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

191 

192 device_path = linstor.get_device_path(vdi_uuid) 

193 vhdutil_inst = LinstorVhdUtil(session, linstor) 

194 new_volume_size = LinstorVolumeManager.round_up_volume_size( 

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

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

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

198 ) 

199 

200 volume_info = linstor.get_volume_info(vdi_uuid) 

201 old_volume_size = volume_info.virtual_size 

202 vhdutil_inst.deflate(device_path, new_volume_size, old_volume_size) 

203 

204 

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

206 # This function must always return without errors. 

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

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

209 try: 

210 detach_thin_impl(session, linstor, sr_uuid, vdi_uuid) 

211 except Exception as e: 

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

213 

214 

215def get_ips_from_xha_config_file(): 

216 ips = dict() 

217 host_id = None 

218 try: 

219 # Ensure there is no dirty read problem. 

220 # For example if the HA is reloaded. 

221 tree = util.retry( 

222 lambda: xml_parser.parse(XHA_CONFIG_PATH), 

223 maxretry=10, 

224 period=1 

225 ) 

226 except: 

227 return (None, ips) 

228 

229 def parse_host_nodes(ips, node): 

230 current_id = None 

231 current_ip = None 

232 

233 for sub_node in node: 

234 if sub_node.tag == 'IPaddress': 

235 current_ip = sub_node.text 

236 elif sub_node.tag == 'HostID': 

237 current_id = sub_node.text 

238 else: 

239 continue 

240 

241 if current_id and current_ip: 

242 ips[current_id] = current_ip 

243 return 

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

245 

246 def parse_common_config(ips, node): 

247 for sub_node in node: 

248 if sub_node.tag == 'host': 

249 parse_host_nodes(ips, sub_node) 

250 

251 def parse_local_config(ips, node): 

252 for sub_node in node: 

253 if sub_node.tag == 'localhost': 

254 for host_node in sub_node: 

255 if host_node.tag == 'HostID': 

256 return host_node.text 

257 

258 for node in tree.getroot(): 

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

260 parse_common_config(ips, node) 

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

262 host_id = parse_local_config(ips, node) 

263 else: 

264 continue 

265 

266 if ips and host_id: 

267 break 

268 

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

270 

271 

272def activate_lvm_group(group_name): 

273 path = group_name.split('/') 

274 assert path and len(path) <= 2 

275 try: 

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

277 except Exception as e: 

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

279 

280# ============================================================================== 

281 

282# Usage example: 

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

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

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

286 

287 

288class LinstorSR(SR.SR): 

289 DRIVER_TYPE = 'linstor' 

290 

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

292 PROVISIONING_DEFAULT = 'thin' 

293 

294 MANAGER_PLUGIN = 'linstor-manager' 

295 

296 INIT_STATUS_NOT_SET = 0 

297 INIT_STATUS_IN_PROGRESS = 1 

298 INIT_STATUS_OK = 2 

299 INIT_STATUS_FAIL = 3 

300 

301 # -------------------------------------------------------------------------- 

302 # SR methods. 

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

304 

305 @staticmethod 

306 def handles(type): 

307 return type == LinstorSR.DRIVER_TYPE 

308 

309 def load(self, sr_uuid): 

310 if not LINSTOR_AVAILABLE: 

311 raise util.SMException( 

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

313 ) 

314 

315 # Check parameters. 

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

317 raise xs_errors.XenError('LinstorConfigGroupNameMissing') 

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

319 raise xs_errors.XenError('LinstorConfigRedundancyMissing') 

320 

321 self.driver_config = DRIVER_CONFIG 

322 

323 # Check provisioning config. 

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

325 if provisioning: 

326 if provisioning in self.PROVISIONING_TYPES: 

327 self._provisioning = provisioning 

328 else: 

329 raise xs_errors.XenError( 

330 'InvalidArg', 

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

332 self.PROVISIONING_TYPES 

333 ) 

334 ) 

335 else: 

336 self._provisioning = self.PROVISIONING_DEFAULT 

337 

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

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

340 distutils.util.strtobool(monitor_db_quorum) 

341 

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

343 # 'vdi_attach_from_config' command is executed. 

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

345 if self._has_session: 

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

347 else: 

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

349 

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

351 if provisioning in self.PROVISIONING_TYPES: 

352 self._provisioning = provisioning 

353 

354 # Define properties for SR parent class. 

355 self.ops_exclusive = OPS_EXCLUSIVE 

356 self.path = LinstorVolumeManager.DEV_ROOT_PATH 

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

358 self.sr_vditype = SR.DEFAULT_TAP 

359 

360 if self.cmd == 'sr_create': 

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

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

363 self._journaler = None 

364 

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

366 

367 self._vdi_shared_time = 0 

368 

369 self._init_status = self.INIT_STATUS_NOT_SET 

370 

371 self._vdis_loaded = False 

372 self._all_volume_info_cache = None 

373 self._all_volume_metadata_cache = None 

374 

375 def _locked_load(method): 

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

377 self._init_status = self.INIT_STATUS_OK 

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

379 

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

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

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

383 activate_lvm_group(self._group_name) 

384 

385 if not self._has_session: 

386 if self.srcmd.cmd in ( 

387 'vdi_attach_from_config', 

388 'vdi_detach_from_config', 

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

390 # empty command. 

391 None 

392 ): 

393 def create_linstor(uri, attempt_count=30): 

394 self._linstor = LinstorVolumeManager( 

395 uri, 

396 self._group_name, 

397 logger=util.SMlog, 

398 attempt_count=attempt_count 

399 ) 

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

401 # I.e. not an HA volume. 

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

403 

404 controller_uri = get_controller_uri() 

405 if controller_uri: 

406 create_linstor(controller_uri) 

407 else: 

408 def connect(): 

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

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

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

412 controller_uri = 'linstor://' + ip 

413 try: 

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

415 create_linstor(controller_uri, attempt_count=0) 

416 return controller_uri 

417 except: 

418 pass 

419 

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

421 if not controller_uri: 

422 raise xs_errors.XenError( 

423 'SRUnavailable', 

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

425 ) 

426 

427 self._journaler = LinstorJournaler( 

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

429 ) 

430 

431 if self.srcmd.cmd is None: 

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

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

434 

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

436 

437 if not self.is_master(): 

438 if self.cmd in [ 

439 'sr_create', 'sr_delete', 'sr_update', 'sr_probe', 

440 'sr_scan', 'vdi_create', 'vdi_delete', 'vdi_resize', 

441 'vdi_snapshot', 'vdi_clone' 

442 ]: 

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

444 raise xs_errors.XenError('LinstorMaster') 

445 

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

447 # the VDI before the LinstorJournaler/LinstorVolumeManager 

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

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

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

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

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

453 self._vdi_shared_time = time.time() 

454 

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

456 try: 

457 self._reconnect() 

458 except Exception as e: 

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

460 

461 if self._linstor: 

462 try: 

463 hosts = self._linstor.disconnected_hosts 

464 except Exception as e: 

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

466 

467 if hosts: 

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

469 

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

471 if ( 

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

473 self.cmd != 'vdi_create' 

474 ): 

475 self._linstor.ensure_volume_is_not_locked( 

476 self.srcmd.params['vdi_uuid'] 

477 ) 

478 

479 try: 

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

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

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

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

484 # 

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

486 # resourceless volumes. 

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

488 'vdi_attach', 'vdi_detach', 

489 'vdi_activate', 'vdi_deactivate', 

490 'vdi_epoch_begin', 'vdi_epoch_end', 

491 'vdi_update', 'vdi_destroy' 

492 ]: 

493 load_vdis = ( 

494 self.cmd == 'sr_scan' or 

495 self.cmd == 'sr_attach' 

496 ) or len( 

497 self._journaler.get_all(LinstorJournaler.INFLATE) 

498 ) or len( 

499 self._journaler.get_all(LinstorJournaler.CLONE) 

500 ) 

501 

502 if load_vdis: 

503 self._load_vdis() 

504 

505 self._linstor.remove_resourceless_volumes() 

506 

507 self._synchronize_metadata() 

508 except Exception as e: 

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

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

511 # from the XAPI database otherwise. 

512 raise e 

513 util.SMlog( 

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

515 ) 

516 util.SMlog(traceback.format_exc()) 

517 

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

519 

520 @functools.wraps(wrapped_method) 

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

522 if self._init_status in \ 

523 (self.INIT_STATUS_OK, self.INIT_STATUS_IN_PROGRESS): 

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

525 if self._init_status == self.INIT_STATUS_FAIL: 

526 util.SMlog( 

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

528 .format(method) 

529 ) 

530 else: 

531 try: 

532 self._init_status = self.INIT_STATUS_IN_PROGRESS 

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

534 except Exception: 

535 if self._init_status != self.INIT_STATUS_OK: 

536 self._init_status = self.INIT_STATUS_FAIL 

537 raise 

538 

539 return wrap 

540 

541 def cleanup(self): 

542 if self._vdi_shared_time: 

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

544 

545 @_locked_load 

546 def create(self, uuid, size): 

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

548 

549 host_adresses = util.get_host_addresses(self.session) 

550 if self._redundancy > len(host_adresses): 

551 raise xs_errors.XenError( 

552 'LinstorSRCreate', 

553 opterr='Redundancy greater than host count' 

554 ) 

555 

556 xenapi = self.session.xenapi 

557 srs = xenapi.SR.get_all_records_where( 

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

559 ) 

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

561 

562 for sr in srs.values(): 

563 for pbd in sr['PBDs']: 

564 device_config = xenapi.PBD.get_device_config(pbd) 

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

566 if group_name and group_name == self._group_name: 

567 raise xs_errors.XenError( 

568 'LinstorSRCreate', 

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

570 xenapi.PBD.get_uuid(pbd) 

571 ) 

572 ) 

573 

574 if srs: 

575 raise xs_errors.XenError( 

576 'LinstorSRCreate', 

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

578 ) 

579 

580 online_hosts = util.get_online_hosts(self.session) 

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

582 raise xs_errors.XenError( 

583 'LinstorSRCreate', 

584 opterr='Not enough online hosts' 

585 ) 

586 

587 ips = {} 

588 for host_ref in online_hosts: 

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

590 hostname = record['hostname'] 

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

592 

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

594 raise xs_errors.XenError( 

595 'LinstorSRCreate', 

596 opterr='Multiple hosts with same hostname' 

597 ) 

598 

599 # Ensure ports are opened and LINSTOR satellites 

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

601 # must be stopped. 

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

603 

604 # Create SR. 

605 # Throw if the SR already exists. 

606 try: 

607 self._linstor = LinstorVolumeManager.create_sr( 

608 self._group_name, 

609 ips, 

610 self._redundancy, 

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

612 auto_quorum=self._monitor_db_quorum, 

613 logger=util.SMlog 

614 ) 

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

616 except Exception as e: 

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

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

619 

620 try: 

621 util.SMlog( 

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

623 ) 

624 self._update_drbd_reactor_on_all_hosts(enabled=True) 

625 except Exception as e: 

626 try: 

627 self._linstor.destroy() 

628 except Exception as e2: 

629 util.SMlog( 

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

631 .format(e2) 

632 ) 

633 raise e 

634 

635 @_locked_load 

636 def delete(self, uuid): 

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

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

639 

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

641 raise xs_errors.XenError('SRNotEmpty') 

642 

643 node_name = get_controller_node_name() 

644 if not node_name: 

645 raise xs_errors.XenError( 

646 'LinstorSRDelete', 

647 opterr='Cannot get controller node name' 

648 ) 

649 

650 host_ref = None 

651 if node_name == 'localhost': 

652 host_ref = util.get_this_host_ref(self.session) 

653 else: 

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

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

656 if r_name == node_name: 

657 host_ref = slave 

658 break 

659 

660 if not host_ref: 

661 raise xs_errors.XenError( 

662 'LinstorSRDelete', 

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

664 node_name 

665 ) 

666 ) 

667 

668 try: 

669 self._update_drbd_reactor_on_all_hosts( 

670 controller_node_name=node_name, enabled=False 

671 ) 

672 

673 args = { 

674 'groupName': self._group_name, 

675 } 

676 self._exec_manager_command( 

677 host_ref, 'destroy', args, 'LinstorSRDelete' 

678 ) 

679 except Exception as e: 

680 try: 

681 self._update_drbd_reactor_on_all_hosts( 

682 controller_node_name=node_name, enabled=True 

683 ) 

684 except Exception as e2: 

685 util.SMlog( 

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

687 .format(e2) 

688 ) 

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

690 raise xs_errors.XenError( 

691 'LinstorSRDelete', 

692 opterr=str(e) 

693 ) 

694 

695 Lock.cleanupAll(self.uuid) 

696 

697 @_locked_load 

698 def update(self, uuid): 

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

700 

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

702 if not self._linstor: 

703 raise xs_errors.XenError( 

704 'SRUnavailable', 

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

706 ) 

707 

708 self._update_stats(0) 

709 

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

711 xenapi = self.session.xenapi 

712 self._linstor.metadata = { 

713 NAME_LABEL_TAG: util.to_plain_string( 

714 xenapi.SR.get_name_label(self.sr_ref) 

715 ), 

716 NAME_DESCRIPTION_TAG: util.to_plain_string( 

717 xenapi.SR.get_name_description(self.sr_ref) 

718 ) 

719 } 

720 

721 @_locked_load 

722 def attach(self, uuid): 

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

724 

725 if not self._linstor: 

726 raise xs_errors.XenError( 

727 'SRUnavailable', 

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

729 ) 

730 

731 @_locked_load 

732 def detach(self, uuid): 

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

734 cleanup.abort(self.uuid) 

735 

736 @_locked_load 

737 def probe(self): 

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

739 # TODO 

740 

741 @_locked_load 

742 def scan(self, uuid): 

743 if self._init_status == self.INIT_STATUS_FAIL: 

744 return 

745 

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

747 if not self._linstor: 

748 raise xs_errors.XenError( 

749 'SRUnavailable', 

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

751 ) 

752 

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

754 # are loaded. 

755 self._load_vdis() 

756 self._update_physical_size() 

757 

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

759 if self.vdis[vdi_uuid].deleted: 

760 del self.vdis[vdi_uuid] 

761 

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

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

764 try: 

765 self._linstor.get_database_path() 

766 except Exception as e: 

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

768 # VDIs in the XAPI database... 

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

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

771 ): 

772 raise xs_errors.XenError( 

773 'SRUnavailable', 

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

775 ) 

776 

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

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

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

780 self._kick_gc() 

781 

782 def is_master(self): 

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

784 if 'SRmaster' not in self.dconf: 

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

786 else: 

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

788 

789 return self._is_master 

790 

791 @_locked_load 

792 def vdi(self, uuid): 

793 return LinstorVDI(self, uuid) 

794 

795 _locked_load = staticmethod(_locked_load) 

796 

797 # -------------------------------------------------------------------------- 

798 # Lock. 

799 # -------------------------------------------------------------------------- 

800 

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

802 master = util.get_master_ref(self.session) 

803 

804 command = 'lockVdi' 

805 args = { 

806 'groupName': self._group_name, 

807 'srUuid': self.uuid, 

808 'vdiUuid': vdi_uuid, 

809 'locked': str(locked) 

810 } 

811 

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

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

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

815 # 

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

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

818 # to implement without impacting performance. 

819 if not locked: 

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

821 timeout = LinstorVolumeManager.LOCKED_EXPIRATION_DELAY * 0.7 

822 if elapsed_time >= timeout: 

823 util.SMlog( 

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

825 .format(vdi_uuid) 

826 ) 

827 return 

828 

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

830 

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

832 # Network. 

833 # -------------------------------------------------------------------------- 

834 

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

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

837 host_uuid = host_rec['uuid'] 

838 

839 try: 

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

841 host_ref, self.MANAGER_PLUGIN, command, args 

842 ) 

843 except Exception as e: 

844 util.SMlog( 

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

846 host_uuid, self.MANAGER_PLUGIN, command, args 

847 ) 

848 ) 

849 raise e 

850 

851 util.SMlog( 

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

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

854 ) 

855 ) 

856 if ret == 'False': 

857 raise xs_errors.XenError( 

858 error, 

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

860 ) 

861 

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

863 self._exec_manager_command( 

864 host, 

865 'prepareSr' if enabled else 'releaseSr', 

866 {'groupName': group_name}, 

867 'SRUnavailable' 

868 ) 

869 

870 def _prepare_sr_on_all_hosts(self, group_name, enabled): 

871 master = util.get_master_ref(self.session) 

872 self._prepare_sr(master, group_name, enabled) 

873 

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

875 self._prepare_sr(slave, group_name, enabled) 

876 

877 def _update_drbd_reactor(self, host, enabled): 

878 self._exec_manager_command( 

879 host, 

880 'updateDrbdReactor', 

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

882 'SRUnavailable' 

883 ) 

884 

885 def _update_drbd_reactor_on_all_hosts( 

886 self, enabled, controller_node_name=None 

887 ): 

888 if controller_node_name == 'localhost': 

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

890 util.get_this_host_ref(self.session) 

891 )['hostname'] 

892 assert controller_node_name 

893 assert controller_node_name != 'localhost' 

894 

895 controller_host = None 

896 secondary_hosts = [] 

897 

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

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

900 hostname = host_rec['hostname'] 

901 if controller_node_name == hostname: 

902 controller_host = host_ref 

903 else: 

904 secondary_hosts.append((host_ref, hostname)) 

905 

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

907 if controller_node_name and not controller_host: 

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

909 controller_node_name 

910 )) 

911 

912 if enabled and controller_host: 

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

914 action_name, controller_node_name 

915 )) 

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

917 # node name first. 

918 self._update_drbd_reactor(controller_host, enabled) 

919 

920 for host_ref, hostname in secondary_hosts: 

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

922 action_name, hostname 

923 )) 

924 self._update_drbd_reactor(host_ref, enabled) 

925 

926 if not enabled and controller_host: 

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

928 action_name, controller_node_name 

929 )) 

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

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

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

933 self._update_drbd_reactor(controller_host, enabled) 

934 

935 # -------------------------------------------------------------------------- 

936 # Metadata. 

937 # -------------------------------------------------------------------------- 

938 

939 def _synchronize_metadata_and_xapi(self): 

940 try: 

941 # First synch SR parameters. 

942 self.update(self.uuid) 

943 

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

945 xenapi = self.session.xenapi 

946 volumes_metadata = self._linstor.get_volumes_with_metadata() 

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

948 try: 

949 vdi_ref = xenapi.VDI.get_by_uuid(vdi_uuid) 

950 except Exception: 

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

952 continue 

953 

954 label = util.to_plain_string( 

955 xenapi.VDI.get_name_label(vdi_ref) 

956 ) 

957 description = util.to_plain_string( 

958 xenapi.VDI.get_name_description(vdi_ref) 

959 ) 

960 

961 if ( 

962 volume_metadata.get(NAME_LABEL_TAG) != label or 

963 volume_metadata.get(NAME_DESCRIPTION_TAG) != description 

964 ): 

965 self._linstor.update_volume_metadata(vdi_uuid, { 

966 NAME_LABEL_TAG: label, 

967 NAME_DESCRIPTION_TAG: description 

968 }) 

969 except Exception as e: 

970 raise xs_errors.XenError( 

971 'MetadataError', 

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

973 ) 

974 

975 def _synchronize_metadata(self): 

976 if not self.is_master(): 

977 return 

978 

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

980 if self.cmd == 'sr_attach': 

981 try: 

982 util.SMlog( 

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

984 ) 

985 self._synchronize_metadata_and_xapi() 

986 except Exception as e: 

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

988 

989 # -------------------------------------------------------------------------- 

990 # Stats. 

991 # -------------------------------------------------------------------------- 

992 

993 def _update_stats(self, virt_alloc_delta): 

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

995 self.sr_ref 

996 )) 

997 

998 # Update size attributes of the SR parent class. 

999 self.virtual_allocation = valloc + virt_alloc_delta 

1000 

1001 self._update_physical_size() 

1002 

1003 # Notify SR parent class. 

1004 self._db_update() 

1005 

1006 def _update_physical_size(self): 

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

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

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

1010 self.physical_size = min_physical_size * pool_count // \ 

1011 self._linstor.redundancy 

1012 

1013 self.physical_utilisation = self._linstor.allocated_volume_size 

1014 

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

1016 # VDIs. 

1017 # -------------------------------------------------------------------------- 

1018 

1019 def _load_vdis(self): 

1020 if self._vdis_loaded: 

1021 return 

1022 

1023 assert self.is_master() 

1024 

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

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

1027 # enjoy it with a few lines. 

1028 self._create_linstor_cache() 

1029 self._load_vdis_ex() 

1030 self._destroy_linstor_cache() 

1031 

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

1033 self._vdis_loaded = True 

1034 

1035 self._undo_all_journal_transactions() 

1036 

1037 def _load_vdis_ex(self): 

1038 # 1. Get existing VDIs in XAPI. 

1039 xenapi = self.session.xenapi 

1040 xapi_vdi_uuids = set() 

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

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

1043 

1044 # 2. Get volumes info. 

1045 all_volume_info = self._all_volume_info_cache 

1046 volumes_metadata = self._all_volume_metadata_cache 

1047 

1048 # 3. Get CBT vdis. 

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

1050 cbt_vdis = set() 

1051 for volume_metadata in volumes_metadata.values(): 

1052 cbt_uuid = volume_metadata.get(CBTLOG_TAG) 

1053 if cbt_uuid: 

1054 cbt_vdis.add(cbt_uuid) 

1055 

1056 introduce = False 

1057 

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

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

1060 has_clone_entries = list(self._journaler.get_all( 

1061 LinstorJournaler.CLONE 

1062 ).items()) 

1063 

1064 if has_clone_entries: 

1065 util.SMlog( 

1066 'Cannot introduce VDIs during scan because it exists ' 

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

1068 ) 

1069 else: 

1070 introduce = True 

1071 

1072 # 4. Now check all volume info. 

1073 vdi_to_snaps = {} 

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

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

1076 continue 

1077 

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

1079 if vdi_uuid not in xapi_vdi_uuids: 

1080 if not introduce: 

1081 continue 

1082 

1083 if vdi_uuid.startswith('DELETED_'): 

1084 continue 

1085 

1086 volume_metadata = volumes_metadata.get(vdi_uuid) 

1087 if not volume_metadata: 

1088 util.SMlog( 

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

1090 .format(vdi_uuid) 

1091 ) 

1092 continue 

1093 

1094 util.SMlog( 

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

1096 'LINSTOR and not in XAPI...' 

1097 .format(vdi_uuid) 

1098 ) 

1099 

1100 try: 

1101 self._linstor.get_device_path(vdi_uuid) 

1102 except Exception as e: 

1103 util.SMlog( 

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

1105 .format(vdi_uuid, e) 

1106 ) 

1107 continue 

1108 

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

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

1111 vdi_type = volume_metadata.get(VDI_TYPE_TAG) 

1112 

1113 if not vdi_type: 

1114 util.SMlog( 

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

1116 'without vdi_type' 

1117 ) 

1118 continue 

1119 

1120 sm_config = { 

1121 'vdi_type': vdi_type 

1122 } 

1123 

1124 if vdi_type == vhdutil.VDI_TYPE_RAW: 

1125 managed = not volume_metadata.get(HIDDEN_TAG) 

1126 elif vdi_type == vhdutil.VDI_TYPE_VHD: 

1127 vhd_info = self._vhdutil.get_vhd_info(vdi_uuid) 

1128 managed = not vhd_info.hidden 

1129 if vhd_info.parentUuid: 

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

1131 else: 

1132 util.SMlog( 

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

1134 .format(vdi_uuid, vdi_type) 

1135 ) 

1136 continue 

1137 

1138 util.SMlog( 

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

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

1141 name_label, 

1142 volume_info.virtual_size, 

1143 volume_info.allocated_size 

1144 ) 

1145 ) 

1146 

1147 vdi_ref = xenapi.VDI.db_introduce( 

1148 vdi_uuid, 

1149 name_label, 

1150 volume_metadata.get(NAME_DESCRIPTION_TAG) or '', 

1151 self.sr_ref, 

1152 type, 

1153 False, # sharable 

1154 bool(volume_metadata.get(READ_ONLY_TAG)), 

1155 {}, # other_config 

1156 vdi_uuid, # location 

1157 {}, # xenstore_data 

1158 sm_config, 

1159 managed, 

1160 str(volume_info.virtual_size), 

1161 str(volume_info.allocated_size) 

1162 ) 

1163 

1164 is_a_snapshot = volume_metadata.get(IS_A_SNAPSHOT_TAG) 

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

1166 if is_a_snapshot: 

1167 xenapi.VDI.set_snapshot_time( 

1168 vdi_ref, 

1169 xmlrpc.client.DateTime( 

1170 volume_metadata[SNAPSHOT_TIME_TAG] or 

1171 '19700101T00:00:00Z' 

1172 ) 

1173 ) 

1174 

1175 snap_uuid = volume_metadata[SNAPSHOT_OF_TAG] 

1176 if snap_uuid in vdi_to_snaps: 

1177 vdi_to_snaps[snap_uuid].append(vdi_uuid) 

1178 else: 

1179 vdi_to_snaps[snap_uuid] = [vdi_uuid] 

1180 

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

1182 vdi = self.vdi(vdi_uuid) 

1183 self.vdis[vdi_uuid] = vdi 

1184 

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

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

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

1188 vdi.sm_config_override['key_hash'] = \ 

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

1190 

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

1192 # or already in XAPI. 

1193 cbt_uuid = volume_metadata.get(CBTLOG_TAG) 

1194 if cbt_uuid in cbt_vdis: 

1195 vdi_ref = xenapi.VDI.get_by_uuid(vdi_uuid) 

1196 xenapi.VDI.set_cbt_enabled(vdi_ref, True) 

1197 # For existing VDIs, update local state too. 

1198 # Scan in base class SR updates existing VDIs 

1199 # again based on local states. 

1200 self.vdis[vdi_uuid].cbt_enabled = True 

1201 cbt_vdis.remove(cbt_uuid) 

1202 

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

1204 for src_uuid in vdi_to_snaps: 

1205 try: 

1206 src_ref = xenapi.VDI.get_by_uuid(src_uuid) 

1207 except Exception: 

1208 # The source VDI no longer exists, continue. 

1209 continue 

1210 

1211 for snap_uuid in vdi_to_snaps[src_uuid]: 

1212 try: 

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

1214 snap_ref = xenapi.VDI.get_by_uuid(snap_uuid) 

1215 xenapi.VDI.set_snapshot_of(snap_ref, src_ref) 

1216 except Exception as e: 

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

1218 

1219 # TODO: Check correctly how to use CBT. 

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

1221 

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

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

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

1225 for cbt_uuid in cbt_vdis: 

1226 new_vdi = self.vdi(cbt_uuid) 

1227 new_vdi.ty = 'cbt_metadata' 

1228 new_vdi.cbt_enabled = True 

1229 self.vdis[cbt_uuid] = new_vdi 

1230 

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

1232 self.virtual_allocation = 0 

1233 

1234 # 8. Build geneology. 

1235 geneology = {} 

1236 

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

1238 if vdi.parent: 

1239 if vdi.parent in self.vdis: 

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

1241 if vdi.parent in geneology: 

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

1243 else: 

1244 geneology[vdi.parent] = [vdi_uuid] 

1245 if not vdi.hidden: 

1246 self.virtual_allocation += vdi.size 

1247 

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

1249 # will be GC'ed. 

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

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

1252 util.SMlog( 

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

1254 ) 

1255 del self.vdis[vdi_uuid] 

1256 

1257 # -------------------------------------------------------------------------- 

1258 # Journals. 

1259 # -------------------------------------------------------------------------- 

1260 

1261 def _get_vdi_path_and_parent(self, vdi_uuid, volume_name): 

1262 try: 

1263 device_path = self._linstor.build_device_path(volume_name) 

1264 if not util.pathexists(device_path): 

1265 return (None, None) 

1266 

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

1268 volume_metadata = self._linstor.get_volume_metadata(vdi_uuid) 

1269 vdi_type = volume_metadata[VDI_TYPE_TAG] 

1270 if vdi_type == vhdutil.VDI_TYPE_RAW: 

1271 return (device_path, None) 

1272 

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

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

1275 return (None, None) 

1276 

1277 vhd_info = self._vhdutil.get_vhd_info(vdi_uuid) 

1278 if vhd_info: 

1279 return (device_path, vhd_info.parentUuid) 

1280 except Exception as e: 

1281 util.SMlog( 

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

1283 .format(e) 

1284 ) 

1285 return (None, None) 

1286 

1287 def _undo_all_journal_transactions(self): 

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

1289 self.lock.acquire() 

1290 try: 

1291 self._handle_interrupted_inflate_ops() 

1292 self._handle_interrupted_clone_ops() 

1293 pass 

1294 finally: 

1295 self.lock.release() 

1296 

1297 def _handle_interrupted_inflate_ops(self): 

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

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

1300 self._handle_interrupted_inflate(vdi_uuid, old_size) 

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

1302 

1303 def _handle_interrupted_clone_ops(self): 

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

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

1306 self._handle_interrupted_clone(vdi_uuid, old_size) 

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

1308 

1309 def _handle_interrupted_inflate(self, vdi_uuid, old_size): 

1310 util.SMlog( 

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

1312 .format(vdi_uuid, old_size) 

1313 ) 

1314 

1315 vdi = self.vdis.get(vdi_uuid) 

1316 if not vdi: 

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

1318 return 

1319 

1320 assert not self._all_volume_info_cache 

1321 volume_info = self._linstor.get_volume_info(vdi_uuid) 

1322 

1323 current_size = volume_info.virtual_size 

1324 assert current_size > 0 

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

1326 

1327 def _handle_interrupted_clone( 

1328 self, vdi_uuid, clone_info, force_undo=False 

1329 ): 

1330 util.SMlog( 

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

1332 .format(vdi_uuid, clone_info) 

1333 ) 

1334 

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

1336 

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

1338 volume_names = self._linstor.get_volumes_with_name() 

1339 

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

1341 if base_uuid not in volume_names: 

1342 if vdi_uuid in volume_names: 

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

1344 return 

1345 raise util.SMException( 

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

1347 .format(base_uuid, vdi_uuid) 

1348 ) 

1349 

1350 if force_undo: 

1351 util.SMlog('Explicit revert') 

1352 self._undo_clone( 

1353 volume_names, vdi_uuid, base_uuid, snap_uuid 

1354 ) 

1355 return 

1356 

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

1358 if vdi_uuid not in volume_names or \ 

1359 (snap_uuid and snap_uuid not in volume_names): 

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

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

1362 return 

1363 

1364 vdi_path, vdi_parent_uuid = self._get_vdi_path_and_parent( 

1365 vdi_uuid, volume_names[vdi_uuid] 

1366 ) 

1367 snap_path, snap_parent_uuid = self._get_vdi_path_and_parent( 

1368 snap_uuid, volume_names[snap_uuid] 

1369 ) 

1370 

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

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

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

1374 return 

1375 

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

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

1378 

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

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

1381 base_metadata = self._linstor.get_volume_metadata(base_uuid) 

1382 base_type = base_metadata[VDI_TYPE_TAG] 

1383 

1384 if not util.pathexists(base_path): 

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

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

1387 return 

1388 

1389 # Un-hide the parent. 

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

1391 if base_type == vhdutil.VDI_TYPE_VHD: 

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

1393 if vhd_info.hidden: 

1394 self._vhdutil.set_hidden(base_path, False) 

1395 elif base_type == vhdutil.VDI_TYPE_RAW and \ 

1396 base_metadata.get(HIDDEN_TAG): 

1397 self._linstor.update_volume_metadata( 

1398 base_uuid, {HIDDEN_TAG: False} 

1399 ) 

1400 

1401 # Remove the child nodes. 

1402 if snap_uuid and snap_uuid in volume_names: 

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

1404 

1405 try: 

1406 self._linstor.destroy_volume(snap_uuid) 

1407 except Exception as e: 

1408 util.SMlog( 

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

1410 .format(snap_uuid, e) 

1411 ) 

1412 

1413 if vdi_uuid in volume_names: 

1414 try: 

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

1416 self._linstor.destroy_volume(vdi_uuid) 

1417 except Exception as e: 

1418 util.SMlog( 

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

1420 .format(vdi_uuid, e) 

1421 ) 

1422 # We can get an exception like this: 

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

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

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

1426 # UUID of this bad VDI before. 

1427 self._linstor.update_volume_uuid( 

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

1429 ) 

1430 

1431 # Rename! 

1432 self._linstor.update_volume_uuid(base_uuid, vdi_uuid) 

1433 

1434 # Inflate to the right size. 

1435 if base_type == vhdutil.VDI_TYPE_VHD: 

1436 vdi = self.vdi(vdi_uuid) 

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

1438 self._vhdutil.inflate( 

1439 self._journaler, vdi_uuid, vdi.path, 

1440 volume_size, vdi.capacity 

1441 ) 

1442 self.vdis[vdi_uuid] = vdi 

1443 

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

1445 # flag to facilitate vm deactivate. 

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

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

1448 

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

1450 

1451 # -------------------------------------------------------------------------- 

1452 # Cache. 

1453 # -------------------------------------------------------------------------- 

1454 

1455 def _create_linstor_cache(self): 

1456 reconnect = False 

1457 

1458 def create_cache(): 

1459 nonlocal reconnect 

1460 try: 

1461 if reconnect: 

1462 self._reconnect() 

1463 return self._linstor.get_volumes_with_info() 

1464 except Exception as e: 

1465 reconnect = True 

1466 raise e 

1467 

1468 self._all_volume_metadata_cache = \ 

1469 self._linstor.get_volumes_with_metadata() 

1470 self._all_volume_info_cache = util.retry( 

1471 create_cache, 

1472 maxretry=10, 

1473 period=3 

1474 ) 

1475 

1476 def _destroy_linstor_cache(self): 

1477 self._all_volume_info_cache = None 

1478 self._all_volume_metadata_cache = None 

1479 

1480 # -------------------------------------------------------------------------- 

1481 # Misc. 

1482 # -------------------------------------------------------------------------- 

1483 

1484 def _reconnect(self): 

1485 controller_uri = get_controller_uri() 

1486 

1487 self._journaler = LinstorJournaler( 

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

1489 ) 

1490 

1491 # Try to open SR if exists. 

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

1493 # we are trying to execute an exclusive operation. 

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

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

1496 # the SR is locked. 

1497 self._linstor = LinstorVolumeManager( 

1498 controller_uri, 

1499 self._group_name, 

1500 repair=( 

1501 self.is_master() and 

1502 self.srcmd.cmd in self.ops_exclusive 

1503 ), 

1504 logger=util.SMlog 

1505 ) 

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

1507 

1508 def _ensure_space_available(self, amount_needed): 

1509 space_available = self._linstor.max_volume_size_allowed 

1510 if (space_available < amount_needed): 

1511 util.SMlog( 

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

1513 space_available, amount_needed 

1514 ) 

1515 ) 

1516 raise xs_errors.XenError('SRNoSpace') 

1517 

1518 def _kick_gc(self): 

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

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

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

1522 lock = Lock(LOCK_TYPE_GC_RUNNING, self.uuid) 

1523 if not lock.acquireNoblock(): 

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

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

1526 return 

1527 

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

1529 try: 

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

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

1532 except util.CommandException as e: 

1533 if e.code != errno.ETIMEDOUT: 

1534 raise 

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

1536 else: 

1537 lock.release() 

1538 

1539 util.SMlog('Kicking GC') 

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

1541 

1542# ============================================================================== 

1543# LinstorSr VDI 

1544# ============================================================================== 

1545 

1546 

1547class LinstorVDI(VDI.VDI): 

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

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

1550 TYPE_RAW = 'raw' 

1551 TYPE_VHD = 'vhd' 

1552 

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

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

1555 # Increase the performance when resize is called. 

1556 MAX_METADATA_VIRT_SIZE = 2 * 1024 * 1024 

1557 

1558 # -------------------------------------------------------------------------- 

1559 # VDI methods. 

1560 # -------------------------------------------------------------------------- 

1561 

1562 def load(self, vdi_uuid): 

1563 self._lock = self.sr.lock 

1564 self._exists = True 

1565 self._linstor = self.sr._linstor 

1566 

1567 # Update hidden parent property. 

1568 self.hidden = False 

1569 

1570 def raise_bad_load(e): 

1571 util.SMlog( 

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

1573 ) 

1574 util.SMlog(traceback.format_exc()) 

1575 raise xs_errors.XenError( 

1576 'VDIUnavailable', 

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

1578 ) 

1579 

1580 # Try to load VDI. 

1581 try: 

1582 if ( 

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

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

1585 ): 

1586 self.vdi_type = vhdutil.VDI_TYPE_RAW 

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

1588 else: 

1589 self._determine_type_and_path() 

1590 self._load_this() 

1591 

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

1593 self.uuid, self.path, self.hidden 

1594 )) 

1595 except LinstorVolumeManagerError as e: 

1596 # 1. It may be a VDI deletion. 

1597 if e.code == LinstorVolumeManagerError.ERR_VOLUME_NOT_EXISTS: 

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

1599 self.deleted = True 

1600 return 

1601 

1602 # 2. Or maybe a creation. 

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

1604 # Set type attribute of VDI parent class. 

1605 # We use VHD by default. 

1606 self.vdi_type = vhdutil.VDI_TYPE_VHD 

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

1608 

1609 self._exists = False 

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

1611 if vdi_sm_config is not None: 

1612 type = vdi_sm_config.get('type') 

1613 if type is not None: 

1614 if type == self.TYPE_RAW: 

1615 self.vdi_type = vhdutil.VDI_TYPE_RAW 

1616 elif type == self.TYPE_VHD: 

1617 self.vdi_type = vhdutil.VDI_TYPE_VHD 

1618 else: 

1619 raise xs_errors.XenError( 

1620 'VDICreate', 

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

1622 ) 

1623 if self.vdi_type == vhdutil.VDI_TYPE_VHD: 

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

1625 

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

1627 self._update_device_name(None) 

1628 return 

1629 raise_bad_load(e) 

1630 except Exception as e: 

1631 raise_bad_load(e) 

1632 

1633 def create(self, sr_uuid, vdi_uuid, size): 

1634 # Usage example: 

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

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

1637 

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

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

1640 if self._exists: 

1641 raise xs_errors.XenError('VDIExists') 

1642 

1643 assert self.uuid 

1644 assert self.ty 

1645 assert self.vdi_type 

1646 

1647 # 2. Compute size and check space available. 

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

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

1650 util.SMlog( 

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

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

1653 ) 

1654 self.sr._ensure_space_available(volume_size) 

1655 

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

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

1658 

1659 # 4. Create! 

1660 failed = False 

1661 try: 

1662 volume_name = None 

1663 if self.ty == 'ha_statefile': 

1664 volume_name = HA_VOLUME_NAME 

1665 elif self.ty == 'redo_log': 

1666 volume_name = REDO_LOG_VOLUME_NAME 

1667 

1668 self._linstor.create_volume( 

1669 self.uuid, 

1670 volume_size, 

1671 persistent=False, 

1672 volume_name=volume_name, 

1673 high_availability=volume_name is not None 

1674 ) 

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

1676 

1677 self._update_device_name(volume_info.name) 

1678 

1679 if self.vdi_type == vhdutil.VDI_TYPE_RAW: 

1680 self.size = volume_info.virtual_size 

1681 else: 

1682 self.sr._vhdutil.create( 

1683 self.path, size, False, self.MAX_METADATA_VIRT_SIZE 

1684 ) 

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

1686 

1687 if self._key_hash: 

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

1689 

1690 # Because vhdutil commands modify the volume data, 

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

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

1693 

1694 volume_metadata = { 

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

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

1697 IS_A_SNAPSHOT_TAG: False, 

1698 SNAPSHOT_OF_TAG: '', 

1699 SNAPSHOT_TIME_TAG: '', 

1700 TYPE_TAG: self.ty, 

1701 VDI_TYPE_TAG: self.vdi_type, 

1702 READ_ONLY_TAG: bool(self.read_only), 

1703 METADATA_OF_POOL_TAG: '' 

1704 } 

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

1706 

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

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

1709 # an already opened volume. 

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

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

1712 

1713 self._linstor.mark_volume_as_persistent(self.uuid) 

1714 except util.CommandException as e: 

1715 failed = True 

1716 raise xs_errors.XenError( 

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

1718 ) 

1719 except Exception as e: 

1720 failed = True 

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

1722 finally: 

1723 if failed: 

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

1725 try: 

1726 self._linstor.destroy_volume(self.uuid) 

1727 except Exception as e: 

1728 util.SMlog( 

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

1730 '{}'.format(e) 

1731 ) 

1732 

1733 self.utilisation = volume_info.allocated_size 

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

1735 

1736 self.ref = self._db_introduce() 

1737 self.sr._update_stats(self.size) 

1738 

1739 return VDI.VDI.get_params(self) 

1740 

1741 def delete(self, sr_uuid, vdi_uuid, data_only=False): 

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

1743 if self.attached: 

1744 raise xs_errors.XenError('VDIInUse') 

1745 

1746 if self.deleted: 

1747 return super(LinstorVDI, self).delete( 

1748 sr_uuid, vdi_uuid, data_only 

1749 ) 

1750 

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

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

1753 raise xs_errors.XenError( 

1754 'VDIDelete', 

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

1756 ) 

1757 

1758 try: 

1759 # Remove from XAPI and delete from LINSTOR. 

1760 self._linstor.destroy_volume(self.uuid) 

1761 if not data_only: 

1762 self._db_forget() 

1763 

1764 self.sr.lock.cleanupAll(vdi_uuid) 

1765 except Exception as e: 

1766 util.SMlog( 

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

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

1769 ) 

1770 

1771 try: 

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

1773 except LinstorVolumeManagerError as e: 

1774 if e.code != LinstorVolumeManagerError.ERR_VOLUME_DESTROY: 

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

1776 

1777 return 

1778 

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

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

1781 

1782 # TODO: Check size after delete. 

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

1784 self.sr._kick_gc() 

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

1786 

1787 def attach(self, sr_uuid, vdi_uuid): 

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

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

1790 if ( 

1791 not attach_from_config or 

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

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

1794 raise xs_errors.XenError( 

1795 'VDIUnavailable', 

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

1797 'scan SR first to trigger auto-repair' 

1798 ) 

1799 

1800 if not attach_from_config or self.sr._is_master: 

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

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

1803 

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

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

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

1807 # than the VHD size + bitmap size. 

1808 need_inflate = True 

1809 if ( 

1810 self.vdi_type == vhdutil.VDI_TYPE_RAW or 

1811 not writable or 

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

1813 ): 

1814 need_inflate = False 

1815 

1816 if need_inflate: 

1817 try: 

1818 self._prepare_thin(True) 

1819 except Exception as e: 

1820 raise xs_errors.XenError( 

1821 'VDIUnavailable', 

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

1823 .format(e) 

1824 ) 

1825 

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

1827 self.xenstore_data = {} 

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

1829 

1830 if ( 

1831 USE_HTTP_NBD_SERVERS and 

1832 attach_from_config and 

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

1834 ): 

1835 return self._attach_using_http_nbd() 

1836 

1837 # Ensure we have a path... 

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

1839 

1840 self.attached = True 

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

1842 

1843 def detach(self, sr_uuid, vdi_uuid): 

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

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

1846 self.attached = False 

1847 

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

1849 return self._detach_using_http_nbd() 

1850 

1851 if self.vdi_type == vhdutil.VDI_TYPE_RAW: 

1852 return 

1853 

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

1855 # equal to the LINSTOR volume size. 

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

1857 already_deflated = self.capacity <= volume_size 

1858 

1859 if already_deflated: 

1860 util.SMlog( 

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

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

1863 ) 

1864 

1865 need_deflate = True 

1866 if already_deflated: 

1867 need_deflate = False 

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

1869 need_deflate = False 

1870 

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

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

1873 need_deflate = True 

1874 

1875 if need_deflate: 

1876 try: 

1877 self._prepare_thin(False) 

1878 except Exception as e: 

1879 raise xs_errors.XenError( 

1880 'VDIUnavailable', 

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

1882 .format(e) 

1883 ) 

1884 

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

1886 if self.sr.is_master(): 

1887 return 

1888 

1889 while vdi_uuid: 

1890 try: 

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

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

1893 except Exception: 

1894 break 

1895 

1896 if util.pathexists(path): 

1897 try: 

1898 self._linstor.remove_volume_if_diskless(vdi_uuid) 

1899 except Exception as e: 

1900 # Ensure we can always detach properly. 

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

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

1903 vdi_uuid = parent_vdi_uuid 

1904 

1905 def resize(self, sr_uuid, vdi_uuid, size): 

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

1907 if not self.sr.is_master(): 

1908 raise xs_errors.XenError( 

1909 'VDISize', 

1910 opterr='resize on slave not allowed' 

1911 ) 

1912 

1913 if self.hidden: 

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

1915 

1916 # Compute the virtual VHD and DRBD volume size. 

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

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

1919 util.SMlog( 

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

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

1922 ) 

1923 

1924 if size < self.size: 

1925 util.SMlog( 

1926 'vdi_resize: shrinking not supported: ' 

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

1928 ) 

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

1930 

1931 if size == self.size: 

1932 return VDI.VDI.get_params(self) 

1933 

1934 if self.vdi_type == vhdutil.VDI_TYPE_RAW: 

1935 old_volume_size = self.size 

1936 new_volume_size = LinstorVolumeManager.round_up_volume_size(size) 

1937 else: 

1938 old_volume_size = self.utilisation 

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

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

1941 new_volume_size = old_volume_size 

1942 else: 

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

1944 assert new_volume_size >= old_volume_size 

1945 

1946 space_needed = new_volume_size - old_volume_size 

1947 self.sr._ensure_space_available(space_needed) 

1948 

1949 old_size = self.size 

1950 if self.vdi_type == vhdutil.VDI_TYPE_RAW: 

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

1952 else: 

1953 if new_volume_size != old_volume_size: 

1954 self.sr._vhdutil.inflate( 

1955 self.sr._journaler, self.uuid, self.path, 

1956 new_volume_size, old_volume_size 

1957 ) 

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

1959 

1960 # Reload size attributes. 

1961 self._load_this() 

1962 

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

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

1965 self.session.xenapi.VDI.set_physical_utilisation( 

1966 vdi_ref, str(self.utilisation) 

1967 ) 

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

1969 return VDI.VDI.get_params(self) 

1970 

1971 def clone(self, sr_uuid, vdi_uuid): 

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

1973 

1974 def compose(self, sr_uuid, vdi1, vdi2): 

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

1976 if self.vdi_type != vhdutil.VDI_TYPE_VHD: 

1977 raise xs_errors.XenError('Unimplemented') 

1978 

1979 parent_uuid = vdi1 

1980 parent_path = self._linstor.get_device_path(parent_uuid) 

1981 

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

1983 # have a readonly error. 

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

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

1986 

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

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

1989 try: 

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

1991 self.sr._vhdutil.set_hidden(parent_path) 

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

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

1994 ) 

1995 finally: 

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

1997 

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

1999 raise util.SMException( 

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

2001 ) 

2002 

2003 util.SMlog('Compose done') 

2004 

2005 def generate_config(self, sr_uuid, vdi_uuid): 

2006 """ 

2007 Generate the XML config required to attach and activate 

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

2009 activation is handled by vdi_attach_from_config below. 

2010 """ 

2011 

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

2013 

2014 resp = {} 

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

2016 resp['sr_uuid'] = sr_uuid 

2017 resp['vdi_uuid'] = self.uuid 

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

2019 resp['command'] = 'vdi_attach_from_config' 

2020 

2021 # By default, we generate a normal config. 

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

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

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

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

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

2027 # instead. 

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

2029 if not USE_HTTP_NBD_SERVERS or volume_name not in [ 

2030 HA_VOLUME_NAME, REDO_LOG_VOLUME_NAME 

2031 ]: 

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

2033 available = False 

2034 # Try to refresh symlink path... 

2035 try: 

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

2037 available = util.pathexists(self.path) 

2038 except Exception: 

2039 pass 

2040 if not available: 

2041 raise xs_errors.XenError('VDIUnavailable') 

2042 

2043 resp['vdi_path'] = self.path 

2044 else: 

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

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

2047 

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

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

2050 

2051 def attach_from_config(self, sr_uuid, vdi_uuid): 

2052 """ 

2053 Attach and activate a VDI using config generated by 

2054 vdi_generate_config above. This is used for cases such as 

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

2056 """ 

2057 

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

2059 

2060 try: 

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

2062 self.sr.attach(sr_uuid) 

2063 

2064 if not DRIVER_CONFIG['ATTACH_FROM_CONFIG_WITH_TAPDISK']: 

2065 return self.attach(sr_uuid, vdi_uuid) 

2066 except Exception: 

2067 util.logException('LinstorVDI.attach_from_config') 

2068 raise xs_errors.XenError( 

2069 'SRUnavailable', 

2070 opterr='Unable to attach from config' 

2071 ) 

2072 

2073 def reset_leaf(self, sr_uuid, vdi_uuid): 

2074 if self.vdi_type != vhdutil.VDI_TYPE_VHD: 

2075 raise xs_errors.XenError('Unimplemented') 

2076 

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

2078 raise util.SMException( 

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

2080 .format(self.uuid) 

2081 ) 

2082 

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

2084 

2085 def _load_this(self): 

2086 volume_metadata = None 

2087 if self.sr._all_volume_metadata_cache: 

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

2089 if volume_metadata is None: 

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

2091 

2092 volume_info = None 

2093 if self.sr._all_volume_info_cache: 

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

2095 if volume_info is None: 

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

2097 

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

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

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

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

2102 # be lower than virtual size at creation. 

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

2104 self.utilisation = volume_info.allocated_size 

2105 self.capacity = volume_info.virtual_size 

2106 

2107 if self.vdi_type == vhdutil.VDI_TYPE_RAW: 

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

2109 self.size = volume_info.virtual_size 

2110 self.parent = '' 

2111 else: 

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

2113 self.hidden = vhd_info.hidden 

2114 self.size = vhd_info.sizeVirt 

2115 self.parent = vhd_info.parentUuid 

2116 

2117 if self.hidden: 

2118 self.managed = False 

2119 

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

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

2122 

2123 # Update sm_config_override of VDI parent class. 

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

2125 

2126 def _mark_hidden(self, hidden=True): 

2127 if self.hidden == hidden: 

2128 return 

2129 

2130 if self.vdi_type == vhdutil.VDI_TYPE_VHD: 

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

2132 else: 

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

2134 HIDDEN_TAG: hidden 

2135 }) 

2136 self.hidden = hidden 

2137 

2138 def update(self, sr_uuid, vdi_uuid): 

2139 xenapi = self.session.xenapi 

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

2141 

2142 volume_metadata = { 

2143 NAME_LABEL_TAG: util.to_plain_string( 

2144 xenapi.VDI.get_name_label(vdi_ref) 

2145 ), 

2146 NAME_DESCRIPTION_TAG: util.to_plain_string( 

2147 xenapi.VDI.get_name_description(vdi_ref) 

2148 ) 

2149 } 

2150 

2151 try: 

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

2153 except LinstorVolumeManagerError as e: 

2154 if e.code == LinstorVolumeManagerError.ERR_VOLUME_NOT_EXISTS: 

2155 raise xs_errors.XenError( 

2156 'VDIUnavailable', 

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

2158 ) 

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

2160 

2161 # -------------------------------------------------------------------------- 

2162 # Thin provisioning. 

2163 # -------------------------------------------------------------------------- 

2164 

2165 def _prepare_thin(self, attach): 

2166 if self.sr.is_master(): 

2167 if attach: 

2168 attach_thin( 

2169 self.session, self.sr._journaler, self._linstor, 

2170 self.sr.uuid, self.uuid 

2171 ) 

2172 else: 

2173 detach_thin( 

2174 self.session, self._linstor, self.sr.uuid, self.uuid 

2175 ) 

2176 else: 

2177 fn = 'attach' if attach else 'detach' 

2178 

2179 master = util.get_master_ref(self.session) 

2180 

2181 args = { 

2182 'groupName': self.sr._group_name, 

2183 'srUuid': self.sr.uuid, 

2184 'vdiUuid': self.uuid 

2185 } 

2186 

2187 try: 

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

2189 except Exception: 

2190 if fn != 'detach': 

2191 raise 

2192 

2193 # Reload size attrs after inflate or deflate! 

2194 self._load_this() 

2195 self.sr._update_physical_size() 

2196 

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

2198 self.session.xenapi.VDI.set_physical_utilisation( 

2199 vdi_ref, str(self.utilisation) 

2200 ) 

2201 

2202 self.session.xenapi.SR.set_physical_utilisation( 

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

2204 ) 

2205 

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

2207 # Generic helpers. 

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

2209 

2210 def _determine_type_and_path(self): 

2211 """ 

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

2213 """ 

2214 

2215 # 1. Check vdi_ref and vdi_type in config. 

2216 try: 

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

2218 if vdi_ref: 

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

2220 vdi_type = sm_config.get('vdi_type') 

2221 if vdi_type: 

2222 # Update parent fields. 

2223 self.vdi_type = vdi_type 

2224 self.sm_config_override = sm_config 

2225 self._update_device_name( 

2226 self._linstor.get_volume_name(self.uuid) 

2227 ) 

2228 return 

2229 except Exception: 

2230 pass 

2231 

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

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

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

2235 self.vdi_type = volume_metadata.get(VDI_TYPE_TAG) 

2236 if not self.vdi_type: 

2237 raise xs_errors.XenError( 

2238 'VDIUnavailable', 

2239 opterr='failed to get vdi_type in metadata' 

2240 ) 

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

2242 

2243 def _update_device_name(self, device_name): 

2244 self._device_name = device_name 

2245 

2246 # Mark path of VDI parent class. 

2247 if device_name: 

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

2249 else: 

2250 self.path = None 

2251 

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

2253 """ 

2254 Snapshot self and return the snapshot VDI object. 

2255 """ 

2256 

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

2258 snap_path = self._linstor.shallow_clone_volume( 

2259 self.uuid, snap_uuid, persistent=False 

2260 ) 

2261 

2262 # 2. Write the snapshot content. 

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

2264 self.sr._vhdutil.snapshot( 

2265 snap_path, self.path, is_raw, self.MAX_METADATA_VIRT_SIZE 

2266 ) 

2267 

2268 # 3. Get snapshot parent. 

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

2270 

2271 # 4. Update metadata. 

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

2273 volume_metadata = { 

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

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

2276 IS_A_SNAPSHOT_TAG: bool(snap_of_uuid), 

2277 SNAPSHOT_OF_TAG: snap_of_uuid, 

2278 SNAPSHOT_TIME_TAG: '', 

2279 TYPE_TAG: self.ty, 

2280 VDI_TYPE_TAG: vhdutil.VDI_TYPE_VHD, 

2281 READ_ONLY_TAG: False, 

2282 METADATA_OF_POOL_TAG: '' 

2283 } 

2284 self._linstor.set_volume_metadata(snap_uuid, volume_metadata) 

2285 

2286 # 5. Set size. 

2287 snap_vdi = LinstorVDI(self.sr, snap_uuid) 

2288 if not snap_vdi._exists: 

2289 raise xs_errors.XenError('VDISnapshot') 

2290 

2291 volume_info = self._linstor.get_volume_info(snap_uuid) 

2292 

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

2294 snap_vdi.utilisation = volume_info.allocated_size 

2295 

2296 # 6. Update sm config. 

2297 snap_vdi.sm_config = {} 

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

2299 if snap_parent: 

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

2301 snap_vdi.parent = snap_parent 

2302 

2303 snap_vdi.label = self.label 

2304 snap_vdi.description = self.description 

2305 

2306 self._linstor.mark_volume_as_persistent(snap_uuid) 

2307 

2308 return snap_vdi 

2309 

2310 # -------------------------------------------------------------------------- 

2311 # Implement specific SR methods. 

2312 # -------------------------------------------------------------------------- 

2313 

2314 def _rename(self, oldpath, newpath): 

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

2316 volume_uuid = self._linstor.get_volume_uuid_from_device_path(oldpath) 

2317 self._linstor.update_volume_name(volume_uuid, newpath) 

2318 

2319 def _do_snapshot( 

2320 self, sr_uuid, vdi_uuid, snap_type, secondary=None, cbtlog=None 

2321 ): 

2322 # If cbt enabled, save file consistency state. 

2323 if cbtlog is not None: 

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

2325 consistency_state = False 

2326 else: 

2327 consistency_state = True 

2328 util.SMlog( 

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

2330 .format(consistency_state, vdi_uuid) 

2331 ) 

2332 else: 

2333 consistency_state = None 

2334 

2335 if self.vdi_type != vhdutil.VDI_TYPE_VHD: 

2336 raise xs_errors.XenError('Unimplemented') 

2337 

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

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

2340 try: 

2341 return self._snapshot(snap_type, cbtlog, consistency_state) 

2342 finally: 

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

2344 

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

2346 util.SMlog( 

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

2348 .format(self.uuid, snap_type) 

2349 ) 

2350 

2351 # 1. Checks... 

2352 if self.hidden: 

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

2354 

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

2356 if depth == -1: 

2357 raise xs_errors.XenError( 

2358 'VDIUnavailable', 

2359 opterr='failed to get VHD depth' 

2360 ) 

2361 elif depth >= vhdutil.MAX_CHAIN_SIZE: 

2362 raise xs_errors.XenError('SnapshotChainTooLong') 

2363 

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

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

2366 

2367 volume_path = self.path 

2368 if not util.pathexists(volume_path): 

2369 raise xs_errors.XenError( 

2370 'EIO', 

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

2372 ) 

2373 

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

2375 base_uuid = util.gen_uuid() 

2376 snap_uuid = None 

2377 

2378 if snap_type == VDI.SNAPSHOT_DOUBLE: 

2379 snap_uuid = util.gen_uuid() 

2380 

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

2382 

2383 active_uuid = self.uuid 

2384 self.sr._journaler.create( 

2385 LinstorJournaler.CLONE, active_uuid, clone_info 

2386 ) 

2387 

2388 try: 

2389 # 3. Self becomes the new base. 

2390 # The device path remains the same. 

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

2392 self.uuid = base_uuid 

2393 self.location = self.uuid 

2394 self.read_only = True 

2395 self.managed = False 

2396 

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

2398 active_vdi = self._create_snapshot(active_uuid) 

2399 

2400 snap_vdi = None 

2401 if snap_type == VDI.SNAPSHOT_DOUBLE: 

2402 snap_vdi = self._create_snapshot(snap_uuid, active_uuid) 

2403 

2404 self.label = 'base copy' 

2405 self.description = '' 

2406 

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

2408 # in subsequent scans. 

2409 self._mark_hidden() 

2410 self._linstor.update_volume_metadata( 

2411 self.uuid, {READ_ONLY_TAG: True} 

2412 ) 

2413 

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

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

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

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

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

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

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

2421 active_vdi.sm_config[key] = sm_config[key] 

2422 

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

2424 # delete base if unused. 

2425 introduce_parent = True 

2426 try: 

2427 snap_parent = None 

2428 if snap_vdi: 

2429 snap_parent = snap_vdi.parent 

2430 

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

2432 snap_type == VDI.SNAPSHOT_SINGLE or 

2433 snap_type == VDI.SNAPSHOT_INTERNAL or 

2434 snap_parent != self.uuid 

2435 ): 

2436 util.SMlog( 

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

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

2439 ) 

2440 introduce_parent = False 

2441 self._linstor.destroy_volume(self.uuid) 

2442 except Exception as e: 

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

2444 pass 

2445 

2446 # 8. Introduce the new VDI records. 

2447 if snap_vdi: 

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

2449 # new snapshot disk. 

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

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

2452 # TODO: Maybe remove key_hash support. 

2453 if 'key_hash' in sm_config: 

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

2455 # If we have CBT enabled on the VDI, 

2456 # set CBT status for the new snapshot disk. 

2457 if cbtlog: 

2458 snap_vdi.cbt_enabled = True 

2459 

2460 if snap_vdi: 

2461 snap_vdi_ref = snap_vdi._db_introduce() 

2462 util.SMlog( 

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

2464 .format(snap_vdi_ref, snap_vdi.uuid) 

2465 ) 

2466 if introduce_parent: 

2467 base_vdi_ref = self._db_introduce() 

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

2469 util.SMlog( 

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

2471 .format(base_vdi_ref, self.uuid) 

2472 ) 

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

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

2475 NAME_DESCRIPTION_TAG: util.to_plain_string( 

2476 self.description 

2477 ), 

2478 READ_ONLY_TAG: True, 

2479 METADATA_OF_POOL_TAG: '' 

2480 }) 

2481 

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

2483 if snap_type == VDI.SNAPSHOT_DOUBLE and cbtlog: 

2484 try: 

2485 self._cbt_snapshot(snap_uuid, cbt_consistency) 

2486 except Exception: 

2487 # CBT operation failed. 

2488 # TODO: Implement me. 

2489 raise 

2490 

2491 if snap_type != VDI.SNAPSHOT_INTERNAL: 

2492 self.sr._update_stats(self.size) 

2493 

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

2495 ret_vdi = snap_vdi 

2496 if not ret_vdi: 

2497 ret_vdi = self 

2498 if not ret_vdi: 

2499 ret_vdi = active_vdi 

2500 

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

2502 self.session.xenapi.VDI.set_sm_config( 

2503 vdi_ref, active_vdi.sm_config 

2504 ) 

2505 except Exception: 

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

2507 try: 

2508 self.sr._handle_interrupted_clone( 

2509 active_uuid, clone_info, force_undo=True 

2510 ) 

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

2512 except Exception as clean_error: 

2513 util.SMlog( 

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

2515 .format(clean_error) 

2516 ) 

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

2518 

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

2520 

2521 return ret_vdi.get_params() 

2522 

2523 @staticmethod 

2524 def _start_persistent_http_server(volume_name): 

2525 pid_path = None 

2526 http_server = None 

2527 

2528 try: 

2529 if volume_name == HA_VOLUME_NAME: 

2530 port = '8076' 

2531 else: 

2532 port = '8077' 

2533 

2534 try: 

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

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

2537 # block indefinitely. 

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

2539 host_ip = util.get_this_host_address(session) 

2540 except: 

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

2542 host_ip, _ = get_ips_from_xha_config_file() 

2543 if not host_ip: 

2544 raise Exception( 

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

2546 ) 

2547 

2548 arguments = [ 

2549 'http-disk-server', 

2550 '--disk', 

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

2552 '--ip', 

2553 host_ip, 

2554 '--port', 

2555 port 

2556 ] 

2557 

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

2559 http_server = subprocess.Popen( 

2560 [FORK_LOG_DAEMON] + arguments, 

2561 stdout=subprocess.PIPE, 

2562 stderr=subprocess.STDOUT, 

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

2564 # touch the current one. 

2565 preexec_fn=os.setsid 

2566 ) 

2567 

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

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

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

2571 

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

2573 def is_ready(): 

2574 while http_server.poll() is None: 

2575 line = http_server.stdout.readline() 

2576 if reg_server_ready.search(line): 

2577 return True 

2578 return False 

2579 try: 

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

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

2582 except util.TimeoutException: 

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

2584 except Exception as e: 

2585 if pid_path: 

2586 try: 

2587 os.remove(pid_path) 

2588 except Exception: 

2589 pass 

2590 

2591 if http_server: 

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

2593 try: 

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

2595 except: 

2596 pass 

2597 

2598 raise xs_errors.XenError( 

2599 'VDIUnavailable', 

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

2601 ) 

2602 

2603 def _start_persistent_nbd_server(self, volume_name): 

2604 pid_path = None 

2605 nbd_path = None 

2606 nbd_server = None 

2607 

2608 try: 

2609 # We use a precomputed device size. 

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

2611 if volume_name == HA_VOLUME_NAME: 

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

2613 port = '8076' 

2614 device_size = 4 * 1024 * 1024 

2615 else: 

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

2617 port = '8077' 

2618 device_size = 256 * 1024 * 1024 

2619 

2620 try: 

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

2622 ips = util.get_host_addresses(session) 

2623 except Exception as e: 

2624 _, ips = get_ips_from_xha_config_file() 

2625 if not ips: 

2626 raise Exception( 

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

2628 ) 

2629 ips = ips.values() 

2630 

2631 arguments = [ 

2632 'nbd-http-server', 

2633 '--socket-path', 

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

2635 '--nbd-name', 

2636 volume_name, 

2637 '--urls', 

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

2639 '--device-size', 

2640 str(device_size) 

2641 ] 

2642 

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

2644 nbd_server = subprocess.Popen( 

2645 [FORK_LOG_DAEMON] + arguments, 

2646 stdout=subprocess.PIPE, 

2647 stderr=subprocess.STDOUT, 

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

2649 # touch the current one. 

2650 preexec_fn=os.setsid 

2651 ) 

2652 

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

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

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

2656 

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

2658 def get_nbd_path(): 

2659 while nbd_server.poll() is None: 

2660 line = nbd_server.stdout.readline() 

2661 match = reg_nbd_path.search(line) 

2662 if match: 

2663 return match.group(1) 

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

2665 try: 

2666 nbd_path = util.timeout_call(10, get_nbd_path) 

2667 if nbd_path is None: 

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

2669 except util.TimeoutException: 

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

2671 

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

2673 os.symlink(nbd_path, self.path) 

2674 except Exception as e: 

2675 if pid_path: 

2676 try: 

2677 os.remove(pid_path) 

2678 except Exception: 

2679 pass 

2680 

2681 if nbd_path: 

2682 try: 

2683 os.remove(nbd_path) 

2684 except Exception: 

2685 pass 

2686 

2687 if nbd_server: 

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

2689 try: 

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

2691 except: 

2692 pass 

2693 

2694 raise xs_errors.XenError( 

2695 'VDIUnavailable', 

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

2697 ) 

2698 

2699 @classmethod 

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

2701 try: 

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

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

2704 return 

2705 

2706 pid = None 

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

2708 try: 

2709 pid = int(pid_file.read()) 

2710 except Exception: 

2711 pass 

2712 

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

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

2715 try: 

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

2717 except Exception as e: 

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

2719 

2720 os.remove(path) 

2721 except: 

2722 pass 

2723 

2724 @classmethod 

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

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

2727 

2728 @classmethod 

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

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

2731 

2732 def _check_http_nbd_volume_name(self): 

2733 volume_name = self.path[14:] 

2734 if volume_name not in [ 

2735 HA_VOLUME_NAME, REDO_LOG_VOLUME_NAME 

2736 ]: 

2737 raise xs_errors.XenError( 

2738 'VDIUnavailable', 

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

2740 ) 

2741 return volume_name 

2742 

2743 def _attach_using_http_nbd(self): 

2744 volume_name = self._check_http_nbd_volume_name() 

2745 

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

2747 self._kill_persistent_nbd_server(volume_name) 

2748 self._kill_persistent_http_server(volume_name) 

2749 

2750 # 0. Fetch drbd path. 

2751 must_get_device_path = True 

2752 if not self.sr.is_master(): 

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

2754 try: 

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

2756 except Exception as e: 

2757 raise xs_errors.XenError( 

2758 'VDIUnavailable', 

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

2760 .format(self.uuid, e) 

2761 ) 

2762 

2763 hostname = socket.gethostname() 

2764 must_get_device_path = hostname in volume_info.diskful 

2765 

2766 drbd_path = None 

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

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

2769 # or diskful available to init HA. 

2770 # It also avoid this error in xensource.log 

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

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

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

2774 available = False 

2775 try: 

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

2777 available = util.pathexists(drbd_path) 

2778 except Exception: 

2779 pass 

2780 

2781 if not available: 

2782 raise xs_errors.XenError( 

2783 'VDIUnavailable', 

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

2785 ) 

2786 

2787 # 1. Prepare http-nbd folder. 

2788 try: 

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

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

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

2792 os.remove(self.path) 

2793 except OSError as e: 

2794 if e.errno != errno.EEXIST: 

2795 raise xs_errors.XenError( 

2796 'VDIUnavailable', 

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

2798 ) 

2799 

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

2801 http_service = None 

2802 if drbd_path: 

2803 assert(drbd_path in ( 

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

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

2806 )) 

2807 self._start_persistent_http_server(volume_name) 

2808 

2809 # 3. Start NBD server in all cases. 

2810 try: 

2811 self._start_persistent_nbd_server(volume_name) 

2812 except Exception as e: 

2813 if drbd_path: 

2814 self._kill_persistent_http_server(volume_name) 

2815 raise 

2816 

2817 self.attached = True 

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

2819 

2820 def _detach_using_http_nbd(self): 

2821 volume_name = self._check_http_nbd_volume_name() 

2822 self._kill_persistent_nbd_server(volume_name) 

2823 self._kill_persistent_http_server(volume_name) 

2824 

2825# ------------------------------------------------------------------------------ 

2826 

2827 

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

2829 def run(): 

2830 SRCommand.run(LinstorSR, DRIVER_INFO) 

2831 

2832 if not TRACE_PERFS: 

2833 run() 

2834 else: 

2835 util.make_profile('LinstorSR', run) 

2836else: 

2837 SR.registerSR(LinstorSR)