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._is_master = False 

366 if 'SRmaster' in self.dconf and self.dconf['SRmaster'] == 'true': 

367 self._is_master = True 

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

369 

370 self._vdi_shared_time = 0 

371 

372 self._init_status = self.INIT_STATUS_NOT_SET 

373 

374 self._vdis_loaded = False 

375 self._all_volume_info_cache = None 

376 self._all_volume_metadata_cache = None 

377 

378 def _locked_load(method): 

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

380 self._init_status = self.INIT_STATUS_OK 

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

382 

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

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

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

386 activate_lvm_group(self._group_name) 

387 

388 if not self._has_session: 

389 if self.srcmd.cmd in ( 

390 'vdi_attach_from_config', 

391 'vdi_detach_from_config', 

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

393 # empty command. 

394 None 

395 ): 

396 def create_linstor(uri, attempt_count=30): 

397 self._linstor = LinstorVolumeManager( 

398 uri, 

399 self._group_name, 

400 logger=util.SMlog, 

401 attempt_count=attempt_count 

402 ) 

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

404 # I.e. not an HA volume. 

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

406 

407 controller_uri = get_controller_uri() 

408 if controller_uri: 

409 create_linstor(controller_uri) 

410 else: 

411 def connect(): 

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

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

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

415 controller_uri = 'linstor://' + ip 

416 try: 

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

418 create_linstor(controller_uri, attempt_count=0) 

419 return controller_uri 

420 except: 

421 pass 

422 

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

424 if not controller_uri: 

425 raise xs_errors.XenError( 

426 'SRUnavailable', 

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

428 ) 

429 

430 self._journaler = LinstorJournaler( 

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

432 ) 

433 

434 if self.srcmd.cmd is None: 

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

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

437 

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

439 

440 if not self._is_master: 

441 if self.cmd in [ 

442 'sr_create', 'sr_delete', 'sr_update', 'sr_probe', 

443 'sr_scan', 'vdi_create', 'vdi_delete', 'vdi_resize', 

444 'vdi_snapshot', 'vdi_clone' 

445 ]: 

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

447 raise xs_errors.XenError('LinstorMaster') 

448 

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

450 # the VDI before the LinstorJournaler/LinstorVolumeManager 

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

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

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

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

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

456 self._vdi_shared_time = time.time() 

457 

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

459 try: 

460 self._reconnect() 

461 except Exception as e: 

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

463 

464 if self._linstor: 

465 try: 

466 hosts = self._linstor.disconnected_hosts 

467 except Exception as e: 

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

469 

470 if hosts: 

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

472 

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

474 if ( 

475 self._is_master and self.cmd.startswith('vdi_') and 

476 self.cmd != 'vdi_create' 

477 ): 

478 self._linstor.ensure_volume_is_not_locked( 

479 self.srcmd.params['vdi_uuid'] 

480 ) 

481 

482 try: 

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

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

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

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

487 # 

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

489 # resourceless volumes. 

490 if self._is_master and self.cmd not in [ 

491 'vdi_attach', 'vdi_detach', 

492 'vdi_activate', 'vdi_deactivate', 

493 'vdi_epoch_begin', 'vdi_epoch_end', 

494 'vdi_update', 'vdi_destroy' 

495 ]: 

496 load_vdis = ( 

497 self.cmd == 'sr_scan' or 

498 self.cmd == 'sr_attach' 

499 ) or len( 

500 self._journaler.get_all(LinstorJournaler.INFLATE) 

501 ) or len( 

502 self._journaler.get_all(LinstorJournaler.CLONE) 

503 ) 

504 

505 if load_vdis: 

506 self._load_vdis() 

507 

508 self._linstor.remove_resourceless_volumes() 

509 

510 self._synchronize_metadata() 

511 except Exception as e: 

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

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

514 # from the XAPI database otherwise. 

515 raise e 

516 util.SMlog( 

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

518 ) 

519 util.SMlog(traceback.format_exc()) 

520 

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

522 

523 @functools.wraps(wrapped_method) 

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

525 if self._init_status in \ 

526 (self.INIT_STATUS_OK, self.INIT_STATUS_IN_PROGRESS): 

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

528 if self._init_status == self.INIT_STATUS_FAIL: 

529 util.SMlog( 

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

531 .format(method) 

532 ) 

533 else: 

534 try: 

535 self._init_status = self.INIT_STATUS_IN_PROGRESS 

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

537 except Exception: 

538 if self._init_status != self.INIT_STATUS_OK: 

539 self._init_status = self.INIT_STATUS_FAIL 

540 raise 

541 

542 return wrap 

543 

544 def cleanup(self): 

545 if self._vdi_shared_time: 

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

547 

548 @_locked_load 

549 def create(self, uuid, size): 

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

551 

552 host_adresses = util.get_host_addresses(self.session) 

553 if self._redundancy > len(host_adresses): 

554 raise xs_errors.XenError( 

555 'LinstorSRCreate', 

556 opterr='Redundancy greater than host count' 

557 ) 

558 

559 xenapi = self.session.xenapi 

560 srs = xenapi.SR.get_all_records_where( 

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

562 ) 

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

564 

565 for sr in srs.values(): 

566 for pbd in sr['PBDs']: 

567 device_config = xenapi.PBD.get_device_config(pbd) 

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

569 if group_name and group_name == self._group_name: 

570 raise xs_errors.XenError( 

571 'LinstorSRCreate', 

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

573 xenapi.PBD.get_uuid(pbd) 

574 ) 

575 ) 

576 

577 if srs: 

578 raise xs_errors.XenError( 

579 'LinstorSRCreate', 

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

581 ) 

582 

583 online_hosts = util.get_online_hosts(self.session) 

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

585 raise xs_errors.XenError( 

586 'LinstorSRCreate', 

587 opterr='Not enough online hosts' 

588 ) 

589 

590 ips = {} 

591 for host_ref in online_hosts: 

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

593 hostname = record['hostname'] 

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

595 

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

597 raise xs_errors.XenError( 

598 'LinstorSRCreate', 

599 opterr='Multiple hosts with same hostname' 

600 ) 

601 

602 # Ensure ports are opened and LINSTOR satellites 

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

604 # must be stopped. 

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

606 

607 # Create SR. 

608 # Throw if the SR already exists. 

609 try: 

610 self._linstor = LinstorVolumeManager.create_sr( 

611 self._group_name, 

612 ips, 

613 self._redundancy, 

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

615 auto_quorum=self._monitor_db_quorum, 

616 logger=util.SMlog 

617 ) 

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

619 except Exception as e: 

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

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

622 

623 try: 

624 util.SMlog( 

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

626 ) 

627 self._update_drbd_reactor_on_all_hosts(enabled=True) 

628 except Exception as e: 

629 try: 

630 self._linstor.destroy() 

631 except Exception as e2: 

632 util.SMlog( 

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

634 .format(e2) 

635 ) 

636 raise e 

637 

638 @_locked_load 

639 def delete(self, uuid): 

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

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

642 

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

644 raise xs_errors.XenError('SRNotEmpty') 

645 

646 node_name = get_controller_node_name() 

647 if not node_name: 

648 raise xs_errors.XenError( 

649 'LinstorSRDelete', 

650 opterr='Cannot get controller node name' 

651 ) 

652 

653 host = None 

654 if node_name == 'localhost': 

655 host = util.get_this_host_ref(self.session) 

656 else: 

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

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

659 if r_name == node_name: 

660 host = slave 

661 break 

662 

663 if not host: 

664 raise xs_errors.XenError( 

665 'LinstorSRDelete', 

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

667 node_name 

668 ) 

669 ) 

670 

671 try: 

672 self._update_drbd_reactor_on_all_hosts( 

673 controller_node_name=node_name, enabled=False 

674 ) 

675 

676 args = { 

677 'groupName': self._group_name, 

678 } 

679 self._exec_manager_command( 

680 host, 'destroy', args, 'LinstorSRDelete' 

681 ) 

682 except Exception as e: 

683 try: 

684 self._update_drbd_reactor_on_all_hosts( 

685 controller_node_name=node_name, enabled=True 

686 ) 

687 except Exception as e2: 

688 util.SMlog( 

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

690 .format(e2) 

691 ) 

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

693 raise xs_errors.XenError( 

694 'LinstorSRDelete', 

695 opterr=str(e) 

696 ) 

697 

698 Lock.cleanupAll(self.uuid) 

699 

700 @_locked_load 

