Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1#!/usr/bin/env python3 

2# 

3# Copyright (C) 2020 Vates SAS - ronan.abhamon@vates.fr 

4# 

5# This program is free software: you can redistribute it and/or modify 

6# it under the terms of the GNU General Public License as published by 

7# the Free Software Foundation, either version 3 of the License, or 

8# (at your option) any later version. 

9# This program is distributed in the hope that it will be useful, 

10# but WITHOUT ANY WARRANTY; without even the implied warranty of 

11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

12# GNU General Public License for more details. 

13# 

14# You should have received a copy of the GNU General Public License 

15# along with this program. If not, see <https://www.gnu.org/licenses/>. 

16 

17from constants import CBTLOG_TAG 

18 

19try: 

20 from linstorjournaler import LinstorJournaler 

21 from linstorvhdutil import LinstorVhdUtil 

22 from linstorvolumemanager import get_controller_uri 

23 from linstorvolumemanager import get_controller_node_name 

24 from linstorvolumemanager import LinstorVolumeManager 

25 from linstorvolumemanager import LinstorVolumeManagerError 

26 from linstorvolumemanager import PERSISTENT_PREFIX 

27 

28 LINSTOR_AVAILABLE = True 

29except ImportError: 

30 PERSISTENT_PREFIX = 'unknown' 

31 

32 LINSTOR_AVAILABLE = False 

33 

34from lock import Lock, LOCK_TYPE_GC_RUNNING 

35import blktap2 

36import cleanup 

37import distutils 

38import errno 

39import functools 

40import lvutil 

41import os 

42import re 

43import scsiutil 

44import signal 

45import socket 

46import SR 

47import SRCommand 

48import subprocess 

49import time 

50import traceback 

51import util 

52import VDI 

53import vhdutil 

54import xml.etree.ElementTree as xml_parser 

55import xmlrpc.client 

56import xs_errors 

57 

58from srmetadata import \ 

59 NAME_LABEL_TAG, NAME_DESCRIPTION_TAG, IS_A_SNAPSHOT_TAG, SNAPSHOT_OF_TAG, \ 

60 TYPE_TAG, VDI_TYPE_TAG, READ_ONLY_TAG, SNAPSHOT_TIME_TAG, \ 

61 METADATA_OF_POOL_TAG 

62 

63HIDDEN_TAG = 'hidden' 

64 

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

66 

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

68 

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

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

71# specific conditions: 

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

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

74USE_HTTP_NBD_SERVERS = True 

75 

76# Useful flag to trace calls using cProfile. 

77TRACE_PERFS = False 

78 

79# Enable/Disable VHD key hash support. 

80USE_KEY_HASH = False 

81 

82# Special volumes. 

83HA_VOLUME_NAME = PERSISTENT_PREFIX + 'ha-statefile' 

84REDO_LOG_VOLUME_NAME = PERSISTENT_PREFIX + 'redo-log' 

85 

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

87 

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

89# 'VDI_CONFIG_CBT', 'SR_PROBE' 

90 

91CAPABILITIES = [ 

92 'ATOMIC_PAUSE', 

93 'SR_UPDATE', 

94 'VDI_CREATE', 

95 'VDI_DELETE', 

96 'VDI_UPDATE', 

97 'VDI_ATTACH', 

98 'VDI_DETACH', 

99 'VDI_ACTIVATE', 

100 'VDI_DEACTIVATE', 

101 'VDI_CLONE', 

102 'VDI_MIRROR', 

103 'VDI_RESIZE', 

104 'VDI_SNAPSHOT', 

105 'VDI_GENERATE_CONFIG' 

106] 

107 

108CONFIGURATION = [ 

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

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

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

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

113] 

114 

115DRIVER_INFO = { 

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

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

118 'vendor': 'Vates', 

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

120 'driver_version': '1.0', 

121 'required_api_version': '1.0', 

122 'capabilities': CAPABILITIES, 

123 'configuration': CONFIGURATION 

124} 

125 

126DRIVER_CONFIG = {'ATTACH_FROM_CONFIG_WITH_TAPDISK': False} 

127 

128OPS_EXCLUSIVE = [ 

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

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

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

132] 

133 

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

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

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

137 

138 

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

140 volume_metadata = linstor.get_volume_metadata(vdi_uuid) 

141 image_type = volume_metadata.get(VDI_TYPE_TAG) 

142 if image_type == vhdutil.VDI_TYPE_RAW: 

143 return 

144 

145 device_path = linstor.get_device_path(vdi_uuid) 

146 

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

148 # there is nothing to do. 

149 vhd_size = LinstorVhdUtil.compute_volume_size( 

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

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

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

153 image_type 

154 ) 

155 

156 volume_info = linstor.get_volume_info(vdi_uuid) 

157 volume_size = volume_info.virtual_size 

158 

159 if vhd_size > volume_size: 

160 LinstorVhdUtil(session, linstor).inflate( 

161 journaler, vdi_uuid, device_path, vhd_size, volume_size 

162 ) 

163 

164 

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

166 volume_metadata = linstor.get_volume_metadata(vdi_uuid) 

167 image_type = volume_metadata.get(VDI_TYPE_TAG) 

168 if image_type == vhdutil.VDI_TYPE_RAW: 

169 return 

170 

171 def check_vbd_count(): 

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

173 vbds = session.xenapi.VBD.get_all_records_where( 

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

175 ) 

176 

177 num_plugged = 0 

178 for vbd_rec in vbds.values(): 

179 if vbd_rec['currently_attached']: 

180 num_plugged += 1 

181 if num_plugged > 1: 

182 raise xs_errors.XenError( 

183 'VDIUnavailable', 

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

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

186 ) 

187 

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

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

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

191 

192 device_path = linstor.get_device_path(vdi_uuid) 

193 vhdutil_inst = LinstorVhdUtil(session, linstor) 

194 new_volume_size = LinstorVolumeManager.round_up_volume_size( 

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

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

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

198 ) 

199 

200 volume_info = linstor.get_volume_info(vdi_uuid) 

201 old_volume_size = volume_info.virtual_size 

202 vhdutil_inst.deflate(device_path, new_volume_size, old_volume_size) 

203 

204 

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

206 # This function must always return without errors. 

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

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

209 try: 

210 detach_thin_impl(session, linstor, sr_uuid, vdi_uuid) 

211 except Exception as e: 

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

213 

214 

215def get_ips_from_xha_config_file(): 

216 ips = dict() 

217 host_id = None 

218 try: 

219 # Ensure there is no dirty read problem. 

220 # For example if the HA is reloaded. 

221 tree = util.retry( 

222 lambda: xml_parser.parse(XHA_CONFIG_PATH), 

223 maxretry=10, 

224 period=1 

225 ) 

226 except: 

227 return (None, ips) 

228 

229 def parse_host_nodes(ips, node): 

230 current_id = None 

231 current_ip = None 

232 

233 for sub_node in node: 

234 if sub_node.tag == 'IPaddress': 

235 current_ip = sub_node.text 

236 elif sub_node.tag == 'HostID': 

237 current_id = sub_node.text 

238 else: 

239 continue 

240 

241 if current_id and current_ip: 

242 ips[current_id] = current_ip 

243 return 

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

245 

246 def parse_common_config(ips, node): 

247 for sub_node in node: 

248 if sub_node.tag == 'host': 

249 parse_host_nodes(ips, sub_node) 

250 

251 def parse_local_config(ips, node): 

252 for sub_node in node: 

253 if sub_node.tag == 'localhost': 

254 for host_node in sub_node: 

255 if host_node.tag == 'HostID': 

256 return host_node.text 

257 

258 for node in tree.getroot(): 

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

260 parse_common_config(ips, node) 

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

262 host_id = parse_local_config(ips, node) 

263 else: 

264 continue 

265 

266 if ips and host_id: 

267 break 

268 

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

270 

271 

272def activate_lvm_group(group_name): 

273 path = group_name.split('/') 

274 assert path and len(path) <= 2 

275 try: 

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

277 except Exception as e: 

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

279 

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

281 

282# Usage example: 

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

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

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

286 

287 

288class LinstorSR(SR.SR): 

289 DRIVER_TYPE = 'linstor' 

290 

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

292 PROVISIONING_DEFAULT = 'thin' 

293 

294 MANAGER_PLUGIN = 'linstor-manager' 

295 

296 INIT_STATUS_NOT_SET = 0 

297 INIT_STATUS_IN_PROGRESS = 1 

298 INIT_STATUS_OK = 2 

299 INIT_STATUS_FAIL = 3 

300 

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

302 # SR methods. 

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

304 

305 @staticmethod 

306 def handles(type): 

307 return type == LinstorSR.DRIVER_TYPE 

308 

309 def load(self, sr_uuid): 

310 if not LINSTOR_AVAILABLE: 

311 raise util.SMException( 

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

313 ) 

314 

315 # Check parameters. 

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

317 raise xs_errors.XenError('LinstorConfigGroupNameMissing') 

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

319 raise xs_errors.XenError('LinstorConfigRedundancyMissing') 

320 

321 self.driver_config = DRIVER_CONFIG 

322 

323 # Check provisioning config. 

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

325 if provisioning: 

326 if provisioning in self.PROVISIONING_TYPES: 

327 self._provisioning = provisioning 

328 else: 

329 raise xs_errors.XenError( 

330 'InvalidArg', 

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

332 self.PROVISIONING_TYPES 

333 ) 

