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 

27 LINSTOR_AVAILABLE = True 

28except ImportError: 

29 LINSTOR_AVAILABLE = False 

30 

31from lock import Lock 

32import blktap2 

33import cleanup 

34import distutils 

35import errno 

36import functools 

37import lvutil 

38import os 

39import re 

40import scsiutil 

41import signal 

42import socket 

43import SR 

44import SRCommand 

45import subprocess 

46import time 

47import traceback 

48import util 

49import VDI 

50import vhdutil 

51import xml.etree.ElementTree as xml_parser 

52import xmlrpc.client 

53import xs_errors 

54 

55from srmetadata import \ 

56 NAME_LABEL_TAG, NAME_DESCRIPTION_TAG, IS_A_SNAPSHOT_TAG, SNAPSHOT_OF_TAG, \ 

57 TYPE_TAG, VDI_TYPE_TAG, READ_ONLY_TAG, SNAPSHOT_TIME_TAG, \ 

58 METADATA_OF_POOL_TAG 

59 

60HIDDEN_TAG = 'hidden' 

61 

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

63 

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

65 

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

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

68# specific conditions: 

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

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

71USE_HTTP_NBD_SERVERS = True 

72 

73# Useful flag to trace calls using cProfile. 

74TRACE_PERFS = False 

75 

76# Enable/Disable VHD key hash support. 

77USE_KEY_HASH = False 

78 

79# ============================================================================== 

80 

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

82# 'VDI_CONFIG_CBT', 'SR_PROBE' 

83 

84CAPABILITIES = [ 

85 'ATOMIC_PAUSE', 

86 'SR_UPDATE', 

87 'VDI_CREATE', 

88 'VDI_DELETE', 

89 'VDI_UPDATE', 

90 'VDI_ATTACH', 

91 'VDI_DETACH', 

92 'VDI_ACTIVATE', 

93 'VDI_DEACTIVATE', 

94 'VDI_CLONE', 

95 'VDI_MIRROR', 

96 'VDI_RESIZE', 

97 'VDI_SNAPSHOT', 

98 'VDI_GENERATE_CONFIG' 

99] 

100 

101CONFIGURATION = [ 

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

103 ['redundancy', 'replication count'], 

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

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

106] 

107 

108DRIVER_INFO = { 

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

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

111 'vendor': 'Vates', 

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

113 'driver_version': '1.0', 

114 'required_api_version': '1.0', 

115 'capabilities': CAPABILITIES, 

116 'configuration': CONFIGURATION 

117} 

118 

119DRIVER_CONFIG = {'ATTACH_FROM_CONFIG_WITH_TAPDISK': False} 

120 

121OPS_EXCLUSIVE = [ 

122 'sr_create', 'sr_delete', 'sr_attach', 'sr_detach', 'sr_scan', 

123 'sr_update', 'sr_probe', 'vdi_init', 'vdi_create', 'vdi_delete', 

124 'vdi_attach', 'vdi_detach', 'vdi_clone', 'vdi_snapshot', 

125] 

126 

127# ============================================================================== 

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

129# ============================================================================== 

130 

131 

132def compute_volume_size(virtual_size, image_type): 

133 if image_type == vhdutil.VDI_TYPE_VHD: 

134 # All LINSTOR VDIs have the metadata area preallocated for 

135 # the maximum possible virtual size (for fast online VDI.resize). 

136 meta_overhead = vhdutil.calcOverheadEmpty(LinstorVDI.MAX_SIZE) 

137 bitmap_overhead = vhdutil.calcOverheadBitmap(virtual_size) 

138 virtual_size += meta_overhead + bitmap_overhead 

139 elif image_type != vhdutil.VDI_TYPE_RAW: 

140 raise Exception('Invalid image type: {}'.format(image_type)) 

141 

142 return LinstorVolumeManager.round_up_volume_size(virtual_size) 

143 

144 

145def try_lock(lock): 

146 for i in range(20): 

147 if lock.acquireNoblock(): 

148 return 

149 time.sleep(1) 

150 raise util.SRBusyException() 

151 

152 

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

154 volume_metadata = linstor.get_volume_metadata(vdi_uuid) 

155 image_type = volume_metadata.get(VDI_TYPE_TAG) 

156 if image_type == vhdutil.VDI_TYPE_RAW: 

157 return 

158 

159 lock = Lock(vhdutil.LOCK_TYPE_SR, sr_uuid) 

160 try: 

161 try_lock(lock) 

162 

163 device_path = linstor.get_device_path(vdi_uuid) 

164 

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

166 # there is nothing to do. 

167 vhd_size = compute_volume_size( 

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

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

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

171 image_type 

172 ) 

173 

174 volume_info = linstor.get_volume_info(vdi_uuid) 

175 volume_size = volume_info.virtual_size 

176 

177 if vhd_size > volume_size: 

178 inflate( 

179 journaler, linstor, vdi_uuid, device_path, 

180 vhd_size, volume_size 

181 ) 

182 finally: 

183 lock.release() 

184 

185 

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

187 volume_metadata = linstor.get_volume_metadata(vdi_uuid) 

188 image_type = volume_metadata.get(VDI_TYPE_TAG) 

189 if image_type == vhdutil.VDI_TYPE_RAW: 

190 return 

191 

192 lock = Lock(vhdutil.LOCK_TYPE_SR, sr_uuid) 

193 try: 

194 try_lock(lock) 

195 

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

197 vbds = session.xenapi.VBD.get_all_records_where( 

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

199 ) 

200 

201 num_plugged = 0 

202 for vbd_rec in vbds.values(): 

203 if vbd_rec['currently_attached']: 

204 num_plugged += 1 

205 if num_plugged > 1: 

206 raise xs_errors.XenError( 

207 'VDIUnavailable', 

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

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

210 ) 

211 

212 device_path = linstor.get_device_path(vdi_uuid) 

213 new_volume_size = LinstorVolumeManager.round_up_volume_size( 

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

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

216 LinstorVhdUtil(session, linstor).get_size_phys(vdi_uuid) # pylint: disable = E1120 

217 ) 

218 

219 volume_info = linstor.get_volume_info(vdi_uuid) 

220 old_volume_size = volume_info.virtual_size 

221 deflate( 

222 linstor, vdi_uuid, device_path, new_volume_size, old_volume_size 

223 ) 

224 finally: 

225 lock.release() 

226 

227 

228def inflate(journaler, linstor, vdi_uuid, vdi_path, new_size, old_size): 

229 # Only inflate if the LINSTOR volume capacity is not enough. 

230 new_size = LinstorVolumeManager.round_up_volume_size(new_size) 

231 if new_size <= old_size: 

232 return 

233 

234 util.SMlog( 

235 'Inflate {} (size={}, previous={})' 

236 .format(vdi_uuid, new_size, old_size) 

237 ) 

238 

239 journaler.create( 

240 LinstorJournaler.INFLATE, vdi_uuid, old_size 

241 ) 

242 linstor.resize_volume(vdi_uuid, new_size) 

243 

244 result_size = linstor.get_volume_size(vdi_uuid) 

245 if result_size < new_size: 

246 util.SMlog( 

247 'WARNING: Cannot inflate volume to {}B, result size: {}B' 

248 .format(new_size, result_size) 

249 ) 

250 

251 if not util.zeroOut( 

252 vdi_path, result_size - vhdutil.VHD_FOOTER_SIZE, 

253 vhdutil.VHD_FOOTER_SIZE 

254 ): 

255 raise xs_errors.XenError( 

256 'EIO', 

257 opterr='Failed to zero out VHD footer {}'.format(vdi_path) 

258 ) 

259 

260 LinstorVhdUtil(None, linstor).set_size_phys(vdi_path, result_size, False) 

261 journaler.remove(LinstorJournaler.INFLATE, vdi_uuid) 

262 

263 

264def deflate(linstor, vdi_uuid, vdi_path, new_size, old_size): 

265 new_size = LinstorVolumeManager.round_up_volume_size(new_size) 

266 if new_size >= old_size: 

267 return 

268 

269 util.SMlog( 

270 'Deflate {} (new size={}, previous={})' 

271 .format(vdi_uuid, new_size, old_size) 

272 ) 

273 

274 LinstorVhdUtil(None, linstor).set_size_phys(vdi_path, new_size) 

275 # TODO: Change the LINSTOR volume size using linstor.resize_volume. 

276 

277 

278IPS_XHA_CACHE = None 

279 

280 

281def get_ips_from_xha_config_file(): 

282 if IPS_XHA_CACHE: 

283 return IPS_XHA_CACHE 

284 

285 ips = dict() 

286 host_id = None 

287 try: 

288 # Ensure there is no dirty read problem. 

289 # For example if the HA is reloaded. 

290 tree = util.retry( 

291 lambda: xml_parser.parse(XHA_CONFIG_PATH), 

292 maxretry=10, 

293 period=1 

294 ) 

295 except: 

296 return (None, ips) 

297 

298 def parse_host_nodes(ips, node): 

299 current_id = None 

300 current_ip = None 

301 

302 for sub_node in node: 

303 if sub_node.tag == 'IPaddress': 

304 current_ip = sub_node.text 

305 elif sub_node.tag == 'HostID': 

306 current_id = sub_node.text 

307 else: 

308 continue 

309 

310 if current_id and current_ip: 

311 ips[current_id] = current_ip 

312 return 

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

314 

315 def parse_common_config(ips, node): 

316 for sub_node in node: 

317 if sub_node.tag == 'host': 

318 parse_host_nodes(ips, sub_node) 

319 

320 def parse_local_config(ips, node): 

321 for sub_node in node: 

322 if sub_node.tag == 'localhost': 

323 for host_node in sub_node: 

324 if host_node.tag == 'HostID': 

325 return host_node.text 

326 

327 for node in tree.getroot(): 

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