701 def update(self, uuid): 

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

703 

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

705 if not self._linstor: 

706 raise xs_errors.XenError( 

707 'SRUnavailable', 

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

709 ) 

710 

711 self._update_stats(0) 

712 

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

714 xenapi = self.session.xenapi 

715 self._linstor.metadata = { 

716 NAME_LABEL_TAG: util.to_plain_string( 

717 xenapi.SR.get_name_label(self.sr_ref) 

718 ), 

719 NAME_DESCRIPTION_TAG: util.to_plain_string( 

720 xenapi.SR.get_name_description(self.sr_ref) 

721 ) 

722 } 

723 

724 @_locked_load 

725 def attach(self, uuid): 

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

727 

728 if not self._linstor: 

729 raise xs_errors.XenError( 

730 'SRUnavailable', 

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

732 ) 

733 

734 @_locked_load 

735 def detach(self, uuid): 

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

737 cleanup.abort(self.uuid) 

738 

739 @_locked_load 

740 def probe(self): 

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

742 # TODO 

743 

744 @_locked_load 

745 def scan(self, uuid): 

746 if self._init_status == self.INIT_STATUS_FAIL: 

747 return 

748 

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

750 if not self._linstor: 

751 raise xs_errors.XenError( 

752 'SRUnavailable', 

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

754 ) 

755 

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

757 # are loaded. 

758 self._load_vdis() 

759 self._update_physical_size() 

760 

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

762 if self.vdis[vdi_uuid].deleted: 

763 del self.vdis[vdi_uuid] 

764 

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

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

767 try: 

768 self._linstor.get_database_path() 

769 except Exception: 

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

771 # VDIs in the XAPI database... 

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

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

774 ): 

775 raise xs_errors.XenError( 

776 'SRUnavailable', 

777 opterr='Database is not mounted' 

778 ) 

779 

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

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

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

783 self._kick_gc() 

784 

785 @_locked_load 

786 def vdi(self, uuid): 

787 return LinstorVDI(self, uuid) 

788 

789 _locked_load = staticmethod(_locked_load) 

790 

791 # -------------------------------------------------------------------------- 

792 # Lock. 

793 # -------------------------------------------------------------------------- 

794 

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

796 master = util.get_master_ref(self.session) 

797 

798 command = 'lockVdi' 

799 args = { 

800 'groupName': self._group_name, 

801 'srUuid': self.uuid, 

802 'vdiUuid': vdi_uuid, 

803 'locked': str(locked) 

804 } 

805 

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

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

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

809 # 

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

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

812 # to implement without impacting performance. 

813 if not locked: 

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

815 timeout = LinstorVolumeManager.LOCKED_EXPIRATION_DELAY * 0.7 

816 if elapsed_time >= timeout: 

817 util.SMlog( 

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

819 .format(vdi_uuid) 

820 ) 

821 return 

822 

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

824 

825 # -------------------------------------------------------------------------- 

826 # Network. 

827 # -------------------------------------------------------------------------- 

828 

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

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

831 host_uuid = host_rec['uuid'] 

832 

833 try: 

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

835 host_ref, self.MANAGER_PLUGIN, command, args 

836 ) 

837 except Exception as e: 

838 util.SMlog( 

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

840 host_uuid, self.MANAGER_PLUGIN, command, args 

841 ) 

842 ) 

843 raise e 

844 

845 util.SMlog( 

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

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

848 ) 

849 ) 

850 if ret == 'False': 

851 raise xs_errors.XenError( 

852 error, 

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

854 ) 

855 

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

857 self._exec_manager_command( 

858 host, 

859 'prepareSr' if enabled else 'releaseSr', 

860 {'groupName': group_name}, 

861 'SRUnavailable' 

862 ) 

863 

864 def _prepare_sr_on_all_hosts(self, group_name, enabled): 

865 master = util.get_master_ref(self.session) 

866 self._prepare_sr(master, group_name, enabled) 

867 

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

869 self._prepare_sr(slave, group_name, enabled) 

870 

871 def _update_drbd_reactor(self, host, enabled): 

872 self._exec_manager_command( 

873 host, 

874 'updateDrbdReactor', 

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

876 'SRUnavailable' 

877 ) 

878 

879 def _update_drbd_reactor_on_all_hosts( 

880 self, enabled, controller_node_name=None 

881 ): 

882 if controller_node_name == 'localhost': 

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

884 util.get_this_host_ref(self.session) 

885 )['hostname'] 

886 assert controller_node_name 

887 assert controller_node_name != 'localhost' 

888 

889 controller_host = None 

890 secondary_hosts = [] 

891 

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

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

894 hostname = host_rec['hostname'] 

895 if controller_node_name == hostname: 

896 controller_host = host_ref 

897 else: 

898 secondary_hosts.append((host_ref, hostname)) 

899 

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

901 if controller_node_name and not controller_host: 

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

903 controller_node_name 

904 )) 

905 

906 if enabled and controller_host: 

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

908 action_name, controller_node_name 

909 )) 

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

911 # node name first. 

912 self._update_drbd_reactor(controller_host, enabled) 

913 

914 for host_ref, hostname in secondary_hosts: 

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

916 action_name, hostname 

917 )) 

918 self._update_drbd_reactor(host_ref, enabled) 

919 

920 if not enabled and controller_host: 

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

922 action_name, controller_node_name 

923 )) 

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

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

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

927 self._update_drbd_reactor(controller_host, enabled) 

928 

929 # -------------------------------------------------------------------------- 

930 # Metadata. 

931 # -------------------------------------------------------------------------- 

932 

933 def _synchronize_metadata_and_xapi(self): 

934 try: 

935 # First synch SR parameters. 

936 self.update(self.uuid) 

937 

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

939 xenapi = self.session.xenapi 

940 volumes_metadata = self._linstor.get_volumes_with_metadata() 

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

942 try: 

943 vdi_ref = xenapi.VDI.get_by_uuid(vdi_uuid) 

944 except Exception: 

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

946 continue 

947 

948 label = util.to_plain_string( 

949 xenapi.VDI.get_name_label(vdi_ref) 

950 ) 

951 description = util.to_plain_string( 

952 xenapi.VDI.get_name_description(vdi_ref) 

953 ) 

954 

955 if ( 

956 volume_metadata.get(NAME_LABEL_TAG) != label or 

957 volume_metadata.get(NAME_DESCRIPTION_TAG) != description 

958 ): 

959 self._linstor.update_volume_metadata(vdi_uuid, { 

960 NAME_LABEL_TAG: label, 

961 NAME_DESCRIPTION_TAG: description 

962 }) 

963 except Exception as e: 

964 raise xs_errors.XenError( 

965 'MetadataError', 

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

967 ) 

968 

969 def _synchronize_metadata(self): 

970 if not self._is_master: 

971 return 

972 

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

974 if self.cmd == 'sr_attach': 

975 try: 

976 util.SMlog( 

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

978 ) 

979 self._synchronize_metadata_and_xapi() 

980 except Exception as e: 

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

982 

983 # -------------------------------------------------------------------------- 

984 # Stats. 

985 # -------------------------------------------------------------------------- 

986 

987 def _update_stats(self, virt_alloc_delta): 

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

989 self.sr_ref 

990 )) 

991 

992 # Update size attributes of the SR parent class. 

993 self.virtual_allocation = valloc + virt_alloc_delta 

994 

995 self._update_physical_size() 

996 

997 # Notify SR parent class. 

998 self._db_update() 

999 

1000 def _update_physical_size(self): 

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

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

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

1004 self.physical_size = min_physical_size * pool_count // \ 

1005 self._linstor.redundancy 

1006 

1007 self.physical_utilisation = self._linstor.allocated_volume_size 

1008 

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

1010 # VDIs. 

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

1012 

1013 def _load_vdis(self): 

1014 if self._vdis_loaded: 

1015 return 

1016 

1017 assert self._is_master 

1018 

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

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

1021 # enjoy it with a few lines. 

1022 self._create_linstor_cache() 

1023 self._load_vdis_ex() 

1024 self._destroy_linstor_cache() 

1025 

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

1027 self._vdis_loaded = True 

1028 

1029 self._undo_all_journal_transactions() 

1030 

1031 def _load_vdis_ex(self): 

1032 # 1. Get existing VDIs in XAPI. 

1033 xenapi = self.session.xenapi 

1034 xapi_vdi_uuids = set() 

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

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

1037 

1038 # 2. Get volumes info. 