334 ) 

335 else: 

336 self._provisioning = self.PROVISIONING_DEFAULT 

337 

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

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

340 distutils.util.strtobool(monitor_db_quorum) 

341 

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

343 # 'vdi_attach_from_config' command is executed. 

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

345 if self._has_session: 

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

347 else: 

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

349 

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

351 if provisioning in self.PROVISIONING_TYPES: 

352 self._provisioning = provisioning 

353 

354 # Define properties for SR parent class. 

355 self.ops_exclusive = OPS_EXCLUSIVE 

356 self.path = LinstorVolumeManager.DEV_ROOT_PATH 

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

358 self.sr_vditype = SR.DEFAULT_TAP 

359 

360 if self.cmd == 'sr_create': 

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

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

363 self._journaler = None 

364 

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

366 

367 self._vdi_shared_time = 0 

368 

369 self._init_status = self.INIT_STATUS_NOT_SET 

370 

371 self._vdis_loaded = False 

372 self._all_volume_info_cache = None 

373 self._all_volume_metadata_cache = None 

374 

375 def _locked_load(method): 

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

377 self._init_status = self.INIT_STATUS_OK 

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

379 

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

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

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

383 activate_lvm_group(self._group_name) 

384 

385 if not self._has_session: 

386 if self.srcmd.cmd in ( 

387 'vdi_attach_from_config', 

388 'vdi_detach_from_config', 

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

390 # empty command. 

391 None 

392 ): 

393 def create_linstor(uri, attempt_count=30): 

394 self._linstor = LinstorVolumeManager( 

395 uri, 

396 self._group_name, 

397 logger=util.SMlog, 

398 attempt_count=attempt_count 

399 ) 

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

401 # I.e. not an HA volume. 

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

403 

404 controller_uri = get_controller_uri() 

405 if controller_uri: 

406 create_linstor(controller_uri) 

407 else: 

408 def connect(): 

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

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

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

412 controller_uri = 'linstor://' + ip 

413 try: 

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

415 create_linstor(controller_uri, attempt_count=0) 

416 return controller_uri 

417 except: 

418 pass 

419 

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

421 if not controller_uri: 

422 raise xs_errors.XenError( 

423 'SRUnavailable', 

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

425 ) 

426 

427 self._journaler = LinstorJournaler( 

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

429 ) 

430 

431 if self.srcmd.cmd is None: 

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

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

434 

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

436 

437 if not self.is_master(): 

438 if self.cmd in [ 

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

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

441 'vdi_snapshot', 'vdi_clone' 

442 ]: 

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

444 raise xs_errors.XenError('LinstorMaster') 

445 

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

447 # the VDI before the LinstorJournaler/LinstorVolumeManager 

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

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

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

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

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

453 self._vdi_shared_time = time.time() 

454 

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

456 try: 

457 self._reconnect() 

458 except Exception as e: 

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

460 

461 if self._linstor: 

462 try: 

463 hosts = self._linstor.disconnected_hosts 

464 except Exception as e: 

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

466 

467 if hosts: 

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

469 

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

471 if ( 

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

473 self.cmd != 'vdi_create' 

474 ): 

475 self._linstor.ensure_volume_is_not_locked( 

476 self.srcmd.params['vdi_uuid'] 

477 ) 

478 

479 try: 

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

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

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

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

484 # 

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

486 # resourceless volumes. 

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

488 'vdi_attach', 'vdi_detach', 

489 'vdi_activate', 'vdi_deactivate', 

490 'vdi_epoch_begin', 'vdi_epoch_end', 

491 'vdi_update', 'vdi_destroy' 

492 ]: 

493 load_vdis = ( 

494 self.cmd == 'sr_scan' or 

495 self.cmd == 'sr_attach' 

496 ) or len( 

497 self._journaler.get_all(LinstorJournaler.INFLATE) 

498 ) or len( 

499 self._journaler.get_all(LinstorJournaler.CLONE) 

500 ) 

501 

502 if load_vdis: 

503 self._load_vdis() 

504 

505 self._linstor.remove_resourceless_volumes() 

506 

507 self._synchronize_metadata() 

508 except Exception as e: 

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

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

511 # from the XAPI database otherwise. 

512 raise e 

513 util.SMlog( 

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

515 ) 

516 util.SMlog(traceback.format_exc()) 

517 

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

519 

520 @functools.wraps(wrapped_method) 

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

522 if self._init_status in \ 

523 (self.INIT_STATUS_OK, self.INIT_STATUS_IN_PROGRESS): 

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

525 if self._init_status == self.INIT_STATUS_FAIL: 

526 util.SMlog( 

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

528 .format(method) 

529 ) 

530 else: 

531 try: 

532 self._init_status = self.INIT_STATUS_IN_PROGRESS 

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

534 except Exception: 

535 if self._init_status != self.INIT_STATUS_OK: 

536 self._init_status = self.INIT_STATUS_FAIL 

537 raise 

538 

539 return wrap 

540 

541 def cleanup(self): 

542 if self._vdi_shared_time: 

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

544 

545 @_locked_load 

546 def create(self, uuid, size): 

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

548 

549 host_adresses = util.get_host_addresses(self.session) 

550 if self._redundancy > len(host_adresses): 

551 raise xs_errors.XenError( 

552 'LinstorSRCreate', 

553 opterr='Redundancy greater than host count' 

554 ) 

555 

556 xenapi = self.session.xenapi 

557 srs = xenapi.SR.get_all_records_where( 

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

559 ) 

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

561 

562 for sr in srs.values(): 

563 for pbd in sr['PBDs']: 

564 device_config = xenapi.PBD.get_device_config(pbd) 

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

566 if group_name and group_name == self._group_name: 

567 raise xs_errors.XenError( 

568 'LinstorSRCreate', 

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

570 xenapi.PBD.get_uuid(pbd) 

571 ) 

572 ) 

573 

574 if srs: 

575 raise xs_errors.XenError( 

576 'LinstorSRCreate', 

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

578 ) 

579 

580 online_hosts = util.get_online_hosts(self.session) 

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

582 raise xs_errors.XenError( 

583 'LinstorSRCreate', 

584 opterr='Not enough online hosts' 

585 ) 

586 

587 ips = {} 

588 for host_ref in online_hosts: 

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

590 hostname = record['hostname'] 

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

592 

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

594 raise xs_errors.XenError( 

595 'LinstorSRCreate', 

596 opterr='Multiple hosts with same hostname' 

597 ) 

598 

599 # Ensure ports are opened and LINSTOR satellites 

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

601 # must be stopped. 

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

603 

604 # Create SR. 

605 # Throw if the SR already exists. 

606 try: 

607 self._linstor = LinstorVolumeManager.create_sr( 

608 self._group_name, 

609 ips, 

610 self._redundancy, 

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

612 auto_quorum=self._monitor_db_quorum, 

613 logger=util.SMlog 

614 ) 

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

616 except Exception as e: 

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

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

619 

620 try: 

621 util.SMlog( 

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

623 ) 

624 self._update_drbd_reactor_on_all_hosts(enabled=True) 

625 except Exception as e: 

626 try: 

627 self._linstor.destroy() 

628 except Exception as e2: 

629 util.SMlog( 

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

631 .format(e2) 

632 ) 

633 raise e 

634 

635 @_locked_load 

636 def delete(self, uuid): 

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

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

639 

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

641 raise xs_errors.XenError('SRNotEmpty') 

642 

643 node_name = get_controller_node_name() 

644 if not node_name: 

645 raise xs_errors.XenError( 

646 'LinstorSRDelete', 

647 opterr='Cannot get controller node name' 

648 ) 

649 

650 host_ref = None 

651 if node_name == 'localhost': 

652 host_ref = util.get_this_host_ref(self.session) 

653 else: 

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

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

656 if r_name == node_name: 

657 host_ref = slave 

658 break 

659 

660 if not host_ref: 

661 raise xs_errors.XenError( 

662 'LinstorSRDelete', 

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

664 node_name 

665 ) 

666 ) 

667 

668 try: 

669 self._update_drbd_reactor_on_all_hosts( 

670 controller_node_name=node_name, enabled=False 

671 ) 

672 

673 args = { 

674 'groupName': self._group_name, 

675 } 

676 self._exec_manager_command( 

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

678 ) 

679 except Exception as e: 

680 try: 

681 self._update_drbd_reactor_on_all_hosts( 

682 controller_node_name=node_name, enabled=True 

683 ) 

684 except Exception as e2: 

685 util.SMlog( 

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

687 .format(e2) 

688 ) 

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

690 raise xs_errors.XenError( 

691 'LinstorSRDelete', 

692 opterr=str(e) 

693 ) 

694 

695 Lock.cleanupAll(self.uuid) 

696 

697 @_locked_load 

698 def update(self, uuid): 

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

700 

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

702 if not self._linstor: 

703 raise xs_errors.XenError( 

704 'SRUnavailable', 

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

706 ) 

707 

708 self._update_stats(0) 

709 

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

711 xenapi = self.session.xenapi 

712 self._linstor.metadata = { 

713 NAME_LABEL_TAG: util.to_plain_string( 

714 xenapi.SR.get_name_label(self.sr_ref) 

715 ), 

716 NAME_DESCRIPTION_TAG: util.to_plain_string( 

717 xenapi.SR.get_name_description(self.sr_ref) 

718 ) 

719 } 