329 parse_common_config(ips, node) 

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

331 host_id = parse_local_config(ips, node) 

332 else: 

333 continue 

334 

335 if ips and host_id: 

336 break 

337 

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

339 

340 

341def activate_lvm_group(group_name): 

342 path = group_name.split('/') 

343 assert path and len(path) <= 2 

344 try: 

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

346 except Exception as e: 

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

348 

349# ============================================================================== 

350 

351# Usage example: 

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

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

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

355 

356 

357class LinstorSR(SR.SR): 

358 DRIVER_TYPE = 'linstor' 

359 

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

361 PROVISIONING_DEFAULT = 'thin' 

362 

363 MANAGER_PLUGIN = 'linstor-manager' 

364 

365 INIT_STATUS_NOT_SET = 0 

366 INIT_STATUS_IN_PROGRESS = 1 

367 INIT_STATUS_OK = 2 

368 INIT_STATUS_FAIL = 3 

369 

370 # -------------------------------------------------------------------------- 

371 # SR methods. 

372 # -------------------------------------------------------------------------- 

373 

374 @staticmethod 

375 def handles(type): 

376 return type == LinstorSR.DRIVER_TYPE 

377 

378 def load(self, sr_uuid): 

379 if not LINSTOR_AVAILABLE: 

380 raise util.SMException( 

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

382 ) 

383 

384 # Check parameters. 

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

386 raise xs_errors.XenError('LinstorConfigGroupNameMissing') 

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

388 raise xs_errors.XenError('LinstorConfigRedundancyMissing') 

389 

390 self.driver_config = DRIVER_CONFIG 

391 

392 # Check provisioning config. 

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

394 if provisioning: 

395 if provisioning in self.PROVISIONING_TYPES: 

396 self._provisioning = provisioning 

397 else: 

398 raise xs_errors.XenError( 

399 'InvalidArg', 

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

401 self.PROVISIONING_TYPES 

402 ) 

403 ) 

404 else: 

405 self._provisioning = self.PROVISIONING_DEFAULT 

406 

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

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

409 distutils.util.strtobool(monitor_db_quorum) 

410 

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

412 # 'vdi_attach_from_config' command is executed. 

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

414 if self._has_session: 

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

416 else: 

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

418 

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

420 if provisioning in self.PROVISIONING_TYPES: 

421 self._provisioning = provisioning 

422 

423 # Define properties for SR parent class. 

424 self.ops_exclusive = OPS_EXCLUSIVE 

425 self.path = LinstorVolumeManager.DEV_ROOT_PATH 

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

427 self.sr_vditype = SR.DEFAULT_TAP 

428 

429 if self.cmd == 'sr_create': 

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

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

432 self._journaler = None 

433 

434 self._is_master = False 

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

436 self._is_master = True 

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

438 

439 self._vdi_shared_time = 0 

440 

441 self._init_status = self.INIT_STATUS_NOT_SET 

442 

443 self._vdis_loaded = False 

444 self._all_volume_info_cache = None 

445 self._all_volume_metadata_cache = None 

446 

447 def _locked_load(method): 

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

449 self._init_status = self.INIT_STATUS_OK 

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

451 

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

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

454 if self.srcmd.cmd == 'sr_attach': 

455 activate_lvm_group(self._group_name) 

456 

457 if not self._has_session: 

458 if self.srcmd.cmd in ( 

459 'vdi_attach_from_config', 

460 'vdi_detach_from_config', 

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

462 # empty command. 

463 None 

464 ): 

465 def create_linstor(uri, attempt_count=30): 

466 self._linstor = LinstorVolumeManager( 

467 uri, 

468 self._group_name, 

469 logger=util.SMlog, 

470 attempt_count=attempt_count 

471 ) 

472 

473 controller_uri = get_controller_uri() 

474 if controller_uri: 

475 create_linstor(controller_uri) 

476 else: 

477 def connect(): 

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

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

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

481 controller_uri = 'linstor://' + ip 

482 try: 

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

484 create_linstor(controller_uri, attempt_count=0) 

485 return controller_uri 

486 except: 

487 pass 

488 

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

490 if not controller_uri: 

491 raise xs_errors.XenError( 

492 'SRUnavailable', 

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

494 ) 

495 

496 self._journaler = LinstorJournaler( 

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

498 ) 

499 

500 if self.srcmd.cmd is None: 

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

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

503 

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

505 

506 if not self._is_master: 

507 if self.cmd in [ 

508 'sr_create', 'sr_delete', 'sr_update', 'sr_probe', 

509 'sr_scan', 'vdi_create', 'vdi_delete', 'vdi_resize', 

510 'vdi_snapshot', 'vdi_clone' 

511 ]: 

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

513 raise xs_errors.XenError('LinstorMaster') 

514 

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

516 # the VDI before the LinstorJournaler/LinstorVolumeManager 

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

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

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

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

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

522 self._vdi_shared_time = time.time() 

523 

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

525 try: 

526 self._reconnect() 

527 except Exception as e: 

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

529 

530 if self._linstor: 

531 try: 

532 hosts = self._linstor.disconnected_hosts 

533 except Exception as e: 

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

535 

536 if hosts: 

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

538 

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

540 if ( 

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

542 self.cmd != 'vdi_create' 

543 ): 

544 self._linstor.ensure_volume_is_not_locked( 

545 self.srcmd.params['vdi_uuid'] 

546 ) 

547 

548 try: 

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

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

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

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

553 # 

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

555 # resourceless volumes. 

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

557 'vdi_attach', 'vdi_detach', 

558 'vdi_activate', 'vdi_deactivate', 

559 'vdi_epoch_begin', 'vdi_epoch_end', 

560 'vdi_update', 'vdi_destroy' 

561 ]: 

562 load_vdis = ( 

563 self.cmd == 'sr_scan' or 

564 self.cmd == 'sr_attach' 

565 ) or len( 

566 self._journaler.get_all(LinstorJournaler.INFLATE) 

567 ) or len( 

568 self._journaler.get_all(LinstorJournaler.CLONE) 

569 ) 

570 

571 if load_vdis: 

572 self._load_vdis() 

573 

574 self._linstor.remove_resourceless_volumes() 

575 

576 self._synchronize_metadata() 

577 except Exception as e: 

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

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

580 # from the XAPI database otherwise. 

581 raise e 

582 util.SMlog( 

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

584 ) 

585 util.SMlog(traceback.format_exc()) 

586 

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

588 

589 @functools.wraps(wrapped_method) 

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

591 if self._init_status in \ 

592 (self.INIT_STATUS_OK, self.INIT_STATUS_IN_PROGRESS): 

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

594 if self._init_status == self.INIT_STATUS_FAIL: 

595 util.SMlog( 

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

597 .format(method) 

598 ) 

599 else: 

600 try: 

601 self._init_status = self.INIT_STATUS_IN_PROGRESS 

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

603 except Exception: 

604 if self._init_status != self.INIT_STATUS_OK: 

605 self._init_status = self.INIT_STATUS_FAIL 

606 raise 

607 

608 return wrap 

609 

610 def cleanup(self): 

611 if self._vdi_shared_time: 

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

613 

614 @_locked_load 

615 def create(self, uuid, size): 

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

617 

618 host_adresses = util.get_host_addresses(self.session) 

619 if self._redundancy > len(host_adresses): 

620 raise xs_errors.XenError( 

621 'LinstorSRCreate', 

622 opterr='Redundancy greater than host count' 

623 ) 

624 

625 xenapi = self.session.xenapi 

626 srs = xenapi.SR.get_all_records_where( 

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

628 ) 

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

630 

631 for sr in srs.values(): 

632 for pbd in sr['PBDs']: 

633 device_config = xenapi.PBD.get_device_config(pbd) 

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

635 if group_name and group_name == self._group_name: 

636 raise xs_errors.XenError( 

637 'LinstorSRCreate', 

638 opterr='group name must be unique' 

639 ) 

640 

641 if srs: 

642 raise xs_errors.XenError( 

643 'LinstorSRCreate', 

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

645 ) 

646 

647 online_hosts = util.get_online_hosts(self.session) 

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

649 raise xs_errors.XenError( 

650 'LinstorSRCreate', 

651 opterr='Not enough online hosts' 

652 ) 

653 

654 ips = {} 

655 for host_ref in online_hosts: 

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

657 hostname = record['hostname'] 

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

659 

660 # Ensure ports are opened and LINSTOR satellites 

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

662 # must be stopped. 

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

664 

665 # Create SR. 

666 # Throw if the SR already exists. 

667 try: 

668 self._linstor = LinstorVolumeManager.create_sr( 

669 self._group_name, 

670 ips, 

671 self._redundancy, 

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

673 auto_quorum=self._monitor_db_quorum, 

674 logger=util.SMlog 

675 ) 

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

677 except Exception as e: 

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

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

680 

681 try: 

682 util.SMlog( 

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

684 ) 

685 self._update_drbd_reactor_on_all_hosts(enabled=True) 

686 except Exception as e: 

687 try: 

688 self._linstor.destroy() 

689 except Exception as e2: 

690 util.SMlog( 

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

692 .format(e2) 

693 ) 

694 raise e 

695 

696 @_locked_load 

697 def delete(self, uuid): 

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

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

700 

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

702 raise xs_errors.XenError('SRNotEmpty') 

703 

704 node_name = get_controller_node_name() 

705 if not node_name: 

706 raise xs_errors.XenError( 

707 'LinstorSRDelete', 

708 opterr='Cannot get controller node name' 

709 ) 

710 

711 host = None 

712 if node_name == 'localhost': 

713 host = util.get_this_host_ref(self.session) 

714 else: 

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

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

717 if r_name == node_name: 

718 host = slave 

719 break 

720 

721 if not host: 