1039 all_volume_info = self._all_volume_info_cache 

1040 volumes_metadata = self._all_volume_metadata_cache 

1041 

1042 # 3. Get CBT vdis. 

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

1044 cbt_vdis = set() 

1045 for volume_metadata in volumes_metadata.values(): 

1046 cbt_uuid = volume_metadata.get(CBTLOG_TAG) 

1047 if cbt_uuid: 

1048 cbt_vdis.add(cbt_uuid) 

1049 

1050 introduce = False 

1051 

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

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

1054 has_clone_entries = list(self._journaler.get_all( 

1055 LinstorJournaler.CLONE 

1056 ).items()) 

1057 

1058 if has_clone_entries: 

1059 util.SMlog( 

1060 'Cannot introduce VDIs during scan because it exists ' 

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

1062 ) 

1063 else: 

1064 introduce = True 

1065 

1066 # 4. Now check all volume info. 

1067 vdi_to_snaps = {} 

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

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

1070 continue 

1071 

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

1073 if vdi_uuid not in xapi_vdi_uuids: 

1074 if not introduce: 

1075 continue 

1076 

1077 if vdi_uuid.startswith('DELETED_'): 

1078 continue 

1079 

1080 volume_metadata = volumes_metadata.get(vdi_uuid) 

1081 if not volume_metadata: 

1082 util.SMlog( 

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

1084 .format(vdi_uuid) 

1085 ) 

1086 continue 

1087 

1088 util.SMlog( 

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

1090 'LINSTOR and not in XAPI...' 

1091 .format(vdi_uuid) 

1092 ) 

1093 

1094 try: 

1095 self._linstor.get_device_path(vdi_uuid) 

1096 except Exception as e: 

1097 util.SMlog( 

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

1099 .format(vdi_uuid, e) 

1100 ) 

1101 continue 

1102 

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

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

1105 vdi_type = volume_metadata.get(VDI_TYPE_TAG) 

1106 

1107 if not vdi_type: 

1108 util.SMlog( 

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

1110 'without vdi_type' 

1111 ) 

1112 continue 

1113 

1114 sm_config = { 

1115 'vdi_type': vdi_type 

1116 } 

1117 

1118 if vdi_type == vhdutil.VDI_TYPE_RAW: 

1119 managed = not volume_metadata.get(HIDDEN_TAG) 

1120 elif vdi_type == vhdutil.VDI_TYPE_VHD: 

1121 vhd_info = self._vhdutil.get_vhd_info(vdi_uuid) 

1122 managed = not vhd_info.hidden 

1123 if vhd_info.parentUuid: 

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

1125 else: 

1126 util.SMlog( 

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

1128 .format(vdi_uuid, vdi_type) 

1129 ) 

1130 continue 

1131 

1132 util.SMlog( 

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

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

1135 name_label, 

1136 volume_info.virtual_size, 

1137 volume_info.allocated_size 

1138 ) 

1139 ) 

1140 

1141 vdi_ref = xenapi.VDI.db_introduce( 

1142 vdi_uuid, 

1143 name_label, 

1144 volume_metadata.get(NAME_DESCRIPTION_TAG) or '', 

1145 self.sr_ref, 

1146 type, 

1147 False, # sharable 

1148 bool(volume_metadata.get(READ_ONLY_TAG)), 

1149 {}, # other_config 

1150 vdi_uuid, # location 

1151 {}, # xenstore_data 

1152 sm_config, 

1153 managed, 

1154 str(volume_info.virtual_size), 

1155 str(volume_info.allocated_size) 

1156 ) 

1157 

1158 is_a_snapshot = volume_metadata.get(IS_A_SNAPSHOT_TAG) 

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

1160 if is_a_snapshot: 

1161 xenapi.VDI.set_snapshot_time( 

1162 vdi_ref, 

1163 xmlrpc.client.DateTime( 

1164 volume_metadata[SNAPSHOT_TIME_TAG] or 

1165 '19700101T00:00:00Z' 

1166 ) 

1167 ) 

1168 

1169 snap_uuid = volume_metadata[SNAPSHOT_OF_TAG] 

1170 if snap_uuid in vdi_to_snaps: 

1171 vdi_to_snaps[snap_uuid].append(vdi_uuid) 

1172 else: 

1173 vdi_to_snaps[snap_uuid] = [vdi_uuid] 

1174 

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

1176 vdi = self.vdi(vdi_uuid) 

1177 self.vdis[vdi_uuid] = vdi 

1178 

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

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

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

1182 vdi.sm_config_override['key_hash'] = \ 

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

1184 

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

1186 # or already in XAPI. 

1187 cbt_uuid = volume_metadata.get(CBTLOG_TAG) 

1188 if cbt_uuid in cbt_vdis: 

1189 vdi_ref = xenapi.VDI.get_by_uuid(vdi_uuid) 

1190 xenapi.VDI.set_cbt_enabled(vdi_ref, True) 

1191 # For existing VDIs, update local state too. 

1192 # Scan in base class SR updates existing VDIs 

1193 # again based on local states. 

1194 self.vdis[vdi_uuid].cbt_enabled = True 

1195 cbt_vdis.remove(cbt_uuid) 

1196 

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

1198 for src_uuid in vdi_to_snaps: 

1199 try: 

1200 src_ref = xenapi.VDI.get_by_uuid(src_uuid) 

1201 except Exception: 

1202 # The source VDI no longer exists, continue. 

1203 continue 

1204 

1205 for snap_uuid in vdi_to_snaps[src_uuid]: 

1206 try: 

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

1208 snap_ref = xenapi.VDI.get_by_uuid(snap_uuid) 

1209 xenapi.VDI.set_snapshot_of(snap_ref, src_ref) 

1210 except Exception as e: 

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

1212 

1213 # TODO: Check correctly how to use CBT. 

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

1215 

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

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

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

1219 for cbt_uuid in cbt_vdis: 

1220 new_vdi = self.vdi(cbt_uuid) 

1221 new_vdi.ty = 'cbt_metadata' 

1222 new_vdi.cbt_enabled = True 

1223 self.vdis[cbt_uuid] = new_vdi 

1224 

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

1226 self.virtual_allocation = 0 

1227 

1228 # 8. Build geneology. 

1229 geneology = {} 

1230 

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

1232 if vdi.parent: 

1233 if vdi.parent in self.vdis: 

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

1235 if vdi.parent in geneology: 

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

1237 else: 

1238 geneology[vdi.parent] = [vdi_uuid] 

1239 if not vdi.hidden: 

1240 self.virtual_allocation += vdi.size 

1241 

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

1243 # will be GC'ed. 

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

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

1246 util.SMlog( 

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

1248 ) 

1249 del self.vdis[vdi_uuid] 

1250 

1251 # -------------------------------------------------------------------------- 

1252 # Journals. 

1253 # -------------------------------------------------------------------------- 

1254 

1255 def _get_vdi_path_and_parent(self, vdi_uuid, volume_name): 

1256 try: 

1257 device_path = self._linstor.build_device_path(volume_name) 

1258 if not util.pathexists(device_path): 

1259 return (None, None) 

1260 

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

1262 volume_metadata = self._linstor.get_volume_metadata(vdi_uuid) 

1263 vdi_type = volume_metadata[VDI_TYPE_TAG] 

1264 if vdi_type == vhdutil.VDI_TYPE_RAW: 

1265 return (device_path, None) 

1266 

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

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

1269 return (None, None) 

1270 

1271 vhd_info = self._vhdutil.get_vhd_info(vdi_uuid) 

1272 if vhd_info: 

1273 return (device_path, vhd_info.parentUuid) 

1274 except Exception as e: 

1275 util.SMlog( 

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

1277 .format(e) 

1278 ) 

1279 return (None, None) 

1280 

1281 def _undo_all_journal_transactions(self): 

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

1283 self.lock.acquire() 

1284 try: 

1285 self._handle_interrupted_inflate_ops() 

1286 self._handle_interrupted_clone_ops() 

1287 pass 

1288 finally: 

1289 self.lock.release() 

1290 

1291 def _handle_interrupted_inflate_ops(self): 

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

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

1294 self._handle_interrupted_inflate(vdi_uuid, old_size) 

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

1296 

1297 def _handle_interrupted_clone_ops(self): 

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

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

1300 self._handle_interrupted_clone(vdi_uuid, old_size) 

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

1302 

1303 def _handle_interrupted_inflate(self, vdi_uuid, old_size): 