720 

721 @_locked_load 

722 def attach(self, uuid): 

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

724 

725 if not self._linstor: 

726 raise xs_errors.XenError( 

727 'SRUnavailable', 

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

729 ) 

730 

731 @_locked_load 

732 def detach(self, uuid): 

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

734 cleanup.abort(self.uuid) 

735 

736 @_locked_load 

737 def probe(self): 

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

739 # TODO 

740 

741 @_locked_load 

742 def scan(self, uuid): 

743 if self._init_status == self.INIT_STATUS_FAIL: 

744 return 

745 

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

747 if not self._linstor: 

748 raise xs_errors.XenError( 

749 'SRUnavailable', 

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

751 ) 

752 

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

754 # are loaded. 

755 self._load_vdis() 

756 self._update_physical_size() 

757 

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

759 if self.vdis[vdi_uuid].deleted: 

760 del self.vdis[vdi_uuid] 

761 

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

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

764 try: 

765 self._linstor.get_database_path() 

766 except Exception as e: 

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

768 # VDIs in the XAPI database... 

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

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

771 ): 

772 raise xs_errors.XenError( 

773 'SRUnavailable', 

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

775 ) 

776 

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

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

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

780 self._kick_gc() 

781 

782 def is_master(self): 

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

784 if 'SRmaster' not in self.dconf: 

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

786 else: 

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

788 

789 return self._is_master 

790 

791 @_locked_load 

792 def vdi(self, uuid): 

793 return LinstorVDI(self, uuid) 

794 

795 _locked_load = staticmethod(_locked_load) 

796 

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

798 # Lock. 

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

800 

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

802 master = util.get_master_ref(self.session) 

803 

804 command = 'lockVdi' 

805 args = { 

806 'groupName': self._group_name, 

807 'srUuid': self.uuid, 

808 'vdiUuid': vdi_uuid, 

809 'locked': str(locked) 

810 } 

811 

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

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

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

815 # 

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

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

818 # to implement without impacting performance. 

819 if not locked: 

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

821 timeout = LinstorVolumeManager.LOCKED_EXPIRATION_DELAY * 0.7 

822 if elapsed_time >= timeout: 

823 util.SMlog( 

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

825 .format(vdi_uuid) 

826 ) 

827 return 

828 

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

830 

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

832 # Network. 

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

834 

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

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

837 host_uuid = host_rec['uuid'] 

838 

839 try: 

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

841 host_ref, self.MANAGER_PLUGIN, command, args 

842 ) 

843 except Exception as e: 

844 util.SMlog( 

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

846 host_uuid, self.MANAGER_PLUGIN, command, args 

847 ) 

848 ) 

849 raise e 

850 

851 util.SMlog( 

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

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

854 ) 

855 ) 

856 if ret == 'False': 

857 raise xs_errors.XenError( 

858 error, 

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

860 ) 

861 

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

863 self._exec_manager_command( 

864 host, 

865 'prepareSr' if enabled else 'releaseSr', 

866 {'groupName': group_name}, 

867 'SRUnavailable' 

868 ) 

869 

870 def _prepare_sr_on_all_hosts(self, group_name, enabled): 

871 master = util.get_master_ref(self.session) 

872 self._prepare_sr(master, group_name, enabled) 

873 

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

875 self._prepare_sr(slave, group_name, enabled) 

876 

877 def _update_drbd_reactor(self, host, enabled): 

878 self._exec_manager_command( 

879 host, 

880 'updateDrbdReactor', 

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

882 'SRUnavailable' 

883 ) 

884 

885 def _update_drbd_reactor_on_all_hosts( 

886 self, enabled, controller_node_name=None 

887 ): 

888 if controller_node_name == 'localhost': 

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

890 util.get_this_host_ref(self.session) 

891 )['hostname'] 

892 assert controller_node_name 

893 assert controller_node_name != 'localhost' 

894 

895 controller_host = None 

896 secondary_hosts = [] 

897 

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

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

900 hostname = host_rec['hostname'] 

901 if controller_node_name == hostname: 

902 controller_host = host_ref 

903 else: 

904 secondary_hosts.append((host_ref, hostname)) 

905 

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

907 if controller_node_name and not controller_host: 

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

909 controller_node_name 

910 )) 

911 

912 if enabled and controller_host: 

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

914 action_name, controller_node_name 

915 )) 

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

917 # node name first. 

918 self._update_drbd_reactor(controller_host, enabled) 

919 

920 for host_ref, hostname in secondary_hosts: 

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

922 action_name, hostname 

923 )) 

924 self._update_drbd_reactor(host_ref, enabled) 

925 

926 if not enabled and controller_host: 

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

928 action_name, controller_node_name 

929 )) 

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

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

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

933 self._update_drbd_reactor(controller_host, enabled) 

934 

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

936 # Metadata. 

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

938 

939 def _synchronize_metadata_and_xapi(self): 

940 try: 

941 # First synch SR parameters. 

942 self.update(self.uuid) 

943 

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

945 xenapi = self.session.xenapi 

946 volumes_metadata = self._linstor.get_volumes_with_metadata() 

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

948 try: 

949 vdi_ref = xenapi.VDI.get_by_uuid(vdi_uuid) 

950 except Exception: 

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

952 continue 

953 

954 label = util.to_plain_string( 

955 xenapi.VDI.get_name_label(vdi_ref) 

956 ) 

957 description = util.to_plain_string( 

958 xenapi.VDI.get_name_description(vdi_ref) 

959 ) 

960 

961 if ( 

962 volume_metadata.get(NAME_LABEL_TAG) != label or 

963 volume_metadata.get(NAME_DESCRIPTION_TAG) != description 

964 ): 

965 self._linstor.update_volume_metadata(vdi_uuid, { 

966 NAME_LABEL_TAG: label, 

967 NAME_DESCRIPTION_TAG: description 

968 }) 

969 except Exception as e: 

970 raise xs_errors.XenError( 

971 'MetadataError', 

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

973 ) 

974 

975 def _synchronize_metadata(self): 

976 if not self.is_master(): 

977 return 

978 

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

980 if self.cmd == 'sr_attach': 

981 try: 

982 util.SMlog( 

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

984 ) 

985 self._synchronize_metadata_and_xapi() 

986 except Exception as e: 

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

988 

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

990 # Stats. 

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

992 

993 def _update_stats(self, virt_alloc_delta): 

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

995 self.sr_ref 

996 )) 

997 

998 # Update size attributes of the SR parent class. 

999 self.virtual_allocation = valloc + virt_alloc_delta 

1000 

1001 self._update_physical_size() 

1002 

1003 # Notify SR parent class. 

1004 self._db_update() 

1005 

1006 def _update_physical_size(self): 

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

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

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

1010 self.physical_size = min_physical_size * pool_count // \ 

1011 self._linstor.redundancy 

1012 

1013 self.physical_utilisation = self._linstor.allocated_volume_size 

1014 

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

1016 # VDIs. 

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

1018 

1019 def _load_vdis(self): 

1020 if self._vdis_loaded: 

1021 return 

1022 

1023 assert self.is_master() 

1024 

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

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

1027 # enjoy it with a few lines. 

1028 self._create_linstor_cache() 

1029 self._load_vdis_ex() 

1030 self._destroy_linstor_cache() 

1031 

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

1033 self._vdis_loaded = True 

1034 

1035 self._undo_all_journal_transactions() 

1036 

1037 def _load_vdis_ex(self): 

1038 # 1. Get existing VDIs in XAPI. 

1039 xenapi = self.session.xenapi 

1040 xapi_vdi_uuids = set() 

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

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

1043 

1044 # 2. Get volumes info. 

1045 all_volume_info = self._all_volume_info_cache 

1046 volumes_metadata = self._all_volume_metadata_cache 

1047 

1048 # 3. Get CBT vdis. 

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

1050 cbt_vdis = set() 

1051 for volume_metadata in volumes_metadata.values(): 

1052 cbt_uuid = volume_metadata.get(CBTLOG_TAG) 

1053 if cbt_uuid: 

1054 cbt_vdis.add(cbt_uuid) 

1055 

1056 introduce = False 

1057 

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

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

1060 has_clone_entries = list(self._journaler.get_all( 

1061 LinstorJournaler.CLONE 

1062 ).items()) 

1063 

1064 if has_clone_entries: 

1065 util.SMlog( 

1066 'Cannot introduce VDIs during scan because it exists ' 

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

1068 ) 

1069 else: 

1070 introduce = True 

1071 

1072 # 4. Now check all volume info. 

1073 vdi_to_snaps = {} 

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

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

1076 continue 

1077 

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

1079 if vdi_uuid not in xapi_vdi_uuids: 

1080 if not introduce: 

1081 continue 

1082 

1083 if vdi_uuid.startswith('DELETED_'): 

1084 continue 

1085 

1086 volume_metadata = volumes_metadata.get(vdi_uuid) 

1087 if not volume_metadata: 

1088 util.SMlog( 

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

1090 .format(vdi_uuid) 

1091 ) 

1092 continue 

1093 

1094 util.SMlog( 

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

1096 'LINSTOR and not in XAPI...' 

1097 .format(vdi_uuid) 

1098 ) 

1099 

1100 try: 

1101 self._linstor.get_device_path(vdi_uuid) 

1102 except Exception as e: 