722 raise xs_errors.XenError( 

723 'LinstorSRDelete', 

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

725 node_name 

726 ) 

727 ) 

728 

729 try: 

730 self._update_drbd_reactor_on_all_hosts( 

731 controller_node_name=node_name, enabled=False 

732 ) 

733 

734 args = { 

735 'groupName': self._group_name, 

736 } 

737 self._exec_manager_command( 

738 host, 'destroy', args, 'LinstorSRDelete' 

739 ) 

740 except Exception as e: 

741 try: 

742 self._update_drbd_reactor_on_all_hosts( 

743 controller_node_name=node_name, enabled=True 

744 ) 

745 except Exception as e2: 

746 util.SMlog( 

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

748 .format(e2) 

749 ) 

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

751 raise xs_errors.XenError( 

752 'LinstorSRDelete', 

753 opterr=str(e) 

754 ) 

755 

756 Lock.cleanupAll(self.uuid) 

757 

758 @_locked_load 

759 def update(self, uuid): 

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

761 

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

763 if not self._linstor: 

764 raise xs_errors.XenError( 

765 'SRUnavailable', 

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

767 ) 

768 

769 self._update_stats(0) 

770 

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

772 xenapi = self.session.xenapi 

773 self._linstor.metadata = { 

774 NAME_LABEL_TAG: util.to_plain_string( 

775 xenapi.SR.get_name_label(self.sr_ref) 

776 ), 

777 NAME_DESCRIPTION_TAG: util.to_plain_string( 

778 xenapi.SR.get_name_description(self.sr_ref) 

779 ) 

780 } 

781 

782 @_locked_load 

783 def attach(self, uuid): 

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

785 

786 if not self._linstor: 

787 raise xs_errors.XenError( 

788 'SRUnavailable', 

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

790 ) 

791 

792 @_locked_load 

793 def detach(self, uuid): 

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

795 cleanup.abort(self.uuid) 

796 

797 @_locked_load 

798 def probe(self): 

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

800 # TODO 

801 

802 @_locked_load 

803 def scan(self, uuid): 

804 if self._init_status == self.INIT_STATUS_FAIL: 

805 return 

806 

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

808 if not self._linstor: 

809 raise xs_errors.XenError( 

810 'SRUnavailable', 

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

812 ) 

813 

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

815 # are loaded. 

816 self._load_vdis() 

817 self._update_physical_size() 

818 

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

820 if self.vdis[vdi_uuid].deleted: 

821 del self.vdis[vdi_uuid] 

822 

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

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

825 ret = super(LinstorSR, self).scan(self.uuid) 

826 self._kick_gc() 

827 return ret 

828 

829 @_locked_load 

830 def vdi(self, uuid): 

831 return LinstorVDI(self, uuid) 

832 

833 _locked_load = staticmethod(_locked_load) 

834 

835 # -------------------------------------------------------------------------- 

836 # Lock. 

837 # -------------------------------------------------------------------------- 

838 

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

840 master = util.get_master_ref(self.session) 

841 

842 command = 'lockVdi' 

843 args = { 

844 'groupName': self._group_name, 

845 'srUuid': self.uuid, 

846 'vdiUuid': vdi_uuid, 

847 'locked': str(locked) 

848 } 

849 

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

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

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

853 # 

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

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

856 # to implement without impacting performance. 

857 if not locked: 

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

859 timeout = LinstorVolumeManager.LOCKED_EXPIRATION_DELAY * 0.7 

860 if elapsed_time >= timeout: 

861 util.SMlog( 

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

863 .format(vdi_uuid) 

864 ) 

865 return 

866 

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

868 

869 # -------------------------------------------------------------------------- 

870 # Network. 

871 # -------------------------------------------------------------------------- 

872 

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

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

875 host_uuid = host_rec['uuid'] 

876 

877 try: 

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

879 host_ref, self.MANAGER_PLUGIN, command, args 

880 ) 

881 except Exception as e: 

882 util.SMlog( 

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

884 host_uuid, self.MANAGER_PLUGIN, command, args 

885 ) 

886 ) 

887 raise e 

888 

889 util.SMlog( 

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

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

892 ) 

893 ) 

894 if ret == 'False': 

895 raise xs_errors.XenError( 

896 error, 

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

898 ) 

899 

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

901 self._exec_manager_command( 

902 host, 

903 'prepareSr' if enabled else 'releaseSr', 

904 {'groupName': group_name}, 

905 'SRUnavailable' 

906 ) 

907 

908 def _prepare_sr_on_all_hosts(self, group_name, enabled): 

909 master = util.get_master_ref(self.session) 

910 self._prepare_sr(master, group_name, enabled) 

911 

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

913 self._prepare_sr(slave, group_name, enabled) 

914 

915 def _update_drbd_reactor(self, host, enabled): 

916 self._exec_manager_command( 

917 host, 

918 'updateDrbdReactor', 

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

920 'SRUnavailable' 

921 ) 

922 

923 def _update_drbd_reactor_on_all_hosts( 

924 self, enabled, controller_node_name=None 

925 ): 

926 if controller_node_name == 'localhost': 

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

928 util.get_this_host_ref(self.session) 

929 )['hostname'] 

930 assert controller_node_name 

931 assert controller_node_name != 'localhost' 

932 

933 controller_host = None 

934 secondary_hosts = [] 

935 

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

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

938 hostname = host_rec['hostname'] 

939 if controller_node_name == hostname: 

940 controller_host = host_ref 

941 else: 

942 secondary_hosts.append((host_ref, hostname)) 

943 

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

945 if controller_node_name and not controller_host: 

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

947 controller_node_name 

948 )) 

949 

950 if enabled and controller_host: 

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

952 action_name, controller_node_name 

953 )) 

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

955 # node name first. 

956 self._update_drbd_reactor(controller_host, enabled) 

957 

958 for host_ref, hostname in secondary_hosts: 

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

960 action_name, hostname 

961 )) 

962 self._update_drbd_reactor(host_ref, enabled) 

963 

964 if not enabled and controller_host: 

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

966 action_name, controller_node_name 

967 )) 

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

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

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

971 self._update_drbd_reactor(controller_host, enabled) 

972 

973 # -------------------------------------------------------------------------- 

974 # Metadata. 

975 # -------------------------------------------------------------------------- 

976 

977 def _synchronize_metadata_and_xapi(self): 

978 try: 

979 # First synch SR parameters. 

980 self.update(self.uuid) 

981 

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

983 xenapi = self.session.xenapi 

984 volumes_metadata = self._linstor.get_volumes_with_metadata() 

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

986 try: 

987 vdi_ref = xenapi.VDI.get_by_uuid(vdi_uuid) 

988 except Exception: 

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

990 continue 

991 

992 label = util.to_plain_string( 

993 xenapi.VDI.get_name_label(vdi_ref) 

994 ) 

995 description = util.to_plain_string( 

996 xenapi.VDI.get_name_description(vdi_ref) 

997 ) 

998 

999 if ( 

1000 volume_metadata.get(NAME_LABEL_TAG) != label or 

1001 volume_metadata.get(NAME_DESCRIPTION_TAG) != description 

1002 ): 

1003 self._linstor.update_volume_metadata(vdi_uuid, { 

1004 NAME_LABEL_TAG: label, 

1005 NAME_DESCRIPTION_TAG: description 

1006 }) 

1007 except Exception as e: 

1008 raise xs_errors.XenError( 

1009 'MetadataError', 

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

1011 ) 

1012 

1013 def _synchronize_metadata(self): 

1014 if not self._is_master: 

1015 return 

1016 

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

1018 if self.cmd == 'sr_attach': 

1019 try: 

1020 util.SMlog( 

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

1022 ) 

1023 self._synchronize_metadata_and_xapi() 

1024 except Exception as e: 

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

1026 

1027 # -------------------------------------------------------------------------- 

1028 # Stats. 

1029 # -------------------------------------------------------------------------- 

1030 

1031 def _update_stats(self, virt_alloc_delta): 

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

1033 self.sr_ref 

1034 )) 

1035 

1036 # Update size attributes of the SR parent class. 

1037 self.virtual_allocation = valloc + virt_alloc_delta 

1038 

1039 self._update_physical_size() 

1040 

1041 # Notify SR parent class. 

1042 self._db_update() 

1043 

1044 def _update_physical_size(self): 

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

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

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

1048 self.physical_size = min_physical_size * pool_count // \ 

1049 self._linstor.redundancy 

1050 

1051 self.physical_utilisation = self._linstor.allocated_volume_size 

1052 

1053 # -------------------------------------------------------------------------- 

1054 # VDIs. 

1055 # -------------------------------------------------------------------------- 

1056 

1057 def _load_vdis(self): 

1058 if self._vdis_loaded: 

1059 return 

1060 

1061 assert self._is_master 

1062 

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

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

1065 # enjoy it with a few lines. 

1066 self._create_linstor_cache() 

1067 self._load_vdis_ex() 

1068 self._destroy_linstor_cache() 

1069 

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

1071 self._vdis_loaded = True 

1072 

1073 self._undo_all_journal_transactions() 

1074 

1075 def _load_vdis_ex(self): 

1076 # 1. Get existing VDIs in XAPI. 

1077 xenapi = self.session.xenapi 

1078 xapi_vdi_uuids = set() 

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

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

1081 

1082 # 2. Get volumes info. 

1083 all_volume_info = self._all_volume_info_cache 

1084 volumes_metadata = self._all_volume_metadata_cache 

1085 

1086 # 3. Get CBT vdis. 

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

1088 cbt_vdis = set() 

1089 for volume_metadata in volumes_metadata.values(): 

1090 cbt_uuid = volume_metadata.get(CBTLOG_TAG) 

1091 if cbt_uuid: 

1092 cbt_vdis.add(cbt_uuid) 

1093 