1304 util.SMlog( 

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

1306 .format(vdi_uuid, old_size) 

1307 ) 

1308 

1309 vdi = self.vdis.get(vdi_uuid) 

1310 if not vdi: 

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

1312 return 

1313 

1314 assert not self._all_volume_info_cache 

1315 volume_info = self._linstor.get_volume_info(vdi_uuid) 

1316 

1317 current_size = volume_info.virtual_size 

1318 assert current_size > 0 

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

1320 

1321 def _handle_interrupted_clone( 

1322 self, vdi_uuid, clone_info, force_undo=False 

1323 ): 

1324 util.SMlog( 

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

1326 .format(vdi_uuid, clone_info) 

1327 ) 

1328 

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

1330 

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

1332 volume_names = self._linstor.get_volumes_with_name() 

1333 

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

1335 if base_uuid not in volume_names: 

1336 if vdi_uuid in volume_names: 

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

1338 return 

1339 raise util.SMException( 

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

1341 .format(base_uuid, vdi_uuid) 

1342 ) 

1343 

1344 if force_undo: 

1345 util.SMlog('Explicit revert') 

1346 self._undo_clone( 

1347 volume_names, vdi_uuid, base_uuid, snap_uuid 

1348 ) 

1349 return 

1350 

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

1352 if vdi_uuid not in volume_names or \ 

1353 (snap_uuid and snap_uuid not in volume_names): 

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

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

1356 return 

1357 

1358 vdi_path, vdi_parent_uuid = self._get_vdi_path_and_parent( 

1359 vdi_uuid, volume_names[vdi_uuid] 

1360 ) 

1361 snap_path, snap_parent_uuid = self._get_vdi_path_and_parent( 

1362 snap_uuid, volume_names[snap_uuid] 

1363 ) 

1364 

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

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

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

1368 return 

1369 

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

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

1372 

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

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

1375 base_metadata = self._linstor.get_volume_metadata(base_uuid) 

1376 base_type = base_metadata[VDI_TYPE_TAG] 

1377 

1378 if not util.pathexists(base_path): 

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

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

1381 return 

1382 

1383 # Un-hide the parent. 

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

1385 if base_type == vhdutil.VDI_TYPE_VHD: 

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

1387 if vhd_info.hidden: 

1388 self._vhdutil.set_hidden(base_path, False) 

1389 elif base_type == vhdutil.VDI_TYPE_RAW and \ 

1390 base_metadata.get(HIDDEN_TAG): 

1391 self._linstor.update_volume_metadata( 

1392 base_uuid, {HIDDEN_TAG: False} 

1393 ) 

1394 

1395 # Remove the child nodes. 

1396 if snap_uuid and snap_uuid in volume_names: 

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

1398 

1399 try: 

1400 self._linstor.destroy_volume(snap_uuid) 

1401 except Exception as e: 

1402 util.SMlog( 

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

1404 .format(snap_uuid, e) 

1405 ) 

1406 

1407 if vdi_uuid in volume_names: 

1408 try: 

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

1410 self._linstor.destroy_volume(vdi_uuid) 

1411 except Exception as e: 

1412 util.SMlog( 

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

1414 .format(vdi_uuid, e) 

1415 ) 

1416 # We can get an exception like this: 

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

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

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

1420 # UUID of this bad VDI before. 

1421 self._linstor.update_volume_uuid( 

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

1423 ) 

1424 

1425 # Rename! 

1426 self._linstor.update_volume_uuid(base_uuid, vdi_uuid) 

1427 

1428 # Inflate to the right size. 

1429 if base_type == vhdutil.VDI_TYPE_VHD: 

1430 vdi = self.vdi(vdi_uuid) 

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

1432 self._vhdutil.inflate( 

1433 self._journaler, vdi_uuid, vdi.path, 

1434 volume_size, vdi.capacity 

1435 ) 

1436 self.vdis[vdi_uuid] = vdi 

1437 

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

1439 # flag to facilitate vm deactivate. 

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

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

1442 

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

1444 

1445 # -------------------------------------------------------------------------- 

1446 # Cache. 

1447 # -------------------------------------------------------------------------- 

1448 

1449 def _create_linstor_cache(self): 

1450 reconnect = False 

1451 

1452 def create_cache(): 

1453 nonlocal reconnect 

1454 try: 

1455 if reconnect: 

1456 self._reconnect() 

1457 return self._linstor.get_volumes_with_info() 

1458 except Exception as e: 

1459 reconnect = True 

1460 raise e 

1461 

1462 self._all_volume_metadata_cache = \ 

1463 self._linstor.get_volumes_with_metadata() 

1464 self._all_volume_info_cache = util.retry( 

1465 create_cache, 

1466 maxretry=10, 

1467 period=3 

1468 ) 

1469 

1470 def _destroy_linstor_cache(self): 

1471 self._all_volume_info_cache = None 

1472 self._all_volume_metadata_cache = None 

1473 

1474 # -------------------------------------------------------------------------- 

1475 # Misc. 

1476 # -------------------------------------------------------------------------- 

1477 

1478 def _reconnect(self): 

1479 controller_uri = get_controller_uri() 

1480 

1481 self._journaler = LinstorJournaler( 

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

1483 ) 

1484 

1485 # Try to open SR if exists. 

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

1487 # we are trying to execute an exclusive operation. 

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

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

1490 # the SR is locked. 

1491 self._linstor = LinstorVolumeManager( 

1492 controller_uri, 

1493 self._group_name, 

1494 repair=( 

1495 self._is_master and 

1496 self.srcmd.cmd in self.ops_exclusive 

1497 ), 

1498 logger=util.SMlog 

1499 ) 

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

1501 

1502 def _ensure_space_available(self, amount_needed): 

1503 space_available = self._linstor.max_volume_size_allowed 

1504 if (space_available < amount_needed): 

1505 util.SMlog( 

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

1507 space_available, amount_needed 

1508 ) 

1509 ) 

1510 raise xs_errors.XenError('SRNoSpace') 

1511 

1512 def _kick_gc(self): 

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

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

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

1516 lock = Lock(LOCK_TYPE_GC_RUNNING, self.uuid) 

1517 if not lock.acquireNoblock(): 

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

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

1520 return 

1521 

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

1523 try: 

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

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

1526 except util.CommandException as e: 

1527 if e.code != errno.ETIMEDOUT: 

1528 raise 

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

1530 else: 

1531 lock.release() 

1532 

1533 util.SMlog('Kicking GC') 

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

1535 

1536# ============================================================================== 

1537# LinstorSr VDI 

1538# ============================================================================== 

1539 

1540 

1541class LinstorVDI(VDI.VDI): 

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

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

1544 TYPE_RAW = 'raw' 

1545 TYPE_VHD = 'vhd' 

1546 

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

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

1549 # Increase the performance when resize is called. 

1550 MAX_METADATA_VIRT_SIZE = 2 * 1024 * 1024 

1551 

1552 # -------------------------------------------------------------------------- 

1553 # VDI methods. 

1554 # -------------------------------------------------------------------------- 

1555 

1556 def load(self, vdi_uuid): 

1557 self._lock = self.sr.lock 

1558 self._exists = True 

1559 self._linstor = self.sr._linstor 

1560 

1561 # Update hidden parent property. 

1562 self.hidden = False 

1563 

1564 def raise_bad_load(e): 

1565 util.SMlog( 

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

1567 ) 

1568 util.SMlog(traceback.format_exc()) 

1569 raise xs_errors.XenError( 

1570 'VDIUnavailable', 

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

1572 ) 

1573 

1574 # Try to load VDI. 

1575 try: 

1576 if ( 

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

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

1579 ): 

1580 self.vdi_type = vhdutil.VDI_TYPE_RAW 

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

1582 else: 

1583 self._determine_type_and_path() 

1584 self._load_this() 

1585 

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

1587 self.uuid, self.path, self.hidden 

1588 )) 

1589 except LinstorVolumeManagerError as e: 

1590 # 1. It may be a VDI deletion. 

1591 if e.code == LinstorVolumeManagerError.ERR_VOLUME_NOT_EXISTS: 

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

1593 self.deleted = True 

1594 return 

1595 

1596 # 2. Or maybe a creation. 

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

1598 # Set type attribute of VDI parent class. 

1599 # We use VHD by default. 

1600 self.vdi_type = vhdutil.VDI_TYPE_VHD 

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

1602 

1603 self._exists = False 

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