1103 util.SMlog( 

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

1105 .format(vdi_uuid, e) 

1106 ) 

1107 continue 

1108 

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

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

1111 vdi_type = volume_metadata.get(VDI_TYPE_TAG) 

1112 

1113 if not vdi_type: 

1114 util.SMlog( 

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

1116 'without vdi_type' 

1117 ) 

1118 continue 

1119 

1120 sm_config = { 

1121 'vdi_type': vdi_type 

1122 } 

1123 

1124 if vdi_type == vhdutil.VDI_TYPE_RAW: 

1125 managed = not volume_metadata.get(HIDDEN_TAG) 

1126 elif vdi_type == vhdutil.VDI_TYPE_VHD: 

1127 vhd_info = self._vhdutil.get_vhd_info(vdi_uuid) 

1128 managed = not vhd_info.hidden 

1129 if vhd_info.parentUuid: 

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

1131 else: 

1132 util.SMlog( 

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

1134 .format(vdi_uuid, vdi_type) 

1135 ) 

1136 continue 

1137 

1138 util.SMlog( 

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

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

1141 name_label, 

1142 volume_info.virtual_size, 

1143 volume_info.allocated_size 

1144 ) 

1145 ) 

1146 

1147 vdi_ref = xenapi.VDI.db_introduce( 

1148 vdi_uuid, 

1149 name_label, 

1150 volume_metadata.get(NAME_DESCRIPTION_TAG) or '', 

1151 self.sr_ref, 

1152 type, 

1153 False, # sharable 

1154 bool(volume_metadata.get(READ_ONLY_TAG)), 

1155 {}, # other_config 

1156 vdi_uuid, # location 

1157 {}, # xenstore_data 

1158 sm_config, 

1159 managed, 

1160 str(volume_info.virtual_size), 

1161 str(volume_info.allocated_size) 

1162 ) 

1163 

1164 is_a_snapshot = volume_metadata.get(IS_A_SNAPSHOT_TAG) 

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

1166 if is_a_snapshot: 

1167 xenapi.VDI.set_snapshot_time( 

1168 vdi_ref, 

1169 xmlrpc.client.DateTime( 

1170 volume_metadata[SNAPSHOT_TIME_TAG] or 

1171 '19700101T00:00:00Z' 

1172 ) 

1173 ) 

1174 

1175 snap_uuid = volume_metadata[SNAPSHOT_OF_TAG] 

1176 if snap_uuid in vdi_to_snaps: 

1177 vdi_to_snaps[snap_uuid].append(vdi_uuid) 

1178 else: 

1179 vdi_to_snaps[snap_uuid] = [vdi_uuid] 

1180 

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

1182 vdi = self.vdi(vdi_uuid) 

1183 self.vdis[vdi_uuid] = vdi 

1184 

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

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

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

1188 vdi.sm_config_override['key_hash'] = \ 

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

1190 

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

1192 # or already in XAPI. 

1193 cbt_uuid = volume_metadata.get(CBTLOG_TAG) 

1194 if cbt_uuid in cbt_vdis: 

1195 vdi_ref = xenapi.VDI.get_by_uuid(vdi_uuid) 

1196 xenapi.VDI.set_cbt_enabled(vdi_ref, True) 

1197 # For existing VDIs, update local state too. 

1198 # Scan in base class SR updates existing VDIs 

1199 # again based on local states. 

1200 self.vdis[vdi_uuid].cbt_enabled = True 

1201 cbt_vdis.remove(cbt_uuid) 

1202 

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

1204 for src_uuid in vdi_to_snaps: 

1205 try: 

1206 src_ref = xenapi.VDI.get_by_uuid(src_uuid) 

1207 except Exception: 

1208 # The source VDI no longer exists, continue. 

1209 continue 

1210 

1211 for snap_uuid in vdi_to_snaps[src_uuid]: 

1212 try: 

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

1214 snap_ref = xenapi.VDI.get_by_uuid(snap_uuid) 

1215 xenapi.VDI.set_snapshot_of(snap_ref, src_ref) 

1216 except Exception as e: 

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

1218 

1219 # TODO: Check correctly how to use CBT. 

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

1221 

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

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

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

1225 for cbt_uuid in cbt_vdis: 

1226 new_vdi = self.vdi(cbt_uuid) 

1227 new_vdi.ty = 'cbt_metadata' 

1228 new_vdi.cbt_enabled = True 

1229 self.vdis[cbt_uuid] = new_vdi 

1230 

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

1232 self.virtual_allocation = 0 

1233 

1234 # 8. Build geneology. 

1235 geneology = {} 

1236 

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

1238 if vdi.parent: 

1239 if vdi.parent in self.vdis: 

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

1241 if vdi.parent in geneology: 

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

1243 else: 

1244 geneology[vdi.parent] = [vdi_uuid] 

1245 if not vdi.hidden: 

1246 self.virtual_allocation += vdi.size 

1247 

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

1249 # will be GC'ed. 

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

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

1252 util.SMlog( 

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

1254 ) 

1255 del self.vdis[vdi_uuid] 

1256 

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

1258 # Journals. 

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

1260 

1261 def _get_vdi_path_and_parent(self, vdi_uuid, volume_name): 

1262 try: 

1263 device_path = self._linstor.build_device_path(volume_name) 

1264 if not util.pathexists(device_path): 

1265 return (None, None) 

1266 

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

1268 volume_metadata = self._linstor.get_volume_metadata(vdi_uuid) 

1269 vdi_type = volume_metadata[VDI_TYPE_TAG] 

1270 if vdi_type == vhdutil.VDI_TYPE_RAW: 

1271 return (device_path, None) 

1272 

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

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

1275 return (None, None) 

1276 

1277 vhd_info = self._vhdutil.get_vhd_info(vdi_uuid) 

1278 if vhd_info: 

1279 return (device_path, vhd_info.parentUuid) 

1280 except Exception as e: 

1281 util.SMlog( 

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

1283 .format(e) 

1284 ) 

1285 return (None, None) 

1286 

1287 def _undo_all_journal_transactions(self): 

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

1289 self.lock.acquire() 

1290 try: 

1291 self._handle_interrupted_inflate_ops() 

1292 self._handle_interrupted_clone_ops() 

1293 pass 

1294 finally: 

1295 self.lock.release() 

1296 

1297 def _handle_interrupted_inflate_ops(self): 

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

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

1300 self._handle_interrupted_inflate(vdi_uuid, old_size) 

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

1302 

1303 def _handle_interrupted_clone_ops(self): 

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

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

1306 self._handle_interrupted_clone(vdi_uuid, old_size) 

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

1308 

1309 def _handle_interrupted_inflate(self, vdi_uuid, old_size): 

1310 util.SMlog( 

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

1312 .format(vdi_uuid, old_size) 

1313 ) 

1314 

1315 vdi = self.vdis.get(vdi_uuid) 

1316 if not vdi: 

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

1318 return 

1319 

1320 assert not self._all_volume_info_cache 

1321 volume_info = self._linstor.get_volume_info(vdi_uuid) 

1322 

1323 current_size = volume_info.virtual_size 

1324 assert current_size > 0 

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

1326 

1327 def _handle_interrupted_clone( 

1328 self, vdi_uuid, clone_info, force_undo=False 

1329 ): 

1330 util.SMlog( 

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

1332 .format(vdi_uuid, clone_info) 

1333 ) 

1334 

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

1336 

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

1338 volume_names = self._linstor.get_volumes_with_name() 

1339 

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

1341 if base_uuid not in volume_names: 

1342 if vdi_uuid in volume_names: 

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

1344 return 

1345 raise util.SMException( 

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

1347 .format(base_uuid, vdi_uuid) 

1348 ) 

1349 

1350 if force_undo: 

1351 util.SMlog('Explicit revert') 

1352 self._undo_clone( 

1353 volume_names, vdi_uuid, base_uuid, snap_uuid 

1354 ) 

1355 return 

1356 

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

1358 if vdi_uuid not in volume_names or \ 

1359 (snap_uuid and snap_uuid not in volume_names): 

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

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

1362 return 

1363 

1364 vdi_path, vdi_parent_uuid = self._get_vdi_path_and_parent( 

1365 vdi_uuid, volume_names[vdi_uuid] 

1366 ) 

1367 snap_path, snap_parent_uuid = self._get_vdi_path_and_parent( 

1368 snap_uuid, volume_names[snap_uuid] 

1369 ) 

1370 

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

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

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

1374 return 

1375 

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

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

1378 

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

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

1381 base_metadata = self._linstor.get_volume_metadata(base_uuid) 

1382 base_type = base_metadata[VDI_TYPE_TAG] 

1383 

1384 if not util.pathexists(base_path): 

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

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

1387 return 

1388 

1389 # Un-hide the parent. 

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

1391 if base_type == vhdutil.VDI_TYPE_VHD: 

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

1393 if vhd_info.hidden: 

1394 self._vhdutil.set_hidden(base_path, False) 

1395 elif base_type == vhdutil.VDI_TYPE_RAW and \ 

1396 base_metadata.get(HIDDEN_TAG): 

1397 self._linstor.update_volume_metadata( 

1398 base_uuid, {HIDDEN_TAG: False} 

1399 ) 

1400 

1401 # Remove the child nodes. 

1402 if snap_uuid and snap_uuid in volume_names: 

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

1404 

1405 try: 