1094 introduce = False 

1095 

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

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

1098 has_clone_entries = list(self._journaler.get_all( 

1099 LinstorJournaler.CLONE 

1100 ).items()) 

1101 

1102 if has_clone_entries: 

1103 util.SMlog( 

1104 'Cannot introduce VDIs during scan because it exists ' 

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

1106 ) 

1107 else: 

1108 introduce = True 

1109 

1110 # 4. Now check all volume info. 

1111 vdi_to_snaps = {} 

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

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

1114 continue 

1115 

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

1117 if vdi_uuid not in xapi_vdi_uuids: 

1118 if not introduce: 

1119 continue 

1120 

1121 if vdi_uuid.startswith('DELETED_'): 

1122 continue 

1123 

1124 volume_metadata = volumes_metadata.get(vdi_uuid) 

1125 if not volume_metadata: 

1126 util.SMlog( 

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

1128 .format(vdi_uuid) 

1129 ) 

1130 continue 

1131 

1132 util.SMlog( 

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

1134 'LINSTOR and not in XAPI...' 

1135 .format(vdi_uuid) 

1136 ) 

1137 

1138 try: 

1139 self._linstor.get_device_path(vdi_uuid) 

1140 except Exception as e: 

1141 util.SMlog( 

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

1143 .format(vdi_uuid, e) 

1144 ) 

1145 continue 

1146 

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

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

1149 vdi_type = volume_metadata.get(VDI_TYPE_TAG) 

1150 

1151 if not vdi_type: 

1152 util.SMlog( 

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

1154 'without vdi_type' 

1155 ) 

1156 continue 

1157 

1158 sm_config = { 

1159 'vdi_type': vdi_type 

1160 } 

1161 

1162 if vdi_type == vhdutil.VDI_TYPE_RAW: 

1163 managed = not volume_metadata.get(HIDDEN_TAG) 

1164 elif vdi_type == vhdutil.VDI_TYPE_VHD: 

1165 vhd_info = self._vhdutil.get_vhd_info(vdi_uuid) 

1166 managed = not vhd_info.hidden 

1167 if vhd_info.parentUuid: 

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

1169 else: 

1170 util.SMlog( 

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

1172 .format(vdi_uuid, vdi_type) 

1173 ) 

1174 continue 

1175 

1176 util.SMlog( 

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

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

1179 name_label, 

1180 volume_info.virtual_size, 

1181 volume_info.allocated_size 

1182 ) 

1183 ) 

1184 

1185 vdi_ref = xenapi.VDI.db_introduce( 

1186 vdi_uuid, 

1187 name_label, 

1188 volume_metadata.get(NAME_DESCRIPTION_TAG) or '', 

1189 self.sr_ref, 

1190 type, 

1191 False, # sharable 

1192 bool(volume_metadata.get(READ_ONLY_TAG)), 

1193 {}, # other_config 

1194 vdi_uuid, # location 

1195 {}, # xenstore_data 

1196 sm_config, 

1197 managed, 

1198 str(volume_info.virtual_size), 

1199 str(volume_info.allocated_size) 

1200 ) 

1201 

1202 is_a_snapshot = volume_metadata.get(IS_A_SNAPSHOT_TAG) 

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

1204 if is_a_snapshot: 

1205 xenapi.VDI.set_snapshot_time( 

1206 vdi_ref, 

1207 xmlrpc.client.DateTime( 

1208 volume_metadata[SNAPSHOT_TIME_TAG] or 

1209 '19700101T00:00:00Z' 

1210 ) 

1211 ) 

1212 

1213 snap_uuid = volume_metadata[SNAPSHOT_OF_TAG] 

1214 if snap_uuid in vdi_to_snaps: 

1215 vdi_to_snaps[snap_uuid].append(vdi_uuid) 

1216 else: 

1217 vdi_to_snaps[snap_uuid] = [vdi_uuid] 

1218 

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

1220 vdi = self.vdi(vdi_uuid) 

1221 self.vdis[vdi_uuid] = vdi 

1222 

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

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

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

1226 vdi.sm_config_override['key_hash'] = \ 

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

1228 

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

1230 # or already in XAPI. 

1231 cbt_uuid = volume_metadata.get(CBTLOG_TAG) 

1232 if cbt_uuid in cbt_vdis: 

1233 vdi_ref = xenapi.VDI.get_by_uuid(vdi_uuid) 

1234 xenapi.VDI.set_cbt_enabled(vdi_ref, True) 

1235 # For existing VDIs, update local state too. 

1236 # Scan in base class SR updates existing VDIs 

1237 # again based on local states. 

1238 self.vdis[vdi_uuid].cbt_enabled = True 

1239 cbt_vdis.remove(cbt_uuid) 

1240 

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

1242 for src_uuid in vdi_to_snaps: 

1243 try: 

1244 src_ref = xenapi.VDI.get_by_uuid(src_uuid) 

1245 except Exception: 

1246 # The source VDI no longer exists, continue. 

1247 continue 

1248 

1249 for snap_uuid in vdi_to_snaps[src_uuid]: 

1250 try: 

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

1252 snap_ref = xenapi.VDI.get_by_uuid(snap_uuid) 

1253 xenapi.VDI.set_snapshot_of(snap_ref, src_ref) 

1254 except Exception as e: 

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

1256 

1257 # TODO: Check correctly how to use CBT. 

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

1259 

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

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

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

1263 for cbt_uuid in cbt_vdis: 

1264 new_vdi = self.vdi(cbt_uuid) 

1265 new_vdi.ty = 'cbt_metadata' 

1266 new_vdi.cbt_enabled = True 

1267 self.vdis[cbt_uuid] = new_vdi 

1268 

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

1270 self.virtual_allocation = 0 

1271 

1272 # 8. Build geneology. 

1273 geneology = {} 

1274 

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

1276 if vdi.parent: 

1277 if vdi.parent in self.vdis: 

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

1279 if vdi.parent in geneology: 

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

1281 else: 

1282 geneology[vdi.parent] = [vdi_uuid] 

1283 if not vdi.hidden: 

1284 self.virtual_allocation += vdi.size 

1285 

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

1287 # will be GC'ed. 

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

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

1290 util.SMlog( 

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

1292 ) 

1293 del self.vdis[vdi_uuid] 

1294 

1295 # -------------------------------------------------------------------------- 

1296 # Journals. 

1297 # -------------------------------------------------------------------------- 

1298 

1299 def _get_vdi_path_and_parent(self, vdi_uuid, volume_name): 

1300 try: 

1301 device_path = self._linstor.build_device_path(volume_name) 

1302 if not util.pathexists(device_path): 

1303 return (None, None) 

1304 

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

1306 volume_metadata = self._linstor.get_volume_metadata(vdi_uuid) 

1307 vdi_type = volume_metadata[VDI_TYPE_TAG] 

1308 if vdi_type == vhdutil.VDI_TYPE_RAW: 

1309 return (device_path, None) 

1310 

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

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

1313 return (None, None) 

1314 

1315 vhd_info = self._vhdutil.get_vhd_info(vdi_uuid) 

1316 if vhd_info: 

1317 return (device_path, vhd_info.parentUuid) 

1318 except Exception as e: 

1319 util.SMlog( 

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

1321 .format(e) 

1322 ) 

1323 return (None, None) 

1324 

1325 def _undo_all_journal_transactions(self): 

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

1327 self.lock.acquire() 

1328 try: 

1329 self._handle_interrupted_inflate_ops() 

1330 self._handle_interrupted_clone_ops() 

1331 pass 

1332 finally: 

1333 self.lock.release() 

1334 

1335 def _handle_interrupted_inflate_ops(self): 

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

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

1338 self._handle_interrupted_inflate(vdi_uuid, old_size) 

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

1340 

1341 def _handle_interrupted_clone_ops(self): 

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

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

1344 self._handle_interrupted_clone(vdi_uuid, old_size) 

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

1346 

1347 def _handle_interrupted_inflate(self, vdi_uuid, old_size): 

1348 util.SMlog( 

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

1350 .format(vdi_uuid, old_size) 

1351 ) 

1352 

1353 vdi = self.vdis.get(vdi_uuid) 

1354 if not vdi: 

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

1356 return 

1357 

1358 assert not self._all_volume_info_cache 

1359 volume_info = self._linstor.get_volume_info(vdi_uuid) 

1360 

1361 current_size = volume_info.virtual_size 

1362 assert current_size > 0 

1363 

1364 util.zeroOut( 

1365 vdi.path, 

1366 current_size - vhdutil.VHD_FOOTER_SIZE, 

1367 vhdutil.VHD_FOOTER_SIZE 

1368 ) 

1369 deflate(self._linstor, vdi_uuid, vdi.path, old_size, current_size) 

1370 

1371 def _handle_interrupted_clone( 

1372 self, vdi_uuid, clone_info, force_undo=False 

1373 ): 

1374 util.SMlog( 

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

1376 .format(vdi_uuid, clone_info) 

1377 ) 

1378 

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

1380 

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

1382 volume_names = self._linstor.get_volumes_with_name() 

1383 

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

1385 if base_uuid not in volume_names: 

1386 if vdi_uuid in volume_names: 

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

1388 return 

1389 raise util.SMException( 

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

1391 .format(base_uuid, vdi_uuid) 

1392 ) 

1393 

1394 if force_undo: 

1395 util.SMlog('Explicit revert') 

1396 self._undo_clone( 

1397 volume_names, vdi_uuid, base_uuid, snap_uuid 

1398 ) 

1399 return 

1400 

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

1402 if vdi_uuid not in volume_names or \ 

1403 (snap_uuid and snap_uuid not in volume_names): 

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

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

1406 return 

1407 

1408 vdi_path, vdi_parent_uuid = self._get_vdi_path_and_parent( 

1409 vdi_uuid, volume_names[vdi_uuid] 

1410 ) 