1605 if vdi_sm_config is not None: 

1606 type = vdi_sm_config.get('type') 

1607 if type is not None: 

1608 if type == self.TYPE_RAW: 

1609 self.vdi_type = vhdutil.VDI_TYPE_RAW 

1610 elif type == self.TYPE_VHD: 

1611 self.vdi_type = vhdutil.VDI_TYPE_VHD 

1612 else: 

1613 raise xs_errors.XenError( 

1614 'VDICreate', 

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

1616 ) 

1617 if self.vdi_type == vhdutil.VDI_TYPE_VHD: 

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

1619 

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

1621 self._update_device_name(None) 

1622 return 

1623 raise_bad_load(e) 

1624 except Exception as e: 

1625 raise_bad_load(e) 

1626 

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

1628 # Usage example: 

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

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

1631 

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

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

1634 if self._exists: 

1635 raise xs_errors.XenError('VDIExists') 

1636 

1637 assert self.uuid 

1638 assert self.ty 

1639 assert self.vdi_type 

1640 

1641 # 2. Compute size and check space available. 

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

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

1644 util.SMlog( 

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

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

1647 ) 

1648 self.sr._ensure_space_available(volume_size) 

1649 

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

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

1652 

1653 # 4. Create! 

1654 failed = False 

1655 try: 

1656 volume_name = None 

1657 if self.ty == 'ha_statefile': 

1658 volume_name = HA_VOLUME_NAME 

1659 elif self.ty == 'redo_log': 

1660 volume_name = REDO_LOG_VOLUME_NAME 

1661 

1662 self._linstor.create_volume( 

1663 self.uuid, volume_size, persistent=False, 

1664 volume_name=volume_name 

1665 ) 

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

1667 

1668 self._update_device_name(volume_info.name) 

1669 

1670 if self.vdi_type == vhdutil.VDI_TYPE_RAW: 

1671 self.size = volume_info.virtual_size 

1672 else: 

1673 self.sr._vhdutil.create( 

1674 self.path, size, False, self.MAX_METADATA_VIRT_SIZE 

1675 ) 

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

1677 

1678 if self._key_hash: 

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

1680 

1681 # Because vhdutil commands modify the volume data, 

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

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

1684 

1685 volume_metadata = { 

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

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

1688 IS_A_SNAPSHOT_TAG: False, 

1689 SNAPSHOT_OF_TAG: '', 

1690 SNAPSHOT_TIME_TAG: '', 

1691 TYPE_TAG: self.ty, 

1692 VDI_TYPE_TAG: self.vdi_type, 

1693 READ_ONLY_TAG: bool(self.read_only), 

1694 METADATA_OF_POOL_TAG: '' 

1695 } 

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

1697 

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

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

1700 # an already opened volume. 

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

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

1703 

1704 self._linstor.mark_volume_as_persistent(self.uuid) 

1705 except util.CommandException as e: 

1706 failed = True 

1707 raise xs_errors.XenError( 

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

1709 ) 

1710 except Exception as e: 

1711 failed = True 

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

1713 finally: 

1714 if failed: 

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

1716 try: 

1717 self._linstor.destroy_volume(self.uuid) 

1718 except Exception as e: 

1719 util.SMlog( 

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

1721 '{}'.format(e) 

1722 ) 

1723 

1724 self.utilisation = volume_info.allocated_size 

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

1726 

1727 self.ref = self._db_introduce() 

1728 self.sr._update_stats(self.size) 

1729 

1730 return VDI.VDI.get_params(self) 

1731 

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

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

1734 if self.attached: 

1735 raise xs_errors.XenError('VDIInUse') 

1736 

1737 if self.deleted: 

1738 return super(LinstorVDI, self).delete( 

1739 sr_uuid, vdi_uuid, data_only 

1740 ) 

1741 

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

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

1744 raise xs_errors.XenError( 

1745 'VDIDelete', 

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

1747 ) 

1748 

1749 try: 

1750 # Remove from XAPI and delete from LINSTOR. 

1751 self._linstor.destroy_volume(self.uuid) 

1752 if not data_only: 

1753 self._db_forget() 

1754 

1755 self.sr.lock.cleanupAll(vdi_uuid) 

1756 except Exception as e: 

1757 util.SMlog( 

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

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

1760 ) 

1761 

1762 try: 

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

1764 except LinstorVolumeManagerError as e: 

1765 if e.code != LinstorVolumeManagerError.ERR_VOLUME_DESTROY: 

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

1767 

1768 return 

1769 

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

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

1772 

1773 # TODO: Check size after delete. 

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

1775 self.sr._kick_gc() 

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

1777 

1778 def attach(self, sr_uuid, vdi_uuid): 

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

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

1781 if ( 

1782 not attach_from_config or 

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

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

1785 raise xs_errors.XenError( 

1786 'VDIUnavailable', 

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

1788 'scan SR first to trigger auto-repair' 

1789 ) 

1790 

1791 if not attach_from_config or self.sr._is_master: 

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

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

1794 

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

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

1797 # than the VHD size + bitmap size. 

1798 need_inflate = True 

1799 if ( 

1800 self.vdi_type == vhdutil.VDI_TYPE_RAW or 

1801 not writable or 

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

1803 ): 

1804 need_inflate = False 

1805 

1806 if need_inflate: 

1807 try: 

1808 self._prepare_thin(True) 

1809 except Exception as e: 

1810 raise xs_errors.XenError( 

1811 'VDIUnavailable', 

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

1813 .format(e) 

1814 ) 

1815 

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

1817 self.xenstore_data = {} 

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

1819 

1820 if ( 

1821 USE_HTTP_NBD_SERVERS and 

1822 attach_from_config and 

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

1824 ): 

1825 return self._attach_using_http_nbd() 

1826 

1827 # Ensure we have a path... 

1828 self._create_chain_paths(self.uuid) 

1829 

1830 self.attached = True 

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

1832 

1833 def detach(self, sr_uuid, vdi_uuid): 

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

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

1836 self.attached = False 

1837 

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

1839 return self._detach_using_http_nbd() 

1840 

1841 if self.vdi_type == vhdutil.VDI_TYPE_RAW: 

1842 return 

1843 

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

1845 # equal to the LINSTOR volume size. 

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

1847 already_deflated = self.capacity <= volume_size 

1848 

1849 if already_deflated: 

1850 util.SMlog( 

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

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

1853 ) 

1854 

1855 need_deflate = True 

1856 if already_deflated: 

1857 need_deflate = False 

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

1859 need_deflate = False 

1860 

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

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

1863 need_deflate = True 

1864 

1865 if need_deflate: 

1866 try: 

1867 self._prepare_thin(False) 

1868 except Exception as e: 

1869 raise xs_errors.XenError( 

1870 'VDIUnavailable', 

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

1872 .format(e) 

1873 ) 

1874 

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

1876 if self.sr._is_master: 

1877 return 

1878 

1879 while vdi_uuid: 

1880 try: 

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

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

1883 except Exception: 

1884 break 

1885 

1886 if util.pathexists(path): 

1887 try: 

1888 self._linstor.remove_volume_if_diskless(vdi_uuid) 

1889 except Exception as e: 

1890 # Ensure we can always detach properly. 

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

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

1893 vdi_uuid = parent_vdi_uuid 

1894 

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

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

1897 if not self.sr._is_master: 

1898 raise xs_errors.XenError( 

1899 'VDISize', 

1900 opterr='resize on slave not allowed' 

1901 ) 

1902 

1903 if self.hidden: 

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

1905 

1906 # Compute the virtual VHD and DRBD volume size. 

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

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

1909 util.SMlog( 

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

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

1912 ) 

1913 

1914 if size < self.size: 

1915 util.SMlog( 

1916 'vdi_resize: shrinking not supported: ' 

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

1918 ) 

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

1920 

1921 if size == self.size: 

1922 return VDI.VDI.get_params(self) 

1923 

1924 if self.vdi_type == vhdutil.VDI_TYPE_RAW: 

1925 old_volume_size = self.size 

1926 new_volume_size = LinstorVolumeManager.round_up_volume_size(size) 

1927 else: 

1928 old_volume_size = self.utilisation 

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

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

1931 new_volume_size = old_volume_size 

1932 else: 

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

1934 assert new_volume_size >= old_volume_size 

1935 

1936 space_needed = new_volume_size - old_volume_size 

1937 self.sr._ensure_space_available(space_needed) 

1938 

1939 old_size = self.size 

1940 if self.vdi_type == vhdutil.VDI_TYPE_RAW: 

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

1942 else: 

1943 if new_volume_size != old_volume_size: 

1944 self.sr._vhdutil.inflate( 

1945 self.sr._journaler, self.uuid, self.path, 

1946 new_volume_size, old_volume_size 

1947 ) 

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

1949 

1950 # Reload size attributes. 

1951 self._load_this() 

1952 

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

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

1955 self.session.xenapi.VDI.set_physical_utilisation( 

1956 vdi_ref, str(self.utilisation) 

1957 ) 

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

1959 return VDI.VDI.get_params(self) 

1960 

1961 def clone(self, sr_uuid, vdi_uuid): 

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

1963 

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

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

1966 if self.vdi_type != vhdutil.VDI_TYPE_VHD: 

1967 raise xs_errors.XenError('Unimplemented') 

1968 

1969 parent_uuid = vdi1 

1970 parent_path = self._linstor.get_device_path(parent_uuid) 

1971 

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

1973 # have a readonly error. 

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

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

1976 

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

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

1979 try: 

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

1981 self.sr._vhdutil.set_hidden(parent_path) 

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

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

1984 ) 