1406 self._linstor.destroy_volume(snap_uuid) 

1407 except Exception as e: 

1408 util.SMlog( 

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

1410 .format(snap_uuid, e) 

1411 ) 

1412 

1413 if vdi_uuid in volume_names: 

1414 try: 

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

1416 self._linstor.destroy_volume(vdi_uuid) 

1417 except Exception as e: 

1418 util.SMlog( 

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

1420 .format(vdi_uuid, e) 

1421 ) 

1422 # We can get an exception like this: 

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

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

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

1426 # UUID of this bad VDI before. 

1427 self._linstor.update_volume_uuid( 

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

1429 ) 

1430 

1431 # Rename! 

1432 self._linstor.update_volume_uuid(base_uuid, vdi_uuid) 

1433 

1434 # Inflate to the right size. 

1435 if base_type == vhdutil.VDI_TYPE_VHD: 

1436 vdi = self.vdi(vdi_uuid) 

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

1438 self._vhdutil.inflate( 

1439 self._journaler, vdi_uuid, vdi.path, 

1440 volume_size, vdi.capacity 

1441 ) 

1442 self.vdis[vdi_uuid] = vdi 

1443 

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

1445 # flag to facilitate vm deactivate. 

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

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

1448 

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

1450 

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

1452 # Cache. 

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

1454 

1455 def _create_linstor_cache(self): 

1456 reconnect = False 

1457 

1458 def create_cache(): 

1459 nonlocal reconnect 

1460 try: 

1461 if reconnect: 

1462 self._reconnect() 

1463 return self._linstor.get_volumes_with_info() 

1464 except Exception as e: 

1465 reconnect = True 

1466 raise e 

1467 

1468 self._all_volume_metadata_cache = \ 

1469 self._linstor.get_volumes_with_metadata() 

1470 self._all_volume_info_cache = util.retry( 

1471 create_cache, 

1472 maxretry=10, 

1473 period=3 

1474 ) 

1475 

1476 def _destroy_linstor_cache(self): 

1477 self._all_volume_info_cache = None 

1478 self._all_volume_metadata_cache = None 

1479 

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

1481 # Misc. 

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

1483 

1484 def _reconnect(self): 

1485 controller_uri = get_controller_uri() 

1486 

1487 self._journaler = LinstorJournaler( 

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

1489 ) 

1490 

1491 # Try to open SR if exists. 

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

1493 # we are trying to execute an exclusive operation. 

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

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

1496 # the SR is locked. 

1497 self._linstor = LinstorVolumeManager( 

1498 controller_uri, 

1499 self._group_name, 

1500 repair=( 

1501 self.is_master() and 

1502 self.srcmd.cmd in self.ops_exclusive 

1503 ), 

1504 logger=util.SMlog 

1505 ) 

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

1507 

1508 def _ensure_space_available(self, amount_needed): 

1509 space_available = self._linstor.max_volume_size_allowed 

1510 if (space_available < amount_needed): 

1511 util.SMlog( 

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

1513 space_available, amount_needed 

1514 ) 

1515 ) 

1516 raise xs_errors.XenError('SRNoSpace') 

1517 

1518 def _kick_gc(self): 

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

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

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

1522 lock = Lock(LOCK_TYPE_GC_RUNNING, self.uuid) 

1523 if not lock.acquireNoblock(): 

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

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

1526 return 

1527 

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

1529 try: 

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

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

1532 except util.CommandException as e: 

1533 if e.code != errno.ETIMEDOUT: 

1534 raise 

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

1536 else: 

1537 lock.release() 

1538 

1539 util.SMlog('Kicking GC') 

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

1541 

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

1543# LinstorSr VDI 

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

1545 

1546 

1547class LinstorVDI(VDI.VDI): 

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

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

1550 TYPE_RAW = 'raw' 

1551 TYPE_VHD = 'vhd' 

1552 

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

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

1555 # Increase the performance when resize is called. 

1556 MAX_METADATA_VIRT_SIZE = 2 * 1024 * 1024 

1557 

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

1559 # VDI methods. 

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

1561 

1562 def load(self, vdi_uuid): 

1563 self._lock = self.sr.lock 

1564 self._exists = True 

1565 self._linstor = self.sr._linstor 

1566 

1567 # Update hidden parent property. 

1568 self.hidden = False 

1569 

1570 def raise_bad_load(e): 

1571 util.SMlog( 

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

1573 ) 

1574 util.SMlog(traceback.format_exc()) 

1575 raise xs_errors.XenError( 

1576 'VDIUnavailable', 

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

1578 ) 

1579 

1580 # Try to load VDI. 

1581 try: 

1582 if ( 

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

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

1585 ): 

1586 self.vdi_type = vhdutil.VDI_TYPE_RAW 

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

1588 else: 

1589 self._determine_type_and_path() 

1590 self._load_this() 

1591 

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

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

1594 )) 

1595 except LinstorVolumeManagerError as e: 

1596 # 1. It may be a VDI deletion. 

1597 if e.code == LinstorVolumeManagerError.ERR_VOLUME_NOT_EXISTS: 

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

1599 self.deleted = True 

1600 return 

1601 

1602 # 2. Or maybe a creation. 

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

1604 # Set type attribute of VDI parent class. 

1605 # We use VHD by default. 

1606 self.vdi_type = vhdutil.VDI_TYPE_VHD 

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

1608 

1609 self._exists = False 

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

1611 if vdi_sm_config is not None: 

1612 type = vdi_sm_config.get('type') 

1613 if type is not None: 

1614 if type == self.TYPE_RAW: 

1615 self.vdi_type = vhdutil.VDI_TYPE_RAW 

1616 elif type == self.TYPE_VHD: 

1617 self.vdi_type = vhdutil.VDI_TYPE_VHD 

1618 else: 

1619 raise xs_errors.XenError( 

1620 'VDICreate', 

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

1622 ) 

1623 if self.vdi_type == vhdutil.VDI_TYPE_VHD: 

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

1625 

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

1627 self._update_device_name(None) 

1628 return 

1629 raise_bad_load(e) 

1630 except Exception as e: 

1631 raise_bad_load(e) 

1632 

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

1634 # Usage example: 

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

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

1637 

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

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

1640 if self._exists: 

1641 raise xs_errors.XenError('VDIExists') 

1642 

1643 assert self.uuid 

1644 assert self.ty 

1645 assert self.vdi_type 

1646 

1647 # 2. Compute size and check space available. 

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

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

1650 util.SMlog( 

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

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

1653 ) 

1654 self.sr._ensure_space_available(volume_size) 

1655 

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

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

1658 

1659 # 4. Create! 

1660 failed = False 

1661 try: 

1662 volume_name = None 

1663 if self.ty == 'ha_statefile': 

1664 volume_name = HA_VOLUME_NAME 

1665 elif self.ty == 'redo_log': 

1666 volume_name = REDO_LOG_VOLUME_NAME 

1667 

1668 self._linstor.create_volume( 

1669 self.uuid, 

1670 volume_size, 

1671 persistent=False, 

1672 volume_name=volume_name, 

1673 high_availability=volume_name is not None 

1674 ) 

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

1676 

1677 self._update_device_name(volume_info.name) 

1678 

1679 if self.vdi_type == vhdutil.VDI_TYPE_RAW: 

1680 self.size = volume_info.virtual_size 

1681 else: 

1682 self.sr._vhdutil.create( 

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

1684 ) 

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

1686 

1687 if self._key_hash: 

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

1689 

1690 # Because vhdutil commands modify the volume data, 

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

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

1693 

1694 volume_metadata = { 

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

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

1697 IS_A_SNAPSHOT_TAG: False, 

1698 SNAPSHOT_OF_TAG: '', 

1699 SNAPSHOT_TIME_TAG: '', 

1700 TYPE_TAG: self.ty, 

1701 VDI_TYPE_TAG: self.vdi_type, 

1702 READ_ONLY_TAG: bool(self.read_only), 

1703 METADATA_OF_POOL_TAG: '' 

1704 } 

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

1706 

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

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

1709 # an already opened volume. 

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

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

1712 

1713 self._linstor.mark_volume_as_persistent(self.uuid) 

1714 except util.CommandException as e: 

1715 failed = True 

1716 raise xs_errors.XenError( 

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

1718 ) 

1719 except Exception as e: 

1720 failed = True 

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

1722 finally: 

1723 if failed: 

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

1725 try: 

1726 self._linstor.destroy_volume(self.uuid) 

1727 except Exception as e: 

1728 util.SMlog( 

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

1730 '{}'.format(e) 

1731 ) 

1732 

1733 self.utilisation = volume_info.allocated_size 

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

1735 

1736 self.ref = self._db_introduce() 

1737 self.sr._update_stats(self.size) 

1738 

1739 return VDI.VDI.get_params(self) 

1740 

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

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

1743 if self.attached: 

1744 raise xs_errors.XenError('VDIInUse') 

1745 

1746 if self.deleted: 

1747 return super(LinstorVDI, self).delete( 

1748 sr_uuid, vdi_uuid, data_only 

1749 ) 

1750 

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

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

1753 raise xs_errors.XenError( 

1754 'VDIDelete', 

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

1756 ) 

1757 

1758 try: 

1759 # Remove from XAPI and delete from LINSTOR. 

1760 self._linstor.destroy_volume(self.uuid) 

1761 if not data_only: 

1762 self._db_forget() 