1411 snap_path, snap_parent_uuid = self._get_vdi_path_and_parent( 

1412 snap_uuid, volume_names[snap_uuid] 

1413 ) 

1414 

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

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

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

1418 return 

1419 

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

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

1422 

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

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

1425 base_metadata = self._linstor.get_volume_metadata(base_uuid) 

1426 base_type = base_metadata[VDI_TYPE_TAG] 

1427 

1428 if not util.pathexists(base_path): 

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

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

1431 return 

1432 

1433 # Un-hide the parent. 

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

1435 if base_type == vhdutil.VDI_TYPE_VHD: 

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

1437 if vhd_info.hidden: 

1438 self._vhdutil.set_hidden(base_path, False) 

1439 elif base_type == vhdutil.VDI_TYPE_RAW and \ 

1440 base_metadata.get(HIDDEN_TAG): 

1441 self._linstor.update_volume_metadata( 

1442 base_uuid, {HIDDEN_TAG: False} 

1443 ) 

1444 

1445 # Remove the child nodes. 

1446 if snap_uuid and snap_uuid in volume_names: 

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

1448 

1449 try: 

1450 self._linstor.destroy_volume(snap_uuid) 

1451 except Exception as e: 

1452 util.SMlog( 

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

1454 .format(snap_uuid, e) 

1455 ) 

1456 

1457 if vdi_uuid in volume_names: 

1458 try: 

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

1460 self._linstor.destroy_volume(vdi_uuid) 

1461 except Exception as e: 

1462 util.SMlog( 

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

1464 .format(vdi_uuid, e) 

1465 ) 

1466 # We can get an exception like this: 

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

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

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

1470 # UUID of this bad VDI before. 

1471 self._linstor.update_volume_uuid( 

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

1473 ) 

1474 

1475 # Rename! 

1476 self._linstor.update_volume_uuid(base_uuid, vdi_uuid) 

1477 

1478 # Inflate to the right size. 

1479 if base_type == vhdutil.VDI_TYPE_VHD: 

1480 vdi = self.vdi(vdi_uuid) 

1481 volume_size = compute_volume_size(vdi.size, vdi.vdi_type) 

1482 inflate( 

1483 self._journaler, self._linstor, vdi_uuid, vdi.path, 

1484 volume_size, vdi.capacity 

1485 ) 

1486 self.vdis[vdi_uuid] = vdi 

1487 

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

1489 # flag to facilitate vm deactivate. 

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

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

1492 

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

1494 

1495 # -------------------------------------------------------------------------- 

1496 # Cache. 

1497 # -------------------------------------------------------------------------- 

1498 

1499 def _create_linstor_cache(self): 

1500 reconnect = False 

1501 

1502 def create_cache(): 

1503 nonlocal reconnect 

1504 try: 

1505 if reconnect: 

1506 self._reconnect() 

1507 return self._linstor.get_volumes_with_info() 

1508 except Exception as e: 

1509 reconnect = True 

1510 raise e 

1511 

1512 self._all_volume_metadata_cache = \ 

1513 self._linstor.get_volumes_with_metadata() 

1514 self._all_volume_info_cache = util.retry( 

1515 create_cache, 

1516 maxretry=10, 

1517 period=3 

1518 ) 

1519 

1520 def _destroy_linstor_cache(self): 

1521 self._all_volume_info_cache = None 

1522 self._all_volume_metadata_cache = None 

1523 

1524 # -------------------------------------------------------------------------- 

1525 # Misc. 

1526 # -------------------------------------------------------------------------- 

1527 

1528 def _reconnect(self): 

1529 controller_uri = get_controller_uri() 

1530 

1531 self._journaler = LinstorJournaler( 

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

1533 ) 

1534 

1535 # Try to open SR if exists. 

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

1537 # we are trying to execute an exclusive operation. 

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

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

1540 # the SR is locked. 

1541 self._linstor = LinstorVolumeManager( 

1542 controller_uri, 

1543 self._group_name, 

1544 repair=( 

1545 self._is_master and 

1546 self.srcmd.cmd in self.ops_exclusive 

1547 ), 

1548 logger=util.SMlog 

1549 ) 

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

1551 

1552 def _ensure_space_available(self, amount_needed): 

1553 space_available = self._linstor.max_volume_size_allowed 

1554 if (space_available < amount_needed): 

1555 util.SMlog( 

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

1557 space_available, amount_needed 

1558 ) 

1559 ) 

1560 raise xs_errors.XenError('SRNoSpace') 

1561 

1562 def _kick_gc(self): 

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

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

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

1566 lock = Lock(cleanup.LOCK_TYPE_RUNNING, self.uuid) 

1567 if not lock.acquireNoblock(): 

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

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

1570 return 

1571 

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

1573 try: 

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

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

1576 except util.CommandException as e: 

1577 if e.code != errno.ETIMEDOUT: 

1578 raise 

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

1580 else: 

1581 lock.release() 

1582 

1583 util.SMlog('Kicking GC') 

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

1585 

1586# ============================================================================== 

1587# LinstorSr VDI 

1588# ============================================================================== 

1589 

1590 

1591class LinstorVDI(VDI.VDI): 

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

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

1594 TYPE_RAW = 'raw' 

1595 TYPE_VHD = 'vhd' 

1596 

1597 MAX_SIZE = 2 * 1024 * 1024 * 1024 * 1024 # Max VHD size. 

1598 

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

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

1601 # Increase the performance when resize is called. 

1602 MAX_METADATA_VIRT_SIZE = 2 * 1024 * 1024 

1603 

1604 # -------------------------------------------------------------------------- 

1605 # VDI methods. 

1606 # -------------------------------------------------------------------------- 

1607 

1608 def load(self, vdi_uuid): 

1609 self._lock = self.sr.lock 

1610 self._exists = True 

1611 self._linstor = self.sr._linstor 

1612 

1613 # Update hidden parent property. 

1614 self.hidden = False 

1615 

1616 def raise_bad_load(e): 

1617 util.SMlog( 

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

1619 ) 

1620 util.SMlog(traceback.format_exc()) 

1621 raise xs_errors.XenError( 

1622 'VDIUnavailable', 

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

1624 ) 

1625 

1626 # Try to load VDI. 

1627 try: 

1628 if ( 

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

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

1631 ): 

1632 self.vdi_type = vhdutil.VDI_TYPE_RAW 

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

1634 else: 

1635 self._determine_type_and_path() 

1636 self._load_this() 

1637 

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

1639 self.uuid, self.path, self.hidden 

1640 )) 

1641 except LinstorVolumeManagerError as e: 

1642 # 1. It may be a VDI deletion. 

1643 if e.code == LinstorVolumeManagerError.ERR_VOLUME_NOT_EXISTS: 

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

1645 self.deleted = True 

1646 return 

1647 

1648 # 2. Or maybe a creation. 

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

1650 # Set type attribute of VDI parent class. 

1651 # We use VHD by default. 

1652 self.vdi_type = vhdutil.VDI_TYPE_VHD 

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

1654 

1655 self._exists = False 

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

1657 if vdi_sm_config is not None: 

1658 type = vdi_sm_config.get('type') 

1659 if type is not None: 

1660 if type == self.TYPE_RAW: 

1661 self.vdi_type = vhdutil.VDI_TYPE_RAW 

1662 elif type == self.TYPE_VHD: 

1663 self.vdi_type = vhdutil.VDI_TYPE_VHD 

1664 else: 

1665 raise xs_errors.XenError( 

1666 'VDICreate', 

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

1668 ) 

1669 if self.vdi_type == vhdutil.VDI_TYPE_VHD: 

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

1671 

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

1673 self._update_device_name(None) 

1674 return 

1675 raise_bad_load(e) 

1676 except Exception as e: 

1677 raise_bad_load(e) 

1678 

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

1680 # Usage example: 

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

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

1683 

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

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

1686 if self._exists: 

1687 raise xs_errors.XenError('VDIExists') 

1688 

1689 assert self.uuid 

1690 assert self.ty 

1691 assert self.vdi_type 

1692 

1693 # 2. Compute size and check space available. 

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

1695 volume_size = compute_volume_size(size, self.vdi_type) 

1696 util.SMlog( 

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

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

1699 ) 

1700 self.sr._ensure_space_available(volume_size) 

1701 

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

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

1704 

1705 # 4. Create! 

1706 failed = False 

1707 try: 

1708 volume_name = None 

1709 if self.ty == 'ha_statefile': 

1710 volume_name = 'xcp-persistent-ha-statefile' 

1711 elif self.ty == 'redo_log': 

1712 volume_name = 'xcp-persistent-redo-log' 

1713 

1714 self._linstor.create_volume( 

1715 self.uuid, volume_size, persistent=False, 

1716 volume_name=volume_name 

1717 ) 

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

1719 

1720 self._update_device_name(volume_info.name) 

1721 

1722 if self.vdi_type == vhdutil.VDI_TYPE_RAW: 

1723 self.size = volume_info.virtual_size 

1724 else: 

1725 self.sr._vhdutil.create( 

1726 self.path, size, False, self.MAX_METADATA_VIRT_SIZE 

1727 ) 

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

1729 

1730 if self._key_hash: 

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

1732 

1733 # Because vhdutil commands modify the volume data, 

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

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

1736 

1737 volume_metadata = { 

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

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

1740 IS_A_SNAPSHOT_TAG: False, 

1741 SNAPSHOT_OF_TAG: '', 

1742 SNAPSHOT_TIME_TAG: '', 

1743 TYPE_TAG: self.ty, 

1744 VDI_TYPE_TAG: self.vdi_type, 

1745 READ_ONLY_TAG: bool(self.read_only), 

1746 METADATA_OF_POOL_TAG: '' 

1747 } 

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