1985 finally: 

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

1987 

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

1989 raise util.SMException( 

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

1991 ) 

1992 

1993 util.SMlog('Compose done') 

1994 

1995 def generate_config(self, sr_uuid, vdi_uuid): 

1996 """ 

1997 Generate the XML config required to attach and activate 

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

1999 activation is handled by vdi_attach_from_config below. 

2000 """ 

2001 

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

2003 

2004 resp = {} 

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

2006 resp['sr_uuid'] = sr_uuid 

2007 resp['vdi_uuid'] = self.uuid 

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

2009 resp['command'] = 'vdi_attach_from_config' 

2010 

2011 # By default, we generate a normal config. 

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

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

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

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

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

2017 # instead. 

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

2019 if not USE_HTTP_NBD_SERVERS or volume_name not in [ 

2020 HA_VOLUME_NAME, REDO_LOG_VOLUME_NAME 

2021 ]: 

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

2023 available = False 

2024 # Try to refresh symlink path... 

2025 try: 

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

2027 available = util.pathexists(self.path) 

2028 except Exception: 

2029 pass 

2030 if not available: 

2031 raise xs_errors.XenError('VDIUnavailable') 

2032 

2033 resp['vdi_path'] = self.path 

2034 else: 

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

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

2037 

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

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

2040 

2041 def attach_from_config(self, sr_uuid, vdi_uuid): 

2042 """ 

2043 Attach and activate a VDI using config generated by 

2044 vdi_generate_config above. This is used for cases such as 

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

2046 """ 

2047 

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

2049 

2050 try: 

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

2052 self.sr.attach(sr_uuid) 

2053 

2054 if not DRIVER_CONFIG['ATTACH_FROM_CONFIG_WITH_TAPDISK']: 

2055 return self.attach(sr_uuid, vdi_uuid) 

2056 except Exception: 

2057 util.logException('LinstorVDI.attach_from_config') 

2058 raise xs_errors.XenError( 

2059 'SRUnavailable', 

2060 opterr='Unable to attach from config' 

2061 ) 

2062 

2063 def reset_leaf(self, sr_uuid, vdi_uuid): 

2064 if self.vdi_type != vhdutil.VDI_TYPE_VHD: 

2065 raise xs_errors.XenError('Unimplemented') 

2066 

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

2068 raise util.SMException( 

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

2070 .format(self.uuid) 

2071 ) 

2072 

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

2074 

2075 def _load_this(self): 

2076 volume_metadata = None 

2077 if self.sr._all_volume_metadata_cache: 

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

2079 if volume_metadata is None: 

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

2081 

2082 volume_info = None 

2083 if self.sr._all_volume_info_cache: 

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

2085 if volume_info is None: 

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

2087 

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

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

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

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

2092 # be lower than virtual size at creation. 

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

2094 self.utilisation = volume_info.allocated_size 

2095 self.capacity = volume_info.virtual_size 

2096 

2097 if self.vdi_type == vhdutil.VDI_TYPE_RAW: 

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

2099 self.size = volume_info.virtual_size 

2100 self.parent = '' 

2101 else: 

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

2103 self.hidden = vhd_info.hidden 

2104 self.size = vhd_info.sizeVirt 

2105 self.parent = vhd_info.parentUuid 

2106 

2107 if self.hidden: 

2108 self.managed = False 

2109 

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

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

2112 

2113 # Update sm_config_override of VDI parent class. 

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

2115 

2116 def _mark_hidden(self, hidden=True): 

2117 if self.hidden == hidden: 

2118 return 

2119 

2120 if self.vdi_type == vhdutil.VDI_TYPE_VHD: 

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

2122 else: 

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

2124 HIDDEN_TAG: hidden 

2125 }) 

2126 self.hidden = hidden 

2127 

2128 def update(self, sr_uuid, vdi_uuid): 

2129 xenapi = self.session.xenapi 

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

2131 

2132 volume_metadata = { 

2133 NAME_LABEL_TAG: util.to_plain_string( 

2134 xenapi.VDI.get_name_label(vdi_ref) 

2135 ), 

2136 NAME_DESCRIPTION_TAG: util.to_plain_string( 

2137 xenapi.VDI.get_name_description(vdi_ref) 

2138 ) 

2139 } 

2140 

2141 try: 

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

2143 except LinstorVolumeManagerError as e: 

2144 if e.code == LinstorVolumeManagerError.ERR_VOLUME_NOT_EXISTS: 

2145 raise xs_errors.XenError( 

2146 'VDIUnavailable', 

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

2148 ) 

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

2150 

2151 # -------------------------------------------------------------------------- 

2152 # Thin provisioning. 

2153 # -------------------------------------------------------------------------- 

2154 

2155 def _prepare_thin(self, attach): 

2156 if self.sr._is_master: 

2157 if attach: 

2158 attach_thin( 

2159 self.session, self.sr._journaler, self._linstor, 

2160 self.sr.uuid, self.uuid 

2161 ) 

2162 else: 

2163 detach_thin( 

2164 self.session, self._linstor, self.sr.uuid, self.uuid 

2165 ) 

2166 else: 

2167 fn = 'attach' if attach else 'detach' 

2168 

2169 master = util.get_master_ref(self.session) 

2170 

2171 args = { 

2172 'groupName': self.sr._group_name, 

2173 'srUuid': self.sr.uuid, 

2174 'vdiUuid': self.uuid 

2175 } 

2176 

2177 try: 

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

2179 except Exception: 

2180 if fn != 'detach': 

2181 raise 

2182 

2183 # Reload size attrs after inflate or deflate! 

2184 self._load_this() 

2185 self.sr._update_physical_size() 

2186 

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

2188 self.session.xenapi.VDI.set_physical_utilisation( 

2189 vdi_ref, str(self.utilisation) 

2190 ) 

2191 

2192 self.session.xenapi.SR.set_physical_utilisation( 

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

2194 ) 

2195 

2196 # -------------------------------------------------------------------------- 

2197 # Generic helpers. 

2198 # -------------------------------------------------------------------------- 

2199 

2200 def _determine_type_and_path(self): 

2201 """ 

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

2203 """ 

2204 

2205 # 1. Check vdi_ref and vdi_type in config. 

2206 try: 

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

2208 if vdi_ref: 

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

2210 vdi_type = sm_config.get('vdi_type') 

2211 if vdi_type: 

2212 # Update parent fields. 

2213 self.vdi_type = vdi_type 

2214 self.sm_config_override = sm_config 

2215 self._update_device_name( 

2216 self._linstor.get_volume_name(self.uuid) 

2217 ) 

2218 return 

2219 except Exception: 

2220 pass 

2221 

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

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

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

2225 self.vdi_type = volume_metadata.get(VDI_TYPE_TAG) 

2226 if not self.vdi_type: 

2227 raise xs_errors.XenError( 

2228 'VDIUnavailable', 

2229 opterr='failed to get vdi_type in metadata' 

2230 ) 

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

2232 

2233 def _update_device_name(self, device_name): 

2234 self._device_name = device_name 

2235 

2236 # Mark path of VDI parent class. 

2237 if device_name: 

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

2239 else: 

2240 self.path = None 

2241 

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

2243 """ 

2244 Snapshot self and return the snapshot VDI object. 

2245 """ 