1763 

1764 self.sr.lock.cleanupAll(vdi_uuid) 

1765 except Exception as e: 

1766 util.SMlog( 

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

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

1769 ) 

1770 

1771 try: 

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

1773 except LinstorVolumeManagerError as e: 

1774 if e.code != LinstorVolumeManagerError.ERR_VOLUME_DESTROY: 

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

1776 

1777 return 

1778 

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

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

1781 

1782 # TODO: Check size after delete. 

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

1784 self.sr._kick_gc() 

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

1786 

1787 def attach(self, sr_uuid, vdi_uuid): 

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

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

1790 if ( 

1791 not attach_from_config or 

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

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

1794 raise xs_errors.XenError( 

1795 'VDIUnavailable', 

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

1797 'scan SR first to trigger auto-repair' 

1798 ) 

1799 

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

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

1802 

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

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

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

1806 # than the VHD size + bitmap size. 

1807 need_inflate = True 

1808 if ( 

1809 self.vdi_type == vhdutil.VDI_TYPE_RAW or 

1810 not writable or 

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

1812 ): 

1813 need_inflate = False 

1814 

1815 if need_inflate: 

1816 try: 

1817 self._prepare_thin(True) 

1818 except Exception as e: 

1819 raise xs_errors.XenError( 

1820 'VDIUnavailable', 

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

1822 .format(e) 

1823 ) 

1824 

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

1826 self.xenstore_data = {} 

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

1828 

1829 if ( 

1830 USE_HTTP_NBD_SERVERS and 

1831 attach_from_config and 

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

1833 ): 

1834 return self._attach_using_http_nbd() 

1835 

1836 # Ensure we have a path... 

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

1838 

1839 self.attached = True 

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

1841 

1842 def detach(self, sr_uuid, vdi_uuid): 

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

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

1845 self.attached = False 

1846 

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

1848 return self._detach_using_http_nbd() 

1849 

1850 if self.vdi_type == vhdutil.VDI_TYPE_RAW: 

1851 return 

1852 

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

1854 # equal to the LINSTOR volume size. 

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

1856 already_deflated = self.capacity <= volume_size 

1857 

1858 if already_deflated: 

1859 util.SMlog( 

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

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

1862 ) 

1863 

1864 need_deflate = True 

1865 if already_deflated: 

1866 need_deflate = False 

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

1868 need_deflate = False 

1869 

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

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

1872 need_deflate = True 

1873 

1874 if need_deflate: 

1875 try: 

1876 self._prepare_thin(False) 

1877 except Exception as e: 

1878 raise xs_errors.XenError( 

1879 'VDIUnavailable', 

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

1881 .format(e) 

1882 ) 

1883 

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

1885 if self.sr.is_master(): 

1886 return 

1887 

1888 while vdi_uuid: 

1889 try: 

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

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

1892 except Exception: 

1893 break 

1894 

1895 if util.pathexists(path): 

1896 try: 

1897 self._linstor.remove_volume_if_diskless(vdi_uuid) 

1898 except Exception as e: 

1899 # Ensure we can always detach properly. 

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

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

1902 vdi_uuid = parent_vdi_uuid 

1903 

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

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

1906 if not self.sr.is_master(): 

1907 raise xs_errors.XenError( 

1908 'VDISize', 

1909 opterr='resize on slave not allowed' 

1910 ) 

1911 

1912 if self.hidden: 

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

1914 

1915 # Compute the virtual VHD and DRBD volume size. 

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

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

1918 util.SMlog( 

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

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

1921 ) 

1922 

1923 if size < self.size: 

1924 util.SMlog( 

1925 'vdi_resize: shrinking not supported: ' 

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

1927 ) 

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

1929 

1930 if size == self.size: 

1931 return VDI.VDI.get_params(self) 

1932 

1933 if self.vdi_type == vhdutil.VDI_TYPE_RAW: 

1934 old_volume_size = self.size 

1935 new_volume_size = LinstorVolumeManager.round_up_volume_size(size) 

1936 else: 

1937 old_volume_size = self.utilisation 

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

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

1940 new_volume_size = old_volume_size 

1941 else: 

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

1943 assert new_volume_size >= old_volume_size 

1944 

1945 space_needed = new_volume_size - old_volume_size 

1946 self.sr._ensure_space_available(space_needed) 

1947 

1948 old_size = self.size 

1949 if self.vdi_type == vhdutil.VDI_TYPE_RAW: 

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

1951 else: 

1952 if new_volume_size != old_volume_size: 

1953 self.sr._vhdutil.inflate( 

1954 self.sr._journaler, self.uuid, self.path, 

1955 new_volume_size, old_volume_size 

1956 ) 

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

1958 

1959 # Reload size attributes. 

1960 self._load_this() 

1961 

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

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

1964 self.session.xenapi.VDI.set_physical_utilisation( 

1965 vdi_ref, str(self.utilisation) 

1966 ) 

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

1968 return VDI.VDI.get_params(self) 

1969 

1970 def clone(self, sr_uuid, vdi_uuid): 

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

1972 

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

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

1975 if self.vdi_type != vhdutil.VDI_TYPE_VHD: 

1976 raise xs_errors.XenError('Unimplemented') 

1977 

1978 parent_uuid = vdi1 

1979 parent_path = self._linstor.get_device_path(parent_uuid) 

1980 

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

1982 # have a readonly error. 

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

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

1985 

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

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

1988 try: 

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

1990 self.sr._vhdutil.set_hidden(parent_path) 

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

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

1993 ) 

1994 finally: 

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

1996 

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

1998 raise util.SMException( 

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

2000 ) 

2001 

2002 util.SMlog('Compose done') 

2003 

2004 def generate_config(self, sr_uuid, vdi_uuid): 

2005 """ 

2006 Generate the XML config required to attach and activate 

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

2008 activation is handled by vdi_attach_from_config below. 

2009 """ 

2010 

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

2012 

2013 resp = {} 

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

2015 resp['sr_uuid'] = sr_uuid 

2016 resp['vdi_uuid'] = self.uuid 

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

2018 resp['command'] = 'vdi_attach_from_config' 

2019 

2020 # By default, we generate a normal config. 

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

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

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

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

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

2026 # instead. 

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

2028 if not USE_HTTP_NBD_SERVERS or volume_name not in [ 

2029 HA_VOLUME_NAME, REDO_LOG_VOLUME_NAME 

2030 ]: 

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

2032 available = False 

2033 # Try to refresh symlink path... 

2034 try: 

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

2036 available = util.pathexists(self.path) 

2037 except Exception: 

2038 pass 

2039 if not available: 

2040 raise xs_errors.XenError('VDIUnavailable') 

2041 

2042 resp['vdi_path'] = self.path 

2043 else: 

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

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

2046 

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

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

2049 

2050 def attach_from_config(self, sr_uuid, vdi_uuid): 

2051 """ 

2052 Attach and activate a VDI using config generated by 

2053 vdi_generate_config above. This is used for cases such as 

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

2055 """ 

2056 

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

2058 

2059 try: 

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

2061 self.sr.attach(sr_uuid) 

2062 

2063 if not DRIVER_CONFIG['ATTACH_FROM_CONFIG_WITH_TAPDISK']: 

2064 return self.attach(sr_uuid, vdi_uuid) 

2065 except Exception: 

2066 util.logException('LinstorVDI.attach_from_config') 

2067 raise xs_errors.XenError( 

2068 'SRUnavailable', 

2069 opterr='Unable to attach from config' 

2070 ) 

2071 

2072 def reset_leaf(self, sr_uuid, vdi_uuid): 

2073 if self.vdi_type != vhdutil.VDI_TYPE_VHD: 

2074 raise xs_errors.XenError('Unimplemented') 

2075 

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

2077 raise util.SMException( 

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

2079 .format(self.uuid) 

2080 ) 

2081 

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

2083 

2084 def _load_this(self): 

2085 volume_metadata = None 

2086 if self.sr._all_volume_metadata_cache: 

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

2088 if volume_metadata is None: 

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

2090 

2091 volume_info = None 

2092 if self.sr._all_volume_info_cache: 

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

2094 if volume_info is None: 

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

2096 

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

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

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

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

2101 # be lower than virtual size at creation. 

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

2103 self.utilisation = volume_info.allocated_size 

2104 self.capacity = volume_info.virtual_size 

2105 

2106 if self.vdi_type == vhdutil.VDI_TYPE_RAW: 

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

2108 self.size = volume_info.virtual_size 

2109 self.parent = '' 

2110 else: 

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

2112 self.hidden = vhd_info.hidden 

2113 self.size = vhd_info.sizeVirt 

2114 self.parent = vhd_info.parentUuid 

2115 

2116 if self.hidden: 

2117 self.managed = False 

2118 

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

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

2121 

2122 # Update sm_config_override of VDI parent class. 

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

2124 

2125 def _mark_hidden(self, hidden=True): 

2126 if self.hidden == hidden: 

2127 return 

2128 

2129 if self.vdi_type == vhdutil.VDI_TYPE_VHD: 

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

2131 else: 

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

2133 HIDDEN_TAG: hidden 

2134 }) 

2135 self.hidden = hidden 

2136 

2137 def update(self, sr_uuid, vdi_uuid): 

2138 xenapi = self.session.xenapi 

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

2140 