1749 

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

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

1752 # an already opened volume. 

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

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

1755 

1756 self._linstor.mark_volume_as_persistent(self.uuid) 

1757 except util.CommandException as e: 

1758 failed = True 

1759 raise xs_errors.XenError( 

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

1761 ) 

1762 except Exception as e: 

1763 failed = True 

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

1765 finally: 

1766 if failed: 

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

1768 try: 

1769 self._linstor.destroy_volume(self.uuid) 

1770 except Exception as e: 

1771 util.SMlog( 

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

1773 '{}'.format(e) 

1774 ) 

1775 

1776 self.utilisation = volume_info.allocated_size 

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

1778 

1779 self.ref = self._db_introduce() 

1780 self.sr._update_stats(self.size) 

1781 

1782 return VDI.VDI.get_params(self) 

1783 

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

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

1786 if self.attached: 

1787 raise xs_errors.XenError('VDIInUse') 

1788 

1789 if self.deleted: 

1790 return super(LinstorVDI, self).delete( 

1791 sr_uuid, vdi_uuid, data_only 

1792 ) 

1793 

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

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

1796 raise xs_errors.XenError( 

1797 'VDIDelete', 

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

1799 ) 

1800 

1801 try: 

1802 # Remove from XAPI and delete from LINSTOR. 

1803 self._linstor.destroy_volume(self.uuid) 

1804 if not data_only: 

1805 self._db_forget() 

1806 

1807 self.sr.lock.cleanupAll(vdi_uuid) 

1808 except Exception as e: 

1809 util.SMlog( 

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

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

1812 ) 

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

1814 

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

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

1817 

1818 # TODO: Check size after delete. 

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

1820 self.sr._kick_gc() 

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

1822 

1823 def attach(self, sr_uuid, vdi_uuid): 

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

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

1826 if ( 

1827 not attach_from_config or 

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

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

1830 raise xs_errors.XenError( 

1831 'VDIUnavailable', 

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

1833 'scan SR first to trigger auto-repair' 

1834 ) 

1835 

1836 if not attach_from_config or self.sr._is_master: 

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

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

1839 

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

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

1842 # than the VHD size + bitmap size. 

1843 need_inflate = True 

1844 if ( 

1845 self.vdi_type == vhdutil.VDI_TYPE_RAW or 

1846 not writable or 

1847 self.capacity >= compute_volume_size(self.size, self.vdi_type) 

1848 ): 

1849 need_inflate = False 

1850 

1851 if need_inflate: 

1852 try: 

1853 self._prepare_thin(True) 

1854 except Exception as e: 

1855 raise xs_errors.XenError( 

1856 'VDIUnavailable', 

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

1858 .format(e) 

1859 ) 

1860 

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

1862 self.xenstore_data = {} 

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

1864 

1865 if ( 

1866 USE_HTTP_NBD_SERVERS and 

1867 attach_from_config and 

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

1869 ): 

1870 return self._attach_using_http_nbd() 

1871 

1872 # Ensure we have a path... 

1873 while vdi_uuid: 

1874 path = self._linstor.get_device_path(vdi_uuid) 

1875 if not util.pathexists(path): 

1876 raise xs_errors.XenError( 

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

1878 ) 

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

1880 

1881 self.attached = True 

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

1883 

1884 def detach(self, sr_uuid, vdi_uuid): 

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

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

1887 self.attached = False 

1888 

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

1890 return self._detach_using_http_nbd() 

1891 

1892 if self.vdi_type == vhdutil.VDI_TYPE_RAW: 

1893 return 

1894 

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

1896 # equal to the LINSTOR volume size. 

1897 volume_size = compute_volume_size(self.size, self.vdi_type) 

1898 already_deflated = self.capacity <= volume_size 

1899 

1900 if already_deflated: 

1901 util.SMlog( 

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

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

1904 ) 

1905 

1906 need_deflate = True 

1907 if already_deflated: 

1908 need_deflate = False 

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

1910 need_deflate = False 

1911 

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

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

1914 need_deflate = True 

1915 

1916 if need_deflate: 

1917 try: 

1918 self._prepare_thin(False) 

1919 except Exception as e: 

1920 raise xs_errors.XenError( 

1921 'VDIUnavailable', 

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

1923 .format(e) 

1924 ) 

1925 

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

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

1928 if not self.sr._is_master: 

1929 raise xs_errors.XenError( 

1930 'VDISize', 

1931 opterr='resize on slave not allowed' 

1932 ) 

1933 

1934 if self.hidden: 

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

1936 

1937 # Compute the virtual VHD and DRBD volume size. 

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

1939 volume_size = compute_volume_size(size, self.vdi_type) 

1940 util.SMlog( 

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

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

1943 ) 

1944 

1945 if size < self.size: 

1946 util.SMlog( 

1947 'vdi_resize: shrinking not supported: ' 

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

1949 ) 

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

1951 

1952 if size == self.size: 

1953 return VDI.VDI.get_params(self) 

1954 

1955 if self.vdi_type == vhdutil.VDI_TYPE_RAW: 

1956 old_volume_size = self.size 

1957 else: 

1958 old_volume_size = self.utilisation 

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

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

1961 new_volume_size = old_volume_size 

1962 assert new_volume_size >= old_volume_size 

1963 

1964 space_needed = new_volume_size - old_volume_size 

1965 self.sr._ensure_space_available(space_needed) 

1966 

1967 old_size = self.size 

1968 if self.vdi_type == vhdutil.VDI_TYPE_RAW: 

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

1970 else: 

1971 if new_volume_size != old_volume_size: 

1972 inflate( 

1973 self.sr._journaler, self._linstor, self.uuid, self.path, 

1974 new_volume_size, old_volume_size 

1975 ) 

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

1977 

1978 # Reload size attributes. 

1979 self._load_this() 

1980 

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

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

1983 self.session.xenapi.VDI.set_physical_utilisation( 

1984 vdi_ref, str(self.utilisation) 

1985 ) 

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

1987 return VDI.VDI.get_params(self) 

1988 

1989 def clone(self, sr_uuid, vdi_uuid): 

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

1991 

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

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

1994 if self.vdi_type != vhdutil.VDI_TYPE_VHD: 

1995 raise xs_errors.XenError('Unimplemented') 

1996 

1997 parent_uuid = vdi1 

1998 parent_path = self._linstor.get_device_path(parent_uuid) 

1999 

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

2001 # have a readonly error. 

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

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

2004 

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

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

2007 try: 

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

2009 self.sr._vhdutil.set_hidden(parent_path) 

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

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

2012 ) 

2013 finally: 

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

2015 

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

2017 raise util.SMException( 

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

2019 ) 

2020 

2021 util.SMlog('Compose done') 

2022 

2023 def generate_config(self, sr_uuid, vdi_uuid): 

2024 """ 

2025 Generate the XML config required to attach and activate 

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

2027 activation is handled by vdi_attach_from_config below. 

2028 """ 

2029 

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

2031 

2032 resp = {} 

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

2034 resp['sr_uuid'] = sr_uuid 

2035 resp['vdi_uuid'] = self.uuid 

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

2037 resp['command'] = 'vdi_attach_from_config' 

2038 

2039 # By default, we generate a normal config. 

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

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

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

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

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

2045 # instead. 

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

2047 if not USE_HTTP_NBD_SERVERS or volume_name not in [ 

2048 'xcp-persistent-ha-statefile', 'xcp-persistent-redo-log' 

2049 ]: 

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

2051 available = False 

2052 # Try to refresh symlink path... 

2053 try: 

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

2055 available = util.pathexists(self.path) 

2056 except Exception: 

2057 pass 

2058 if not available: 

2059 raise xs_errors.XenError('VDIUnavailable') 

2060 

2061 resp['vdi_path'] = self.path 

2062 else: 

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

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

2065 

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

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

2068 

2069 def attach_from_config(self, sr_uuid, vdi_uuid): 

2070 """ 

2071 Attach and activate a VDI using config generated by 

2072 vdi_generate_config above. This is used for cases such as 

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

2074 """ 

2075 

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

2077 

2078 try: 

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

2080 self.sr.attach(sr_uuid) 

2081 

2082 if not DRIVER_CONFIG['ATTACH_FROM_CONFIG_WITH_TAPDISK']: 

2083 return self.attach(sr_uuid, vdi_uuid) 

2084 except Exception: 

2085 util.logException('LinstorVDI.attach_from_config') 

2086 raise xs_errors.XenError( 

2087 'SRUnavailable', 

2088 opterr='Unable to attach from config' 

2089 ) 

2090 

2091 def reset_leaf(self, sr_uuid, vdi_uuid): 

2092 if self.vdi_type != vhdutil.VDI_TYPE_VHD: 

2093 raise xs_errors.XenError('Unimplemented') 

2094 

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

2096 raise util.SMException( 

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

2098 .format(self.uuid) 

2099 ) 

2100 

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

2102 

2103 def _load_this(self): 

2104 volume_metadata = None 

2105 if self.sr._all_volume_metadata_cache: 

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

2107 if volume_metadata is None: 

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

2109 

2110 volume_info = None 

2111 if self.sr._all_volume_info_cache: 

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

2113 if volume_info is None: 

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

2115 

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

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

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

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

2120 # be lower than virtual size at creation. 

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

2122 self.utilisation = volume_info.allocated_size 

2123 self.capacity = volume_info.virtual_size 

2124 

2125 if self.vdi_type == vhdutil.VDI_TYPE_RAW: 

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

2127 self.size = volume_info.virtual_size 

2128 self.parent = '' 

2129 else: 

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

2131 self.hidden = vhd_info.hidden 

2132 self.size = vhd_info.sizeVirt 

2133 self.parent = vhd_info.parentUuid 

2134 

2135 if self.hidden: 

2136 self.managed = False 