2246 

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

2248 snap_path = self._linstor.shallow_clone_volume( 

2249 self.uuid, snap_uuid, persistent=False 

2250 ) 

2251 

2252 # 2. Write the snapshot content. 

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

2254 self.sr._vhdutil.snapshot( 

2255 snap_path, self.path, is_raw, self.MAX_METADATA_VIRT_SIZE 

2256 ) 

2257 

2258 # 3. Get snapshot parent. 

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

2260 

2261 # 4. Update metadata. 

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

2263 volume_metadata = { 

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

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

2266 IS_A_SNAPSHOT_TAG: bool(snap_of_uuid), 

2267 SNAPSHOT_OF_TAG: snap_of_uuid, 

2268 SNAPSHOT_TIME_TAG: '', 

2269 TYPE_TAG: self.ty, 

2270 VDI_TYPE_TAG: vhdutil.VDI_TYPE_VHD, 

2271 READ_ONLY_TAG: False, 

2272 METADATA_OF_POOL_TAG: '' 

2273 } 

2274 self._linstor.set_volume_metadata(snap_uuid, volume_metadata) 

2275 

2276 # 5. Set size. 

2277 snap_vdi = LinstorVDI(self.sr, snap_uuid) 

2278 if not snap_vdi._exists: 

2279 raise xs_errors.XenError('VDISnapshot') 

2280 

2281 volume_info = self._linstor.get_volume_info(snap_uuid) 

2282 

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

2284 snap_vdi.utilisation = volume_info.allocated_size 

2285 

2286 # 6. Update sm config. 

2287 snap_vdi.sm_config = {} 

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

2289 if snap_parent: 

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

2291 snap_vdi.parent = snap_parent 

2292 

2293 snap_vdi.label = self.label 

2294 snap_vdi.description = self.description 

2295 

2296 self._linstor.mark_volume_as_persistent(snap_uuid) 

2297 

2298 return snap_vdi 

2299 

2300 # -------------------------------------------------------------------------- 

2301 # Implement specific SR methods. 

2302 # -------------------------------------------------------------------------- 

2303 

2304 def _rename(self, oldpath, newpath): 

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

2306 volume_uuid = self._linstor.get_volume_uuid_from_device_path(oldpath) 

2307 self._linstor.update_volume_name(volume_uuid, newpath) 

2308 

2309 def _do_snapshot( 

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

2311 ): 

2312 # If cbt enabled, save file consistency state. 

2313 if cbtlog is not None: 

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

2315 consistency_state = False 

2316 else: 

2317 consistency_state = True 

2318 util.SMlog( 

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

2320 .format(consistency_state, vdi_uuid) 

2321 ) 

2322 else: 

2323 consistency_state = None 

2324 

2325 if self.vdi_type != vhdutil.VDI_TYPE_VHD: 

2326 raise xs_errors.XenError('Unimplemented') 

2327 

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

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

2330 try: 

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

2332 finally: 

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

2334 

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

2336 util.SMlog( 

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

2338 .format(self.uuid, snap_type) 

2339 ) 

2340 

2341 # 1. Checks... 

2342 if self.hidden: 

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

2344 

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

2346 if depth == -1: 

2347 raise xs_errors.XenError( 

2348 'VDIUnavailable', 

2349 opterr='failed to get VHD depth' 

2350 ) 

2351 elif depth >= vhdutil.MAX_CHAIN_SIZE: 

2352 raise xs_errors.XenError('SnapshotChainTooLong') 

2353 

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

2355 self._create_chain_paths(self.uuid) 

2356 

2357 volume_path = self.path 

2358 if not util.pathexists(volume_path): 

2359 raise xs_errors.XenError( 

2360 'EIO', 

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

2362 ) 

2363 

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

2365 base_uuid = util.gen_uuid() 

2366 snap_uuid = None 

2367 

2368 if snap_type == VDI.SNAPSHOT_DOUBLE: 

2369 snap_uuid = util.gen_uuid() 

2370 

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

2372 

2373 active_uuid = self.uuid 

2374 self.sr._journaler.create( 

2375 LinstorJournaler.CLONE, active_uuid, clone_info 

2376 ) 

2377 

2378 try: 

2379 # 3. Self becomes the new base. 

2380 # The device path remains the same. 

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

2382 self.uuid = base_uuid 

2383 self.location = self.uuid 

2384 self.read_only = True 

2385 self.managed = False 

2386 

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

2388 active_vdi = self._create_snapshot(active_uuid) 

2389 

2390 snap_vdi = None 

2391 if snap_type == VDI.SNAPSHOT_DOUBLE: 

2392 snap_vdi = self._create_snapshot(snap_uuid, active_uuid) 

2393 

2394 self.label = 'base copy' 

2395 self.description = '' 

2396 

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

2398 # in subsequent scans. 

2399 self._mark_hidden() 

2400 self._linstor.update_volume_metadata( 

2401 self.uuid, {READ_ONLY_TAG: True} 

2402 ) 

2403 

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

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

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

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

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

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

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

2411 active_vdi.sm_config[key] = sm_config[key] 

2412 

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

2414 # delete base if unused. 

2415 introduce_parent = True 

2416 try: 

2417 snap_parent = None 

2418 if snap_vdi: 

2419 snap_parent = snap_vdi.parent 

2420 

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

2422 snap_type == VDI.SNAPSHOT_SINGLE or 

2423 snap_type == VDI.SNAPSHOT_INTERNAL or 

2424 snap_parent != self.uuid 

2425 ): 

2426 util.SMlog( 

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

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

2429 ) 

2430 introduce_parent = False 

2431 self._linstor.destroy_volume(self.uuid) 

2432 except Exception as e: 

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

2434 pass 

2435 

2436 # 8. Introduce the new VDI records. 

2437 if snap_vdi: 

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

2439 # new snapshot disk. 

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

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

2442 # TODO: Maybe remove key_hash support. 

2443 if 'key_hash' in sm_config: 

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

2445 # If we have CBT enabled on the VDI, 

2446 # set CBT status for the new snapshot disk. 

2447 if cbtlog: 

2448 snap_vdi.cbt_enabled = True 

2449 

2450 if snap_vdi: 

2451 snap_vdi_ref = snap_vdi._db_introduce() 

2452 util.SMlog( 

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

2454 .format(snap_vdi_ref, snap_vdi.uuid) 

2455 ) 

2456 if introduce_parent: 

2457 base_vdi_ref = self._db_introduce() 

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

2459 util.SMlog( 

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

2461 .format(base_vdi_ref, self.uuid) 

2462 ) 

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

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

2465 NAME_DESCRIPTION_TAG: util.to_plain_string( 

2466 self.description 

2467 ), 

2468 READ_ONLY_TAG: True, 

2469 METADATA_OF_POOL_TAG: '' 

2470 }) 

2471 

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

2473 if snap_type == VDI.SNAPSHOT_DOUBLE and cbtlog: 

2474 try: 

2475 self._cbt_snapshot(snap_uuid, cbt_consistency) 

2476 except Exception: 

2477 # CBT operation failed. 

2478 # TODO: Implement me. 

2479 raise 

2480 

2481 if snap_type != VDI.SNAPSHOT_INTERNAL: 

2482 self.sr._update_stats(self.size) 

2483 

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

2485 ret_vdi = snap_vdi 

2486 if not ret_vdi: 

2487 ret_vdi = self 

2488 if not ret_vdi: 

2489 ret_vdi = active_vdi 

2490 

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

2492 self.session.xenapi.VDI.set_sm_config( 

2493 vdi_ref, active_vdi.sm_config 

2494 ) 

2495 except Exception: 

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

2497 try: 

2498 self.sr._handle_interrupted_clone( 

2499 active_uuid, clone_info, force_undo=True 

2500 ) 

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

2502 except Exception as e: 

2503 util.SMlog( 

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

2505 .format(e) 

2506 ) 

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

2508 

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

2510 

2511 return ret_vdi.get_params() 

2512 

2513 @staticmethod 

2514 def _start_persistent_http_server(volume_name): 

2515 pid_path = None 

2516 http_server = None 

2517 

2518 try: 

2519 if volume_name == HA_VOLUME_NAME: 

2520 port = '8076' 

2521 else: 

2522 port = '8077' 

2523 

2524 try: 

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

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

2527 # block indefinitely. 

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

2529 host_ip = util.get_this_host_address(session) 

2530 except: 

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