2141 volume_metadata = { 

2142 NAME_LABEL_TAG: util.to_plain_string( 

2143 xenapi.VDI.get_name_label(vdi_ref) 

2144 ), 

2145 NAME_DESCRIPTION_TAG: util.to_plain_string( 

2146 xenapi.VDI.get_name_description(vdi_ref) 

2147 ) 

2148 } 

2149 

2150 try: 

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

2152 except LinstorVolumeManagerError as e: 

2153 if e.code == LinstorVolumeManagerError.ERR_VOLUME_NOT_EXISTS: 

2154 raise xs_errors.XenError( 

2155 'VDIUnavailable', 

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

2157 ) 

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

2159 

2160 # -------------------------------------------------------------------------- 

2161 # Thin provisioning. 

2162 # -------------------------------------------------------------------------- 

2163 

2164 def _prepare_thin(self, attach): 

2165 if self.sr.is_master(): 

2166 if attach: 

2167 attach_thin( 

2168 self.session, self.sr._journaler, self._linstor, 

2169 self.sr.uuid, self.uuid 

2170 ) 

2171 else: 

2172 detach_thin( 

2173 self.session, self._linstor, self.sr.uuid, self.uuid 

2174 ) 

2175 else: 

2176 fn = 'attach' if attach else 'detach' 

2177 

2178 master = util.get_master_ref(self.session) 

2179 

2180 args = { 

2181 'groupName': self.sr._group_name, 

2182 'srUuid': self.sr.uuid, 

2183 'vdiUuid': self.uuid 

2184 } 

2185 

2186 try: 

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

2188 except Exception: 

2189 if fn != 'detach': 

2190 raise 

2191 

2192 # Reload size attrs after inflate or deflate! 

2193 self._load_this() 

2194 self.sr._update_physical_size() 

2195 

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

2197 self.session.xenapi.VDI.set_physical_utilisation( 

2198 vdi_ref, str(self.utilisation) 

2199 ) 

2200 

2201 self.session.xenapi.SR.set_physical_utilisation( 

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

2203 ) 

2204 

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

2206 # Generic helpers. 

2207 # -------------------------------------------------------------------------- 

2208 

2209 def _determine_type_and_path(self): 

2210 """ 

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

2212 """ 

2213 

2214 # 1. Check vdi_ref and vdi_type in config. 

2215 try: 

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

2217 if vdi_ref: 

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

2219 vdi_type = sm_config.get('vdi_type') 

2220 if vdi_type: 

2221 # Update parent fields. 

2222 self.vdi_type = vdi_type 

2223 self.sm_config_override = sm_config 

2224 self._update_device_name( 

2225 self._linstor.get_volume_name(self.uuid) 

2226 ) 

2227 return 

2228 except Exception: 

2229 pass 

2230 

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

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

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

2234 self.vdi_type = volume_metadata.get(VDI_TYPE_TAG) 

2235 if not self.vdi_type: 

2236 raise xs_errors.XenError( 

2237 'VDIUnavailable', 

2238 opterr='failed to get vdi_type in metadata' 

2239 ) 

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

2241 

2242 def _update_device_name(self, device_name): 

2243 self._device_name = device_name 

2244 

2245 # Mark path of VDI parent class. 

2246 if device_name: 

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

2248 else: 

2249 self.path = None 

2250 

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

2252 """ 

2253 Snapshot self and return the snapshot VDI object. 

2254 """ 

2255 

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

2257 snap_path = self._linstor.shallow_clone_volume( 

2258 self.uuid, snap_uuid, persistent=False 

2259 ) 

2260 

2261 # 2. Write the snapshot content. 

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

2263 self.sr._vhdutil.snapshot( 

2264 snap_path, self.path, is_raw, self.MAX_METADATA_VIRT_SIZE 

2265 ) 

2266 

2267 # 3. Get snapshot parent. 

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

2269 

2270 # 4. Update metadata. 

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

2272 volume_metadata = { 

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

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

2275 IS_A_SNAPSHOT_TAG: bool(snap_of_uuid), 

2276 SNAPSHOT_OF_TAG: snap_of_uuid, 

2277 SNAPSHOT_TIME_TAG: '', 

2278 TYPE_TAG: self.ty, 

2279 VDI_TYPE_TAG: vhdutil.VDI_TYPE_VHD, 

2280 READ_ONLY_TAG: False, 

2281 METADATA_OF_POOL_TAG: '' 

2282 } 

2283 self._linstor.set_volume_metadata(snap_uuid, volume_metadata) 

2284 

2285 # 5. Set size. 

2286 snap_vdi = LinstorVDI(self.sr, snap_uuid) 

2287 if not snap_vdi._exists: 

2288 raise xs_errors.XenError('VDISnapshot') 

2289 

2290 volume_info = self._linstor.get_volume_info(snap_uuid) 

2291 

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

2293 snap_vdi.utilisation = volume_info.allocated_size 

2294 

2295 # 6. Update sm config. 

2296 snap_vdi.sm_config = {} 

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

2298 if snap_parent: 

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

2300 snap_vdi.parent = snap_parent 

2301 

2302 snap_vdi.label = self.label 

2303 snap_vdi.description = self.description 

2304 

2305 self._linstor.mark_volume_as_persistent(snap_uuid) 

2306 

2307 return snap_vdi 

2308 

2309 # -------------------------------------------------------------------------- 

2310 # Implement specific SR methods. 

2311 # -------------------------------------------------------------------------- 

2312 

2313 def _rename(self, oldpath, newpath): 

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

2315 volume_uuid = self._linstor.get_volume_uuid_from_device_path(oldpath) 

2316 self._linstor.update_volume_name(volume_uuid, newpath) 

2317 

2318 def _do_snapshot( 

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

2320 ): 

2321 # If cbt enabled, save file consistency state. 

2322 if cbtlog is not None: 

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

2324 consistency_state = False 

2325 else: 

2326 consistency_state = True 

2327 util.SMlog( 

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

2329 .format(consistency_state, vdi_uuid) 

2330 ) 

2331 else: 

2332 consistency_state = None 

2333 

2334 if self.vdi_type != vhdutil.VDI_TYPE_VHD: 

2335 raise xs_errors.XenError('Unimplemented') 

2336 

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

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

2339 try: 

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

2341 finally: 

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

2343 

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

2345 util.SMlog( 

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

2347 .format(self.uuid, snap_type) 

2348 ) 

2349 

2350 # 1. Checks... 

2351 if self.hidden: 

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

2353 

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

2355 if depth == -1: 

2356 raise xs_errors.XenError( 

2357 'VDIUnavailable', 

2358 opterr='failed to get VHD depth' 

2359 ) 

2360 elif depth >= vhdutil.MAX_CHAIN_SIZE: 

2361 raise xs_errors.XenError('SnapshotChainTooLong') 

2362 

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

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

2365 

2366 volume_path = self.path 

2367 if not util.pathexists(volume_path): 

2368 raise xs_errors.XenError( 

2369 'EIO', 

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

2371 ) 

2372 

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

2374 base_uuid = util.gen_uuid() 

2375 snap_uuid = None 

2376 

2377 if snap_type == VDI.SNAPSHOT_DOUBLE: 

2378 snap_uuid = util.gen_uuid() 

2379 

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

2381 

2382 active_uuid = self.uuid 

2383 self.sr._journaler.create( 

2384 LinstorJournaler.CLONE, active_uuid, clone_info 

2385 ) 

2386 

2387 try: 

2388 # 3. Self becomes the new base. 

2389 # The device path remains the same. 

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

2391 self.uuid = base_uuid 

2392 self.location = self.uuid 

2393 self.read_only = True 

2394 self.managed = False 

2395 

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

2397 active_vdi = self._create_snapshot(active_uuid) 

2398 

2399 snap_vdi = None 

2400 if snap_type == VDI.SNAPSHOT_DOUBLE: 

2401 snap_vdi = self._create_snapshot(snap_uuid, active_uuid) 

2402 

2403 self.label = 'base copy' 

2404 self.description = '' 

2405 

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

2407 # in subsequent scans. 

2408 self._mark_hidden() 

2409 self._linstor.update_volume_metadata( 

2410 self.uuid, {READ_ONLY_TAG: True} 

2411 ) 

2412 

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

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

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

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

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

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

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

2420 active_vdi.sm_config[key] = sm_config[key] 

2421 

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

2423 # delete base if unused. 

2424 introduce_parent = True 

2425 try: 

2426 snap_parent = None 

2427 if snap_vdi: 

2428 snap_parent = snap_vdi.parent 

2429 

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

2431 snap_type == VDI.SNAPSHOT_SINGLE or 

2432 snap_type == VDI.SNAPSHOT_INTERNAL or 

2433 snap_parent != self.uuid 

2434 ): 

2435 util.SMlog( 

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

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

2438 ) 

2439 introduce_parent = False 

2440 self._linstor.destroy_volume(self.uuid) 

2441 except Exception as e: 

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

2443 pass 

2444 

2445 # 8. Introduce the new VDI records. 

2446 if snap_vdi: 

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

2448 # new snapshot disk. 

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

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

2451 # TODO: Maybe remove key_hash support. 

2452 if 'key_hash' in sm_config: 

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

2454 # If we have CBT enabled on the VDI, 

2455 # set CBT status for the new snapshot disk. 

2456 if cbtlog: 

2457 snap_vdi.cbt_enabled = True 

2458 

2459 if snap_vdi: 