2137 

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

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

2140 

2141 # Update sm_config_override of VDI parent class. 

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

2143 

2144 def _mark_hidden(self, hidden=True): 

2145 if self.hidden == hidden: 

2146 return 

2147 

2148 if self.vdi_type == vhdutil.VDI_TYPE_VHD: 

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

2150 else: 

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

2152 HIDDEN_TAG: hidden 

2153 }) 

2154 self.hidden = hidden 

2155 

2156 def update(self, sr_uuid, vdi_uuid): 

2157 xenapi = self.session.xenapi 

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

2159 

2160 volume_metadata = { 

2161 NAME_LABEL_TAG: util.to_plain_string( 

2162 xenapi.VDI.get_name_label(vdi_ref) 

2163 ), 

2164 NAME_DESCRIPTION_TAG: util.to_plain_string( 

2165 xenapi.VDI.get_name_description(vdi_ref) 

2166 ) 

2167 } 

2168 

2169 try: 

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

2171 except LinstorVolumeManagerError as e: 

2172 if e.code == LinstorVolumeManagerError.ERR_VOLUME_NOT_EXISTS: 

2173 raise xs_errors.XenError( 

2174 'VDIUnavailable', 

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

2176 ) 

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

2178 

2179 # -------------------------------------------------------------------------- 

2180 # Thin provisioning. 

2181 # -------------------------------------------------------------------------- 

2182 

2183 def _prepare_thin(self, attach): 

2184 if self.sr._is_master: 

2185 if attach: 

2186 attach_thin( 

2187 self.session, self.sr._journaler, self._linstor, 

2188 self.sr.uuid, self.uuid 

2189 ) 

2190 else: 

2191 detach_thin( 

2192 self.session, self._linstor, self.sr.uuid, self.uuid 

2193 ) 

2194 else: 

2195 fn = 'attach' if attach else 'detach' 

2196 

2197 master = util.get_master_ref(self.session) 

2198 

2199 args = { 

2200 'groupName': self.sr._group_name, 

2201 'srUuid': self.sr.uuid, 

2202 'vdiUuid': self.uuid 

2203 } 

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

2205 

2206 # Reload size attrs after inflate or deflate! 

2207 self._load_this() 

2208 self.sr._update_physical_size() 

2209 

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

2211 self.session.xenapi.VDI.set_physical_utilisation( 

2212 vdi_ref, str(self.utilisation) 

2213 ) 

2214 

2215 self.session.xenapi.SR.set_physical_utilisation( 

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

2217 ) 

2218 

2219 # -------------------------------------------------------------------------- 

2220 # Generic helpers. 

2221 # -------------------------------------------------------------------------- 

2222 

2223 def _determine_type_and_path(self): 

2224 """ 

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

2226 """ 

2227 

2228 # 1. Check vdi_ref and vdi_type in config. 

2229 try: 

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

2231 if vdi_ref: 

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

2233 vdi_type = sm_config.get('vdi_type') 

2234 if vdi_type: 

2235 # Update parent fields. 

2236 self.vdi_type = vdi_type 

2237 self.sm_config_override = sm_config 

2238 self._update_device_name( 

2239 self._linstor.get_volume_name(self.uuid) 

2240 ) 

2241 return 

2242 except Exception: 

2243 pass 

2244 

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

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

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

2248 self.vdi_type = volume_metadata.get(VDI_TYPE_TAG) 

2249 if not self.vdi_type: 

2250 raise xs_errors.XenError( 

2251 'VDIUnavailable', 

2252 opterr='failed to get vdi_type in metadata' 

2253 ) 

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

2255 

2256 def _update_device_name(self, device_name): 

2257 self._device_name = device_name 

2258 

2259 # Mark path of VDI parent class. 

2260 if device_name: 

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

2262 else: 

2263 self.path = None 

2264 

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

2266 """ 

2267 Snapshot self and return the snapshot VDI object. 

2268 """ 

2269 

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

2271 snap_path = self._linstor.shallow_clone_volume( 

2272 self.uuid, snap_uuid, persistent=False 

2273 ) 

2274 

2275 # 2. Write the snapshot content. 

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

2277 self.sr._vhdutil.snapshot( 

2278 snap_path, self.path, is_raw, self.MAX_METADATA_VIRT_SIZE 

2279 ) 

2280 

2281 # 3. Get snapshot parent. 

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

2283 

2284 # 4. Update metadata. 

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

2286 volume_metadata = { 

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

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

2289 IS_A_SNAPSHOT_TAG: bool(snap_of_uuid), 

2290 SNAPSHOT_OF_TAG: snap_of_uuid, 

2291 SNAPSHOT_TIME_TAG: '', 

2292 TYPE_TAG: self.ty, 

2293 VDI_TYPE_TAG: vhdutil.VDI_TYPE_VHD, 

2294 READ_ONLY_TAG: False, 

2295 METADATA_OF_POOL_TAG: '' 

2296 } 

2297 self._linstor.set_volume_metadata(snap_uuid, volume_metadata) 

2298 

2299 # 5. Set size. 

2300 snap_vdi = LinstorVDI(self.sr, snap_uuid) 

2301 if not snap_vdi._exists: 

2302 raise xs_errors.XenError('VDISnapshot') 

2303 

2304 volume_info = self._linstor.get_volume_info(snap_uuid) 

2305 

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

2307 snap_vdi.utilisation = volume_info.allocated_size 

2308 

2309 # 6. Update sm config. 

2310 snap_vdi.sm_config = {} 

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

2312 if snap_parent: 

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

2314 snap_vdi.parent = snap_parent 

2315 

2316 snap_vdi.label = self.label 

2317 snap_vdi.description = self.description 

2318 

2319 self._linstor.mark_volume_as_persistent(snap_uuid) 

2320 

2321 return snap_vdi 

2322 

2323 # -------------------------------------------------------------------------- 

2324 # Implement specific SR methods. 

2325 # -------------------------------------------------------------------------- 

2326 

2327 def _rename(self, oldpath, newpath): 

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

2329 volume_uuid = self._linstor.get_volume_uuid_from_device_path(oldpath) 

2330 self._linstor.update_volume_name(volume_uuid, newpath) 

2331 

2332 def _do_snapshot( 

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

2334 ): 

2335 # If cbt enabled, save file consistency state. 

2336 if cbtlog is not None: 

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

2338 consistency_state = False 

2339 else: 

2340 consistency_state = True 

2341 util.SMlog( 

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

2343 .format(consistency_state, vdi_uuid) 

2344 ) 

2345 else: 

2346 consistency_state = None 

2347 

2348 if self.vdi_type != vhdutil.VDI_TYPE_VHD: 

2349 raise xs_errors.XenError('Unimplemented') 

2350 

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

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

2353 try: 

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

2355 finally: 

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

2357 

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

2359 util.SMlog( 

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

2361 .format(self.uuid, snap_type) 

2362 ) 

2363 

2364 # 1. Checks... 

2365 if self.hidden: 

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

2367 

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

2369 if depth == -1: 

2370 raise xs_errors.XenError( 

2371 'VDIUnavailable', 

2372 opterr='failed to get VHD depth' 

2373 ) 

2374 elif depth >= vhdutil.MAX_CHAIN_SIZE: 

2375 raise xs_errors.XenError('SnapshotChainTooLong') 

2376 

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

2378 self.sr._linstor.get_device_path(self.uuid) 

2379 

2380 volume_path = self.path 

2381 if not util.pathexists(volume_path): 

2382 raise xs_errors.XenError( 

2383 'EIO', 

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

2385 ) 

2386 

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

2388 base_uuid = util.gen_uuid() 

2389 snap_uuid = None 

2390 

2391 if snap_type == VDI.SNAPSHOT_DOUBLE: 

2392 snap_uuid = util.gen_uuid() 

2393 

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

2395 

2396 active_uuid = self.uuid 

2397 self.sr._journaler.create( 

2398 LinstorJournaler.CLONE, active_uuid, clone_info 

2399 ) 

2400 

2401 try: 

2402 # 3. Self becomes the new base. 

2403 # The device path remains the same. 

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

2405 self.uuid = base_uuid 

2406 self.location = self.uuid 

2407 self.read_only = True 

2408 self.managed = False 

2409 

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

2411 active_vdi = self._create_snapshot(active_uuid) 

2412 

2413 snap_vdi = None 

2414 if snap_type == VDI.SNAPSHOT_DOUBLE: 

2415 snap_vdi = self._create_snapshot(snap_uuid, active_uuid) 

2416 

2417 self.label = 'base copy' 

2418 self.description = '' 

2419 

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

2421 # in subsequent scans. 

2422 self._mark_hidden() 

2423 self._linstor.update_volume_metadata( 

2424 self.uuid, {READ_ONLY_TAG: True} 

2425 ) 

2426 

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

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

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

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

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

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

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

2434 active_vdi.sm_config[key] = sm_config[key] 

2435 

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

2437 # delete base if unused. 

2438 introduce_parent = True 

2439 try: 

2440 snap_parent = None 

2441 if snap_vdi: 

2442 snap_parent = snap_vdi.parent 

2443 

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

2445 snap_type == VDI.SNAPSHOT_SINGLE or 

2446 snap_type == VDI.SNAPSHOT_INTERNAL or 

2447 snap_parent != self.uuid 

2448 ): 

2449 util.SMlog( 

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

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

2452 ) 

2453 introduce_parent = False 

2454 self._linstor.destroy_volume(self.uuid) 

2455 except Exception as e: 

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

2457 pass 

2458 

2459 # 8. Introduce the new VDI records. 

2460 if snap_vdi: 

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

2462 # new snapshot disk. 

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

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

2465 # TODO: Maybe remove key_hash support. 

2466 if 'key_hash' in sm_config: 

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

2468 # If we have CBT enabled on the VDI, 