2532 host_ip, _ = get_ips_from_xha_config_file() 

2533 if not host_ip: 

2534 raise Exception( 

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

2536 ) 

2537 

2538 arguments = [ 

2539 'http-disk-server', 

2540 '--disk', 

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

2542 '--ip', 

2543 host_ip, 

2544 '--port', 

2545 port 

2546 ] 

2547 

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

2549 http_server = subprocess.Popen( 

2550 [FORK_LOG_DAEMON] + arguments, 

2551 stdout=subprocess.PIPE, 

2552 stderr=subprocess.STDOUT, 

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

2554 # touch the current one. 

2555 preexec_fn=os.setsid 

2556 ) 

2557 

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

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

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

2561 

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

2563 def is_ready(): 

2564 while http_server.poll() is None: 

2565 line = http_server.stdout.readline() 

2566 if reg_server_ready.search(line): 

2567 return True 

2568 return False 

2569 try: 

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

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

2572 except util.TimeoutException: 

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

2574 except Exception as e: 

2575 if pid_path: 

2576 try: 

2577 os.remove(pid_path) 

2578 except Exception: 

2579 pass 

2580 

2581 if http_server: 

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

2583 try: 

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

2585 except: 

2586 pass 

2587 

2588 raise xs_errors.XenError( 

2589 'VDIUnavailable', 

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

2591 ) 

2592 

2593 def _start_persistent_nbd_server(self, volume_name): 

2594 pid_path = None 

2595 nbd_path = None 

2596 nbd_server = None 

2597 

2598 try: 

2599 # We use a precomputed device size. 

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

2601 if volume_name == HA_VOLUME_NAME: 

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

2603 port = '8076' 

2604 device_size = 4 * 1024 * 1024 

2605 else: 

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

2607 port = '8077' 

2608 device_size = 256 * 1024 * 1024 

2609 

2610 try: 

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

2612 ips = util.get_host_addresses(session) 

2613 except Exception as e: 

2614 _, ips = get_ips_from_xha_config_file() 

2615 if not ips: 

2616 raise Exception( 

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

2618 ) 

2619 ips = ips.values() 

2620 

2621 arguments = [ 

2622 'nbd-http-server', 

2623 '--socket-path', 

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

2625 '--nbd-name', 

2626 volume_name, 

2627 '--urls', 

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

2629 '--device-size', 

2630 str(device_size) 

2631 ] 

2632 

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

2634 nbd_server = subprocess.Popen( 

2635 [FORK_LOG_DAEMON] + arguments, 

2636 stdout=subprocess.PIPE, 

2637 stderr=subprocess.STDOUT, 

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

2639 # touch the current one. 

2640 preexec_fn=os.setsid 

2641 ) 

2642 

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

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

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

2646 

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

2648 def get_nbd_path(): 

2649 while nbd_server.poll() is None: 

2650 line = nbd_server.stdout.readline() 

2651 match = reg_nbd_path.search(line) 

2652 if match: 

2653 return match.group(1) 

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

2655 try: 

2656 nbd_path = util.timeout_call(10, get_nbd_path) 

2657 if nbd_path is None: 

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

2659 except util.TimeoutException: 

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

2661 

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

2663 os.symlink(nbd_path, self.path) 

2664 except Exception as e: 

2665 if pid_path: 

2666 try: 

2667 os.remove(pid_path) 

2668 except Exception: 

2669 pass 

2670 

2671 if nbd_path: 

2672 try: 

2673 os.remove(nbd_path) 

2674 except Exception: 

2675 pass 

2676 

2677 if nbd_server: 

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

2679 try: 

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

2681 except: 

2682 pass 

2683 

2684 raise xs_errors.XenError( 

2685 'VDIUnavailable', 

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

2687 ) 

2688 

2689 @classmethod 

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

2691 try: 

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

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

2694 return 

2695 

2696 pid = None 

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

2698 try: 

2699 pid = int(pid_file.read()) 

2700 except Exception: 

2701 pass 

2702 

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

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

2705 try: 

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

2707 except Exception as e: 

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

2709 

2710 os.remove(path) 

2711 except: 

2712 pass 

2713 

2714 @classmethod 

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

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

2717 

2718 @classmethod 

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

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

2721 

2722 def _check_http_nbd_volume_name(self): 

2723 volume_name = self.path[14:] 

2724 if volume_name not in [ 

2725 HA_VOLUME_NAME, REDO_LOG_VOLUME_NAME 

2726 ]: 

2727 raise xs_errors.XenError( 

2728 'VDIUnavailable', 

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

2730 ) 

2731 return volume_name 

2732 

2733 def _attach_using_http_nbd(self): 

2734 volume_name = self._check_http_nbd_volume_name() 

2735 

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

2737 self._kill_persistent_nbd_server(volume_name) 

2738 self._kill_persistent_http_server(volume_name) 

2739 

2740 # 0. Fetch drbd path. 

2741 must_get_device_path = True 

2742 if not self.sr._is_master: 

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

2744 try: 

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

2746 except Exception as e: 

2747 raise xs_errors.XenError( 

2748 'VDIUnavailable', 

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

2750 .format(self.uuid, e) 

2751 ) 

2752 

2753 hostname = socket.gethostname() 

2754 must_get_device_path = hostname in volume_info.diskful 

2755 

2756 drbd_path = None 

2757 if must_get_device_path or self.sr._is_master: 

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

2759 # or diskful available to init HA. 

2760 # It also avoid this error in xensource.log 

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

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

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

2764 available = False 

2765 try: 

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

2767 available = util.pathexists(drbd_path) 

2768 except Exception: 

2769 pass 

2770 

2771 if not available: 

2772 raise xs_errors.XenError( 

2773 'VDIUnavailable', 

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

2775 ) 

2776 

2777 # 1. Prepare http-nbd folder. 

2778 try: 

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

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

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

2782 os.remove(self.path) 

2783 except OSError as e: 

2784 if e.errno != errno.EEXIST: 

2785 raise xs_errors.XenError( 

2786 'VDIUnavailable', 

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

2788 ) 

2789 

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

2791 http_service = None 

2792 if drbd_path: 

2793 assert(drbd_path in ( 

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

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

2796 )) 

2797 self._start_persistent_http_server(volume_name) 

2798 

2799 # 3. Start NBD server in all cases. 

2800 try: 

2801 self._start_persistent_nbd_server(volume_name) 

2802 except Exception as e: 

2803 if drbd_path: 

2804 self._kill_persistent_http_server(volume_name) 

2805 raise 

2806 

2807 self.attached = True 

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

2809 

2810 def _detach_using_http_nbd(self): 

2811 volume_name = self._check_http_nbd_volume_name() 

2812 self._kill_persistent_nbd_server(volume_name) 

2813 self._kill_persistent_http_server(volume_name) 

2814 

2815 def _create_chain_paths(self, vdi_uuid): 

2816 # OPTIMIZE: Add a limit_to_first_allocated_block param to limit vhdutil calls. 

2817 # Useful for the snapshot code algorithm. 

2818 

2819 while vdi_uuid: 

2820 path = self._linstor.get_device_path(vdi_uuid) 

2821 if not util.pathexists(path): 

2822 raise xs_errors.XenError( 

2823 'VDIUnavailable', opterr='Could not find: {}'.format(path) 

2824 ) 

2825 

2826 # Diskless path can be created on the fly, ensure we can open it. 

2827 def check_volume_usable(): 

2828 while True: 

2829 try: 

2830 with open(path, 'r+'): 

2831 pass 

2832 except IOError as e: 

2833 if e.errno == errno.ENODATA: 

2834 time.sleep(2) 

2835 continue 

2836 if e.errno == errno.EROFS: 

2837 util.SMlog('Volume not attachable because RO. Openers: {}'.format( 

2838 self.sr._linstor.get_volume_openers(vdi_uuid) 

2839 )) 

2840 raise 

2841 break 

2842 util.retry(check_volume_usable, 15, 2) 

2843 

2844 vdi_uuid = self.sr._vhdutil.get_vhd_info(vdi_uuid).parentUuid 

2845 

2846# ------------------------------------------------------------------------------ 

2847 

2848 

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

2850 def run(): 

2851 SRCommand.run(LinstorSR, DRIVER_INFO) 

2852 

2853 if not TRACE_PERFS: 

2854 run() 

2855 else: 

2856 util.make_profile('LinstorSR', run) 

2857else: 

2858 SR.registerSR(LinstorSR)