2460 snap_vdi_ref = snap_vdi._db_introduce() 

2461 util.SMlog( 

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

2463 .format(snap_vdi_ref, snap_vdi.uuid) 

2464 ) 

2465 if introduce_parent: 

2466 base_vdi_ref = self._db_introduce() 

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

2468 util.SMlog( 

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

2470 .format(base_vdi_ref, self.uuid) 

2471 ) 

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

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

2474 NAME_DESCRIPTION_TAG: util.to_plain_string( 

2475 self.description 

2476 ), 

2477 READ_ONLY_TAG: True, 

2478 METADATA_OF_POOL_TAG: '' 

2479 }) 

2480 

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

2482 if snap_type == VDI.SNAPSHOT_DOUBLE and cbtlog: 

2483 try: 

2484 self._cbt_snapshot(snap_uuid, cbt_consistency) 

2485 except Exception: 

2486 # CBT operation failed. 

2487 # TODO: Implement me. 

2488 raise 

2489 

2490 if snap_type != VDI.SNAPSHOT_INTERNAL: 

2491 self.sr._update_stats(self.size) 

2492 

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

2494 ret_vdi = snap_vdi 

2495 if not ret_vdi: 

2496 ret_vdi = self 

2497 if not ret_vdi: 

2498 ret_vdi = active_vdi 

2499 

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

2501 self.session.xenapi.VDI.set_sm_config( 

2502 vdi_ref, active_vdi.sm_config 

2503 ) 

2504 except Exception: 

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

2506 try: 

2507 self.sr._handle_interrupted_clone( 

2508 active_uuid, clone_info, force_undo=True 

2509 ) 

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

2511 except Exception as clean_error: 

2512 util.SMlog( 

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

2514 .format(clean_error) 

2515 ) 

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

2517 

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

2519 

2520 return ret_vdi.get_params() 

2521 

2522 @staticmethod 

2523 def _start_persistent_http_server(volume_name): 

2524 pid_path = None 

2525 http_server = None 

2526 

2527 try: 

2528 if volume_name == HA_VOLUME_NAME: 

2529 port = '8076' 

2530 else: 

2531 port = '8077' 

2532 

2533 try: 

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

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

2536 # block indefinitely. 

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

2538 host_ip = util.get_this_host_address(session) 

2539 except: 

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

2541 host_ip, _ = get_ips_from_xha_config_file() 

2542 if not host_ip: 

2543 raise Exception( 

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

2545 ) 

2546 

2547 arguments = [ 

2548 'http-disk-server', 

2549 '--disk', 

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

2551 '--ip', 

2552 host_ip, 

2553 '--port', 

2554 port 

2555 ] 

2556 

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

2558 http_server = subprocess.Popen( 

2559 [FORK_LOG_DAEMON] + arguments, 

2560 stdout=subprocess.PIPE, 

2561 stderr=subprocess.STDOUT, 

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

2563 # touch the current one. 

2564 preexec_fn=os.setsid 

2565 ) 

2566 

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

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

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

2570 

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

2572 def is_ready(): 

2573 while http_server.poll() is None: 

2574 line = http_server.stdout.readline() 

2575 if reg_server_ready.search(line): 

2576 return True 

2577 return False 

2578 try: 

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

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

2581 except util.TimeoutException: 

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

2583 except Exception as e: 

2584 if pid_path: 

2585 try: 

2586 os.remove(pid_path) 

2587 except Exception: 

2588 pass 

2589 

2590 if http_server: 

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

2592 try: 

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

2594 except: 

2595 pass 

2596 

2597 raise xs_errors.XenError( 

2598 'VDIUnavailable', 

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

2600 ) 

2601 

2602 def _start_persistent_nbd_server(self, volume_name): 

2603 pid_path = None 

2604 nbd_path = None 

2605 nbd_server = None 

2606 

2607 try: 

2608 # We use a precomputed device size. 

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

2610 if volume_name == HA_VOLUME_NAME: 

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

2612 port = '8076' 

2613 device_size = 4 * 1024 * 1024 

2614 else: 

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

2616 port = '8077' 

2617 device_size = 256 * 1024 * 1024 

2618 

2619 try: 

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

2621 ips = util.get_host_addresses(session) 

2622 except Exception as e: 

2623 _, ips = get_ips_from_xha_config_file() 

2624 if not ips: 

2625 raise Exception( 

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

2627 ) 

2628 ips = ips.values() 

2629 

2630 arguments = [ 

2631 'nbd-http-server', 

2632 '--socket-path', 

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

2634 '--nbd-name', 

2635 volume_name, 

2636 '--urls', 

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

2638 '--device-size', 

2639 str(device_size) 

2640 ] 

2641 

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

2643 nbd_server = subprocess.Popen( 

2644 [FORK_LOG_DAEMON] + arguments, 

2645 stdout=subprocess.PIPE, 

2646 stderr=subprocess.STDOUT, 

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

2648 # touch the current one. 

2649 preexec_fn=os.setsid 

2650 ) 

2651 

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

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

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

2655 

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

2657 def get_nbd_path(): 

2658 while nbd_server.poll() is None: 

2659 line = nbd_server.stdout.readline() 

2660 match = reg_nbd_path.search(line) 

2661 if match: 

2662 return match.group(1) 

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

2664 try: 

2665 nbd_path = util.timeout_call(10, get_nbd_path) 

2666 if nbd_path is None: 

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

2668 except util.TimeoutException: 

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

2670 

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

2672 os.symlink(nbd_path, self.path) 

2673 except Exception as e: 

2674 if pid_path: 

2675 try: 

2676 os.remove(pid_path) 

2677 except Exception: 

2678 pass 

2679 

2680 if nbd_path: 

2681 try: 

2682 os.remove(nbd_path) 

2683 except Exception: 

2684 pass 

2685 

2686 if nbd_server: 

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

2688 try: 

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

2690 except: 

2691 pass 

2692 

2693 raise xs_errors.XenError( 

2694 'VDIUnavailable', 

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

2696 ) 

2697 

2698 @classmethod 

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

2700 try: 

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

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

2703 return 

2704 

2705 pid = None 

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

2707 try: 

2708 pid = int(pid_file.read()) 

2709 except Exception: 

2710 pass 

2711 

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

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

2714 try: 

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

2716 except Exception as e: 

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

2718 

2719 os.remove(path) 

2720 except: 

2721 pass 

2722 

2723 @classmethod 

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

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

2726 

2727 @classmethod 

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

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

2730 

2731 def _check_http_nbd_volume_name(self): 

2732 volume_name = self.path[14:] 

2733 if volume_name not in [ 

2734 HA_VOLUME_NAME, REDO_LOG_VOLUME_NAME 

2735 ]: 

2736 raise xs_errors.XenError( 

2737 'VDIUnavailable', 

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

2739 ) 

2740 return volume_name 

2741 

2742 def _attach_using_http_nbd(self): 

2743 volume_name = self._check_http_nbd_volume_name() 

2744 

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

2746 self._kill_persistent_nbd_server(volume_name) 

2747 self._kill_persistent_http_server(volume_name) 

2748 

2749 # 0. Fetch drbd path. 

2750 must_get_device_path = True 

2751 if not self.sr.is_master(): 

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

2753 try: 

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

2755 except Exception as e: 

2756 raise xs_errors.XenError( 

2757 'VDIUnavailable', 

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

2759 .format(self.uuid, e) 

2760 ) 

2761 

2762 hostname = socket.gethostname() 

2763 must_get_device_path = hostname in volume_info.diskful 

2764 

2765 drbd_path = None 

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

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

2768 # or diskful available to init HA. 

2769 # It also avoid this error in xensource.log 

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

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

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

2773 available = False 

2774 try: 

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

2776 available = util.pathexists(drbd_path) 

2777 except Exception: 

2778 pass 

2779 

2780 if not available: 

2781 raise xs_errors.XenError( 

2782 'VDIUnavailable', 

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

2784 ) 

2785 

2786 # 1. Prepare http-nbd folder. 

2787 try: 

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

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

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

2791 os.remove(self.path) 

2792 except OSError as e: 

2793 if e.errno != errno.EEXIST: 

2794 raise xs_errors.XenError( 

2795 'VDIUnavailable', 

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

2797 ) 

2798 

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

2800 http_service = None 

2801 if drbd_path: 

2802 assert(drbd_path in ( 

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

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

2805 )) 

2806 self._start_persistent_http_server(volume_name) 

2807 

2808 # 3. Start NBD server in all cases. 

2809 try: 

2810 self._start_persistent_nbd_server(volume_name) 

2811 except Exception as e: 

2812 if drbd_path: 

2813 self._kill_persistent_http_server(volume_name) 

2814 raise 

2815 

2816 self.attached = True 

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

2818 

2819 def _detach_using_http_nbd(self): 

2820 volume_name = self._check_http_nbd_volume_name() 

2821 self._kill_persistent_nbd_server(volume_name) 

2822 self._kill_persistent_http_server(volume_name) 

2823 

2824# ------------------------------------------------------------------------------ 

2825 

2826 

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

2828 def run(): 

2829 SRCommand.run(LinstorSR, DRIVER_INFO) 

2830 

2831 if not TRACE_PERFS: 

2832 run() 

2833 else: 

2834 util.make_profile('LinstorSR', run) 

2835else: 

2836 SR.registerSR(LinstorSR)