2469 # set CBT status for the new snapshot disk. 

2470 if cbtlog: 

2471 snap_vdi.cbt_enabled = True 

2472 

2473 if snap_vdi: 

2474 snap_vdi_ref = snap_vdi._db_introduce() 

2475 util.SMlog( 

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

2477 .format(snap_vdi_ref, snap_vdi.uuid) 

2478 ) 

2479 if introduce_parent: 

2480 base_vdi_ref = self._db_introduce() 

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

2482 util.SMlog( 

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

2484 .format(base_vdi_ref, self.uuid) 

2485 ) 

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

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

2488 NAME_DESCRIPTION_TAG: util.to_plain_string( 

2489 self.description 

2490 ), 

2491 READ_ONLY_TAG: True, 

2492 METADATA_OF_POOL_TAG: '' 

2493 }) 

2494 

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

2496 if snap_type == VDI.SNAPSHOT_DOUBLE and cbtlog: 

2497 try: 

2498 self._cbt_snapshot(snap_uuid, cbt_consistency) 

2499 except Exception: 

2500 # CBT operation failed. 

2501 # TODO: Implement me. 

2502 raise 

2503 

2504 if snap_type != VDI.SNAPSHOT_INTERNAL: 

2505 self.sr._update_stats(self.size) 

2506 

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

2508 ret_vdi = snap_vdi 

2509 if not ret_vdi: 

2510 ret_vdi = self 

2511 if not ret_vdi: 

2512 ret_vdi = active_vdi 

2513 

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

2515 self.session.xenapi.VDI.set_sm_config( 

2516 vdi_ref, active_vdi.sm_config 

2517 ) 

2518 except Exception as e: 

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

2520 try: 

2521 self.sr._handle_interrupted_clone( 

2522 active_uuid, clone_info, force_undo=True 

2523 ) 

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

2525 except Exception as e: 

2526 util.SMlog( 

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

2528 .format(e) 

2529 ) 

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

2531 

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

2533 

2534 return ret_vdi.get_params() 

2535 

2536 @staticmethod 

2537 def _start_persistent_http_server(volume_name): 

2538 pid_path = None 

2539 http_server = None 

2540 

2541 try: 

2542 if volume_name == 'xcp-persistent-ha-statefile': 

2543 port = '8076' 

2544 else: 

2545 port = '8077' 

2546 

2547 try: 

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

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

2550 # block indefinitely. 

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

2552 host_ip = util.get_this_host_address(session) 

2553 except: 

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

2555 host_ip, _ = get_ips_from_xha_config_file() 

2556 if not host_ip: 

2557 raise Exception( 

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

2559 ) 

2560 

2561 arguments = [ 

2562 'http-disk-server', 

2563 '--disk', 

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

2565 '--ip', 

2566 host_ip, 

2567 '--port', 

2568 port 

2569 ] 

2570 

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

2572 http_server = subprocess.Popen( 

2573 [FORK_LOG_DAEMON] + arguments, 

2574 stdout=subprocess.PIPE, 

2575 stderr=subprocess.STDOUT, 

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

2577 # touch the current one. 

2578 preexec_fn=os.setsid 

2579 ) 

2580 

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

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

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

2584 

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

2586 def is_ready(): 

2587 while http_server.poll() is None: 

2588 line = http_server.stdout.readline() 

2589 if reg_server_ready.search(line): 

2590 return True 

2591 return False 

2592 try: 

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

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

2595 except util.TimeoutException: 

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

2597 except Exception as e: 

2598 if pid_path: 

2599 try: 

2600 os.remove(pid_path) 

2601 except Exception: 

2602 pass 

2603 

2604 if http_server: 

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

2606 try: 

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

2608 except: 

2609 pass 

2610 

2611 raise xs_errors.XenError( 

2612 'VDIUnavailable', 

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

2614 ) 

2615 

2616 def _start_persistent_nbd_server(self, volume_name): 

2617 pid_path = None 

2618 nbd_path = None 

2619 nbd_server = None 

2620 

2621 try: 

2622 # We use a precomputed device size. 

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

2624 if volume_name == 'xcp-persistent-ha-statefile': 

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

2626 port = '8076' 

2627 device_size = 4 * 1024 * 1024 

2628 else: 

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

2630 port = '8077' 

2631 device_size = 256 * 1024 * 1024 

2632 

2633 try: 

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

2635 ips = util.get_host_addresses(session) 

2636 except Exception as e: 

2637 _, ips = get_ips_from_xha_config_file() 

2638 if not ips: 

2639 raise Exception( 

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

2641 ) 

2642 ips = ips.values() 

2643 

2644 arguments = [ 

2645 'nbd-http-server', 

2646 '--socket-path', 

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

2648 '--nbd-name', 

2649 volume_name, 

2650 '--urls', 

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

2652 '--device-size', 

2653 str(device_size) 

2654 ] 

2655 

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

2657 nbd_server = subprocess.Popen( 

2658 [FORK_LOG_DAEMON] + arguments, 

2659 stdout=subprocess.PIPE, 

2660 stderr=subprocess.STDOUT, 

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

2662 # touch the current one. 

2663 preexec_fn=os.setsid 

2664 ) 

2665 

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

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

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

2669 

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

2671 def get_nbd_path(): 

2672 while nbd_server.poll() is None: 

2673 line = nbd_server.stdout.readline() 

2674 match = reg_nbd_path.search(line) 

2675 if match: 

2676 return match.group(1) 

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

2678 try: 

2679 nbd_path = util.timeout_call(10, get_nbd_path) 

2680 if nbd_path is None: 

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

2682 except util.TimeoutException: 

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

2684 

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

2686 os.symlink(nbd_path, self.path) 

2687 except Exception as e: 

2688 if pid_path: 

2689 try: 

2690 os.remove(pid_path) 

2691 except Exception: 

2692 pass 

2693 

2694 if nbd_path: 

2695 try: 

2696 os.remove(nbd_path) 

2697 except Exception: 

2698 pass 

2699 

2700 if nbd_server: 

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

2702 try: 

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

2704 except: 

2705 pass 

2706 

2707 raise xs_errors.XenError( 

2708 'VDIUnavailable', 

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

2710 ) 

2711 

2712 @classmethod 

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

2714 try: 

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

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

2717 return 

2718 

2719 pid = None 

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

2721 try: 

2722 pid = int(pid_file.read()) 

2723 except Exception: 

2724 pass 

2725 

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

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

2728 try: 

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

2730 except Exception as e: 

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

2732 

2733 os.remove(path) 

2734 except: 

2735 pass 

2736 

2737 @classmethod 

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

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

2740 

2741 @classmethod 

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

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

2744 

2745 def _check_http_nbd_volume_name(self): 

2746 volume_name = self.path[14:] 

2747 if volume_name not in [ 

2748 'xcp-persistent-ha-statefile', 'xcp-persistent-redo-log' 

2749 ]: 

2750 raise xs_errors.XenError( 

2751 'VDIUnavailable', 

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

2753 ) 

2754 return volume_name 

2755 

2756 def _attach_using_http_nbd(self): 

2757 volume_name = self._check_http_nbd_volume_name() 

2758 

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

2760 self._kill_persistent_nbd_server(volume_name) 

2761 self._kill_persistent_http_server(volume_name) 

2762 

2763 # 0. Fetch drbd path. 

2764 must_get_device_path = True 

2765 if not self.sr._is_master: 

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

2767 try: 

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

2769 except Exception as e: 

2770 raise xs_errors.XenError( 

2771 'VDIUnavailable', 

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

2773 .format(self.uuid, e) 

2774 ) 

2775 

2776 hostname = socket.gethostname() 

2777 must_get_device_path = hostname in volume_info.diskful 

2778 

2779 drbd_path = None 

2780 if must_get_device_path or self.sr._is_master: 

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

2782 # or diskful available to init HA. 

2783 # It also avoid this error in xensource.log 

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

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

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

2787 available = False 

2788 try: 

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

2790 available = util.pathexists(drbd_path) 

2791 except Exception: 

2792 pass 

2793 

2794 if not available: 

2795 raise xs_errors.XenError( 

2796 'VDIUnavailable', 

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

2798 ) 

2799 

2800 # 1. Prepare http-nbd folder. 

2801 try: 

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

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

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

2805 os.remove(self.path) 

2806 except OSError as e: 

2807 if e.errno != errno.EEXIST: 

2808 raise xs_errors.XenError( 

2809 'VDIUnavailable', 

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

2811 ) 

2812 

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

2814 http_service = None 

2815 if drbd_path: 

2816 assert(drbd_path in ( 

2817 '/dev/drbd/by-res/xcp-persistent-ha-statefile/0', 

2818 '/dev/drbd/by-res/xcp-persistent-redo-log/0' 

2819 )) 

2820 self._start_persistent_http_server(volume_name) 

2821 

2822 # 3. Start NBD server in all cases. 

2823 try: 

2824 self._start_persistent_nbd_server(volume_name) 

2825 except Exception as e: 

2826 if drbd_path: 

2827 self._kill_persistent_http_server(volume_name) 

2828 raise 

2829 

2830 self.attached = True 

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

2832 

2833 def _detach_using_http_nbd(self): 

2834 volume_name = self._check_http_nbd_volume_name() 

2835 self._kill_persistent_nbd_server(volume_name) 

2836 self._kill_persistent_http_server(volume_name) 

2837 

2838# ------------------------------------------------------------------------------ 

2839 

2840 

2841if __name__ == '__main__': 

2842 def run(): 

2843 SRCommand.run(LinstorSR, DRIVER_INFO) 

2844 

2845 if not TRACE_PERFS: 

2846 run() 

2847 else: 

2848 util.make_profile('LinstorSR', run) 

2849else: 

2850 SR.registerSR(LinstorSR)