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 

35import blktap2 

36import cleanup 

37import distutils 

38import errno 

39import functools 

40import lvutil 

41import os 

42import re 

43import scsiutil 

44import signal 

45import socket 

46import SR 

47import SRCommand 

48import subprocess 

49import time 

50import traceback 

51import util 

52import VDI 

53import vhdutil 

54import xml.etree.ElementTree as xml_parser 

55import xmlrpc.client 

56import xs_errors 

57 

58from srmetadata import \ 

59 NAME_LABEL_TAG, NAME_DESCRIPTION_TAG, IS_A_SNAPSHOT_TAG, SNAPSHOT_OF_TAG, \ 

60 TYPE_TAG, VDI_TYPE_TAG, READ_ONLY_TAG, SNAPSHOT_TIME_TAG, \ 

61 METADATA_OF_POOL_TAG 

62 

63HIDDEN_TAG = 'hidden' 

64 

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

66 

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

68 

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

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

71# specific conditions: 

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

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

74USE_HTTP_NBD_SERVERS = True 

75 

76# Useful flag to trace calls using cProfile. 

77TRACE_PERFS = False 

78 

79# Enable/Disable VHD key hash support. 

80USE_KEY_HASH = False 

81 

82# Special volumes. 

83HA_VOLUME_NAME = PERSISTENT_PREFIX + 'ha-statefile' 

84REDO_LOG_VOLUME_NAME = PERSISTENT_PREFIX + 'redo-log' 

85 

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

87 

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

89# 'VDI_CONFIG_CBT', 'SR_PROBE' 

90 

91CAPABILITIES = [ 

92 'ATOMIC_PAUSE', 

93 'SR_UPDATE', 

94 'VDI_CREATE', 

95 'VDI_DELETE', 

96 'VDI_UPDATE', 

97 'VDI_ATTACH', 

98 'VDI_DETACH', 

99 'VDI_ACTIVATE', 

100 'VDI_DEACTIVATE', 

101 'VDI_CLONE', 

102 'VDI_MIRROR', 

103 'VDI_RESIZE', 

104 'VDI_SNAPSHOT', 

105 'VDI_GENERATE_CONFIG' 

106] 

107 

108CONFIGURATION = [ 

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

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

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

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

113] 

114 

115DRIVER_INFO = { 

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

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

118 'vendor': 'Vates', 

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

120 'driver_version': '1.0', 

121 'required_api_version': '1.0', 

122 'capabilities': CAPABILITIES, 

123 'configuration': CONFIGURATION 

124} 

125 

126DRIVER_CONFIG = {'ATTACH_FROM_CONFIG_WITH_TAPDISK': False} 

127 

128OPS_EXCLUSIVE = [ 

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

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

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

132] 

133 

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

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

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

137 

138 

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

140 volume_metadata = linstor.get_volume_metadata(vdi_uuid) 

141 image_type = volume_metadata.get(VDI_TYPE_TAG) 

142 if image_type == vhdutil.VDI_TYPE_RAW: 

143 return 

144 

145 device_path = linstor.get_device_path(vdi_uuid) 

146 

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

148 # there is nothing to do. 

149 vhd_size = LinstorVhdUtil.compute_volume_size( 

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

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

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

153 image_type 

154 ) 

155 

156 volume_info = linstor.get_volume_info(vdi_uuid) 

157 volume_size = volume_info.virtual_size 

158 

159 if vhd_size > volume_size: 

160 LinstorVhdUtil(session, linstor).inflate( 

161 journaler, vdi_uuid, device_path, vhd_size, volume_size 

162 ) 

163 

164 

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

166 volume_metadata = linstor.get_volume_metadata(vdi_uuid) 

167 image_type = volume_metadata.get(VDI_TYPE_TAG) 

168 if image_type == vhdutil.VDI_TYPE_RAW: 

169 return 

170 

171 def check_vbd_count(): 

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

173 vbds = session.xenapi.VBD.get_all_records_where( 

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

175 ) 

176 

177 num_plugged = 0 

178 for vbd_rec in vbds.values(): 

179 if vbd_rec['currently_attached']: 

180 num_plugged += 1 

181 if num_plugged > 1: 

182 raise xs_errors.XenError( 

183 'VDIUnavailable', 

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

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

186 ) 

187 

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

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

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

191 

192 device_path = linstor.get_device_path(vdi_uuid) 

193 vhdutil_inst = LinstorVhdUtil(session, linstor) 

194 new_volume_size = LinstorVolumeManager.round_up_volume_size( 

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

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

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

198 ) 

199 

200 volume_info = linstor.get_volume_info(vdi_uuid) 

201 old_volume_size = volume_info.virtual_size 

202 vhdutil_inst.deflate(device_path, new_volume_size, old_volume_size) 

203 

204 

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

206 # This function must always return without errors. 

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

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

209 try: 

210 detach_thin_impl(session, linstor, sr_uuid, vdi_uuid) 

211 except Exception as e: 

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

213 

214 

215def get_ips_from_xha_config_file(): 

216 ips = dict() 

217 host_id = None 

218 try: 

219 # Ensure there is no dirty read problem. 

220 # For example if the HA is reloaded. 

221 tree = util.retry( 

222 lambda: xml_parser.parse(XHA_CONFIG_PATH), 

223 maxretry=10, 

224 period=1 

225 ) 

226 except: 

227 return (None, ips) 

228 

229 def parse_host_nodes(ips, node): 

230 current_id = None 

231 current_ip = None 

232 

233 for sub_node in node: 

234 if sub_node.tag == 'IPaddress': 

235 current_ip = sub_node.text 

236 elif sub_node.tag == 'HostID': 

237 current_id = sub_node.text 

238 else: 

239 continue 

240 

241 if current_id and current_ip: 

242 ips[current_id] = current_ip 

243 return 

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

245 

246 def parse_common_config(ips, node): 

247 for sub_node in node: 

248 if sub_node.tag == 'host': 

249 parse_host_nodes(ips, sub_node) 

250 

251 def parse_local_config(ips, node): 

252 for sub_node in node: 

253 if sub_node.tag == 'localhost': 

254 for host_node in sub_node: 

255 if host_node.tag == 'HostID': 

256 return host_node.text 

257 

258 for node in tree.getroot(): 

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

260 parse_common_config(ips, node) 

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

262 host_id = parse_local_config(ips, node) 

263 else: 

264 continue 

265 

266 if ips and host_id: 

267 break 

268 

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

270 

271 

272def activate_lvm_group(group_name): 

273 path = group_name.split('/') 

274 assert path and len(path) <= 2 

275 try: 

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

277 except Exception as e: 

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

279 

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

281 

282# Usage example: 

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

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

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

286 

287 

288class LinstorSR(SR.SR): 

289 DRIVER_TYPE = 'linstor' 

290 

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

292 PROVISIONING_DEFAULT = 'thin' 

293 

294 MANAGER_PLUGIN = 'linstor-manager' 

295 

296 INIT_STATUS_NOT_SET = 0 

297 INIT_STATUS_IN_PROGRESS = 1 

298 INIT_STATUS_OK = 2 

299 INIT_STATUS_FAIL = 3 

300 

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

302 # SR methods. 

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

304 

305 @staticmethod 

306 def handles(type): 

307 return type == LinstorSR.DRIVER_TYPE 

308 

309 def load(self, sr_uuid): 

310 if not LINSTOR_AVAILABLE: 

311 raise util.SMException( 

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

313 ) 

314 

315 # Check parameters. 

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

317 raise xs_errors.XenError('LinstorConfigGroupNameMissing') 

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

319 raise xs_errors.XenError('LinstorConfigRedundancyMissing') 

320 

321 self.driver_config = DRIVER_CONFIG 

322 

323 # Check provisioning config. 

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

325 if provisioning: 

326 if provisioning in self.PROVISIONING_TYPES: 

327 self._provisioning = provisioning 

328 else: 

329 raise xs_errors.XenError( 

330 'InvalidArg', 

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

332 self.PROVISIONING_TYPES 

333 ) 

334 ) 

335 else: 

336 self._provisioning = self.PROVISIONING_DEFAULT 

337 

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

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

340 distutils.util.strtobool(monitor_db_quorum) 

341 

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

343 # 'vdi_attach_from_config' command is executed. 

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

345 if self._has_session: 

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

347 else: 

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

349 

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

351 if provisioning in self.PROVISIONING_TYPES: 

352 self._provisioning = provisioning 

353 

354 # Define properties for SR parent class. 

355 self.ops_exclusive = OPS_EXCLUSIVE 

356 self.path = LinstorVolumeManager.DEV_ROOT_PATH 

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

358 self.sr_vditype = SR.DEFAULT_TAP 

359 

360 if self.cmd == 'sr_create': 

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

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

363 self._journaler = None 

364 

365 self._is_master = False 

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

367 self._is_master = True 

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

369 

370 self._vdi_shared_time = 0 

371 

372 self._init_status = self.INIT_STATUS_NOT_SET 

373 

374 self._vdis_loaded = False 

375 self._all_volume_info_cache = None 

376 self._all_volume_metadata_cache = None 

377 

378 def _locked_load(method): 

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

380 self._init_status = self.INIT_STATUS_OK 

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

382 

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

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

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

386 activate_lvm_group(self._group_name) 

387 

388 if not self._has_session: 

389 if self.srcmd.cmd in ( 

390 'vdi_attach_from_config', 

391 'vdi_detach_from_config', 

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

393 # empty command. 

394 None 

395 ): 

396 def create_linstor(uri, attempt_count=30): 

397 self._linstor = LinstorVolumeManager( 

398 uri, 

399 self._group_name, 

400 logger=util.SMlog, 

401 attempt_count=attempt_count 

402 ) 

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

404 # I.e. not an HA volume. 

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

406 

407 controller_uri = get_controller_uri() 

408 if controller_uri: 

409 create_linstor(controller_uri) 

410 else: 

411 def connect(): 

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

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

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

415 controller_uri = 'linstor://' + ip 

416 try: 

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

418 create_linstor(controller_uri, attempt_count=0) 

419 return controller_uri 

420 except: 

421 pass 

422 

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

424 if not controller_uri: 

425 raise xs_errors.XenError( 

426 'SRUnavailable', 

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

428 ) 

429 

430 self._journaler = LinstorJournaler( 

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

432 ) 

433 

434 if self.srcmd.cmd is None: 

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

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

437 

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

439 

440 if not self._is_master: 

441 if self.cmd in [ 

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

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

444 'vdi_snapshot', 'vdi_clone' 

445 ]: 

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

447 raise xs_errors.XenError('LinstorMaster') 

448 

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

450 # the VDI before the LinstorJournaler/LinstorVolumeManager 

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

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

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

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

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

456 self._vdi_shared_time = time.time() 

457 

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

459 try: 

460 self._reconnect() 

461 except Exception as e: 

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

463 

464 if self._linstor: 

465 try: 

466 hosts = self._linstor.disconnected_hosts 

467 except Exception as e: 

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

469 

470 if hosts: 

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

472 

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

474 if ( 

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

476 self.cmd != 'vdi_create' 

477 ): 

478 self._linstor.ensure_volume_is_not_locked( 

479 self.srcmd.params['vdi_uuid'] 

480 ) 

481 

482 try: 

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

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

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

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

487 # 

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

489 # resourceless volumes. 

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

491 'vdi_attach', 'vdi_detach', 

492 'vdi_activate', 'vdi_deactivate', 

493 'vdi_epoch_begin', 'vdi_epoch_end', 

494 'vdi_update', 'vdi_destroy' 

495 ]: 

496 load_vdis = ( 

497 self.cmd == 'sr_scan' or 

498 self.cmd == 'sr_attach' 

499 ) or len( 

500 self._journaler.get_all(LinstorJournaler.INFLATE) 

501 ) or len( 

502 self._journaler.get_all(LinstorJournaler.CLONE) 

503 ) 

504 

505 if load_vdis: 

506 self._load_vdis() 

507 

508 self._linstor.remove_resourceless_volumes() 

509 

510 self._synchronize_metadata() 

511 except Exception as e: 

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

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

514 # from the XAPI database otherwise. 

515 raise e 

516 util.SMlog( 

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

518 ) 

519 util.SMlog(traceback.format_exc()) 

520 

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

522 

523 @functools.wraps(wrapped_method) 

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

525 if self._init_status in \ 

526 (self.INIT_STATUS_OK, self.INIT_STATUS_IN_PROGRESS): 

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

528 if self._init_status == self.INIT_STATUS_FAIL: 

529 util.SMlog( 

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

531 .format(method) 

532 ) 

533 else: 

534 try: 

535 self._init_status = self.INIT_STATUS_IN_PROGRESS 

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

537 except Exception: 

538 if self._init_status != self.INIT_STATUS_OK: 

539 self._init_status = self.INIT_STATUS_FAIL 

540 raise 

541 

542 return wrap 

543 

544 def cleanup(self): 

545 if self._vdi_shared_time: 

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

547 

548 @_locked_load 

549 def create(self, uuid, size): 

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

551 

552 host_adresses = util.get_host_addresses(self.session) 

553 if self._redundancy > len(host_adresses): 

554 raise xs_errors.XenError( 

555 'LinstorSRCreate', 

556 opterr='Redundancy greater than host count' 

557 ) 

558 

559 xenapi = self.session.xenapi 

560 srs = xenapi.SR.get_all_records_where( 

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

562 ) 

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

564 

565 for sr in srs.values(): 

566 for pbd in sr['PBDs']: 

567 device_config = xenapi.PBD.get_device_config(pbd) 

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

569 if group_name and group_name == self._group_name: 

570 raise xs_errors.XenError( 

571 'LinstorSRCreate', 

572 opterr='group name must be unique' 

573 ) 

574 

575 if srs: 

576 raise xs_errors.XenError( 

577 'LinstorSRCreate', 

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

579 ) 

580 

581 online_hosts = util.get_online_hosts(self.session) 

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

583 raise xs_errors.XenError( 

584 'LinstorSRCreate', 

585 opterr='Not enough online hosts' 

586 ) 

587 

588 ips = {} 

589 for host_ref in online_hosts: 

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

591 hostname = record['hostname'] 

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

593 

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

595 raise xs_errors.XenError( 

596 'LinstorSRCreate', 

597 opterr='Multiple hosts with same hostname' 

598 ) 

599 

600 # Ensure ports are opened and LINSTOR satellites 

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

602 # must be stopped. 

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

604 

605 # Create SR. 

606 # Throw if the SR already exists. 

607 try: 

608 self._linstor = LinstorVolumeManager.create_sr( 

609 self._group_name, 

610 ips, 

611 self._redundancy, 

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

613 auto_quorum=self._monitor_db_quorum, 

614 logger=util.SMlog 

615 ) 

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

617 except Exception as e: 

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

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

620 

621 try: 

622 util.SMlog( 

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

624 ) 

625 self._update_drbd_reactor_on_all_hosts(enabled=True) 

626 except Exception as e: 

627 try: 

628 self._linstor.destroy() 

629 except Exception as e2: 

630 util.SMlog( 

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

632 .format(e2) 

633 ) 

634 raise e 

635 

636 @_locked_load 

637 def delete(self, uuid): 

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

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

640 

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

642 raise xs_errors.XenError('SRNotEmpty') 

643 

644 node_name = get_controller_node_name() 

645 if not node_name: 

646 raise xs_errors.XenError( 

647 'LinstorSRDelete', 

648 opterr='Cannot get controller node name' 

649 ) 

650 

651 host = None 

652 if node_name == 'localhost': 

653 host = util.get_this_host_ref(self.session) 

654 else: 

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

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

657 if r_name == node_name: 

658 host = slave 

659 break 

660 

661 if not host: 

662 raise xs_errors.XenError( 

663 'LinstorSRDelete', 

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

665 node_name 

666 ) 

667 ) 

668 

669 try: 

670 self._update_drbd_reactor_on_all_hosts( 

671 controller_node_name=node_name, enabled=False 

672 ) 

673 

674 args = { 

675 'groupName': self._group_name, 

676 } 

677 self._exec_manager_command( 

678 host, 'destroy', args, 'LinstorSRDelete' 

679 ) 

680 except Exception as e: 

681 try: 

682 self._update_drbd_reactor_on_all_hosts( 

683 controller_node_name=node_name, enabled=True 

684 ) 

685 except Exception as e2: 

686 util.SMlog( 

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

688 .format(e2) 

689 ) 

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

691 raise xs_errors.XenError( 

692 'LinstorSRDelete', 

693 opterr=str(e) 

694 ) 

695 

696 Lock.cleanupAll(self.uuid) 

697 

698 @_locked_load 

699 def update(self, uuid): 

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

701 

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

703 if not self._linstor: 

704 raise xs_errors.XenError( 

705 'SRUnavailable', 

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

707 ) 

708 

709 self._update_stats(0) 

710 

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

712 xenapi = self.session.xenapi 

713 self._linstor.metadata = { 

714 NAME_LABEL_TAG: util.to_plain_string( 

715 xenapi.SR.get_name_label(self.sr_ref) 

716 ), 

717 NAME_DESCRIPTION_TAG: util.to_plain_string( 

718 xenapi.SR.get_name_description(self.sr_ref) 

719 ) 

720 } 

721 

722 @_locked_load 

723 def attach(self, uuid): 

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

725 

726 if not self._linstor: 

727 raise xs_errors.XenError( 

728 'SRUnavailable', 

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

730 ) 

731 

732 @_locked_load 

733 def detach(self, uuid): 

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

735 cleanup.abort(self.uuid) 

736 

737 @_locked_load 

738 def probe(self): 

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

740 # TODO 

741 

742 @_locked_load 

743 def scan(self, uuid): 

744 if self._init_status == self.INIT_STATUS_FAIL: 

745 return 

746 

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

748 if not self._linstor: 

749 raise xs_errors.XenError( 

750 'SRUnavailable', 

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

752 ) 

753 

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

755 # are loaded. 

756 self._load_vdis() 

757 self._update_physical_size() 

758 

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

760 if self.vdis[vdi_uuid].deleted: 

761 del self.vdis[vdi_uuid] 

762 

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

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

765 try: 

766 self._linstor.get_database_path() 

767 except Exception: 

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

769 # VDIs in the XAPI database... 

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

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

772 ): 

773 raise xs_errors.XenError( 

774 'SRUnavailable', 

775 opterr='Database is not mounted' 

776 ) 

777 

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

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

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

781 self._kick_gc() 

782 

783 @_locked_load 

784 def vdi(self, uuid): 

785 return LinstorVDI(self, uuid) 

786 

787 _locked_load = staticmethod(_locked_load) 

788 

789 # -------------------------------------------------------------------------- 

790 # Lock. 

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

792 

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

794 master = util.get_master_ref(self.session) 

795 

796 command = 'lockVdi' 

797 args = { 

798 'groupName': self._group_name, 

799 'srUuid': self.uuid, 

800 'vdiUuid': vdi_uuid, 

801 'locked': str(locked) 

802 } 

803 

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

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

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

807 # 

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

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

810 # to implement without impacting performance. 

811 if not locked: 

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

813 timeout = LinstorVolumeManager.LOCKED_EXPIRATION_DELAY * 0.7 

814 if elapsed_time >= timeout: 

815 util.SMlog( 

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

817 .format(vdi_uuid) 

818 ) 

819 return 

820 

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

822 

823 # -------------------------------------------------------------------------- 

824 # Network. 

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

826 

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

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

829 host_uuid = host_rec['uuid'] 

830 

831 try: 

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

833 host_ref, self.MANAGER_PLUGIN, command, args 

834 ) 

835 except Exception as e: 

836 util.SMlog( 

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

838 host_uuid, self.MANAGER_PLUGIN, command, args 

839 ) 

840 ) 

841 raise e 

842 

843 util.SMlog( 

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

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

846 ) 

847 ) 

848 if ret == 'False': 

849 raise xs_errors.XenError( 

850 error, 

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

852 ) 

853 

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

855 self._exec_manager_command( 

856 host, 

857 'prepareSr' if enabled else 'releaseSr', 

858 {'groupName': group_name}, 

859 'SRUnavailable' 

860 ) 

861 

862 def _prepare_sr_on_all_hosts(self, group_name, enabled): 

863 master = util.get_master_ref(self.session) 

864 self._prepare_sr(master, group_name, enabled) 

865 

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

867 self._prepare_sr(slave, group_name, enabled) 

868 

869 def _update_drbd_reactor(self, host, enabled): 

870 self._exec_manager_command( 

871 host, 

872 'updateDrbdReactor', 

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

874 'SRUnavailable' 

875 ) 

876 

877 def _update_drbd_reactor_on_all_hosts( 

878 self, enabled, controller_node_name=None 

879 ): 

880 if controller_node_name == 'localhost': 

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

882 util.get_this_host_ref(self.session) 

883 )['hostname'] 

884 assert controller_node_name 

885 assert controller_node_name != 'localhost' 

886 

887 controller_host = None 

888 secondary_hosts = [] 

889 

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

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

892 hostname = host_rec['hostname'] 

893 if controller_node_name == hostname: 

894 controller_host = host_ref 

895 else: 

896 secondary_hosts.append((host_ref, hostname)) 

897 

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

899 if controller_node_name and not controller_host: 

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

901 controller_node_name 

902 )) 

903 

904 if enabled and controller_host: 

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

906 action_name, controller_node_name 

907 )) 

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

909 # node name first. 

910 self._update_drbd_reactor(controller_host, enabled) 

911 

912 for host_ref, hostname in secondary_hosts: 

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

914 action_name, hostname 

915 )) 

916 self._update_drbd_reactor(host_ref, enabled) 

917 

918 if not enabled and controller_host: 

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

920 action_name, controller_node_name 

921 )) 

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

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

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

925 self._update_drbd_reactor(controller_host, enabled) 

926 

927 # -------------------------------------------------------------------------- 

928 # Metadata. 

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

930 

931 def _synchronize_metadata_and_xapi(self): 

932 try: 

933 # First synch SR parameters. 

934 self.update(self.uuid) 

935 

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

937 xenapi = self.session.xenapi 

938 volumes_metadata = self._linstor.get_volumes_with_metadata() 

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

940 try: 

941 vdi_ref = xenapi.VDI.get_by_uuid(vdi_uuid) 

942 except Exception: 

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

944 continue 

945 

946 label = util.to_plain_string( 

947 xenapi.VDI.get_name_label(vdi_ref) 

948 ) 

949 description = util.to_plain_string( 

950 xenapi.VDI.get_name_description(vdi_ref) 

951 ) 

952 

953 if ( 

954 volume_metadata.get(NAME_LABEL_TAG) != label or 

955 volume_metadata.get(NAME_DESCRIPTION_TAG) != description 

956 ): 

957 self._linstor.update_volume_metadata(vdi_uuid, { 

958 NAME_LABEL_TAG: label, 

959 NAME_DESCRIPTION_TAG: description 

960 }) 

961 except Exception as e: 

962 raise xs_errors.XenError( 

963 'MetadataError', 

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

965 ) 

966 

967 def _synchronize_metadata(self): 

968 if not self._is_master: 

969 return 

970 

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

972 if self.cmd == 'sr_attach': 

973 try: 

974 util.SMlog( 

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

976 ) 

977 self._synchronize_metadata_and_xapi() 

978 except Exception as e: 

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

980 

981 # -------------------------------------------------------------------------- 

982 # Stats. 

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

984 

985 def _update_stats(self, virt_alloc_delta): 

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

987 self.sr_ref 

988 )) 

989 

990 # Update size attributes of the SR parent class. 

991 self.virtual_allocation = valloc + virt_alloc_delta 

992 

993 self._update_physical_size() 

994 

995 # Notify SR parent class. 

996 self._db_update() 

997 

998 def _update_physical_size(self): 

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

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

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

1002 self.physical_size = min_physical_size * pool_count // \ 

1003 self._linstor.redundancy 

1004 

1005 self.physical_utilisation = self._linstor.allocated_volume_size 

1006 

1007 # -------------------------------------------------------------------------- 

1008 # VDIs. 

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

1010 

1011 def _load_vdis(self): 

1012 if self._vdis_loaded: 

1013 return 

1014 

1015 assert self._is_master 

1016 

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

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

1019 # enjoy it with a few lines. 

1020 self._create_linstor_cache() 

1021 self._load_vdis_ex() 

1022 self._destroy_linstor_cache() 

1023 

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

1025 self._vdis_loaded = True 

1026 

1027 self._undo_all_journal_transactions() 

1028 

1029 def _load_vdis_ex(self): 

1030 # 1. Get existing VDIs in XAPI. 

1031 xenapi = self.session.xenapi 

1032 xapi_vdi_uuids = set() 

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

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

1035 

1036 # 2. Get volumes info. 

1037 all_volume_info = self._all_volume_info_cache 

1038 volumes_metadata = self._all_volume_metadata_cache 

1039 

1040 # 3. Get CBT vdis. 

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

1042 cbt_vdis = set() 

1043 for volume_metadata in volumes_metadata.values(): 

1044 cbt_uuid = volume_metadata.get(CBTLOG_TAG) 

1045 if cbt_uuid: 

1046 cbt_vdis.add(cbt_uuid) 

1047 

1048 introduce = False 

1049 

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

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

1052 has_clone_entries = list(self._journaler.get_all( 

1053 LinstorJournaler.CLONE 

1054 ).items()) 

1055 

1056 if has_clone_entries: 

1057 util.SMlog( 

1058 'Cannot introduce VDIs during scan because it exists ' 

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

1060 ) 

1061 else: 

1062 introduce = True 

1063 

1064 # 4. Now check all volume info. 

1065 vdi_to_snaps = {} 

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

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

1068 continue 

1069 

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

1071 if vdi_uuid not in xapi_vdi_uuids: 

1072 if not introduce: 

1073 continue 

1074 

1075 if vdi_uuid.startswith('DELETED_'): 

1076 continue 

1077 

1078 volume_metadata = volumes_metadata.get(vdi_uuid) 

1079 if not volume_metadata: 

1080 util.SMlog( 

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

1082 .format(vdi_uuid) 

1083 ) 

1084 continue 

1085 

1086 util.SMlog( 

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

1088 'LINSTOR and not in XAPI...' 

1089 .format(vdi_uuid) 

1090 ) 

1091 

1092 try: 

1093 self._linstor.get_device_path(vdi_uuid) 

1094 except Exception as e: 

1095 util.SMlog( 

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

1097 .format(vdi_uuid, e) 

1098 ) 

1099 continue 

1100 

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

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

1103 vdi_type = volume_metadata.get(VDI_TYPE_TAG) 

1104 

1105 if not vdi_type: 

1106 util.SMlog( 

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

1108 'without vdi_type' 

1109 ) 

1110 continue 

1111 

1112 sm_config = { 

1113 'vdi_type': vdi_type 

1114 } 

1115 

1116 if vdi_type == vhdutil.VDI_TYPE_RAW: 

1117 managed = not volume_metadata.get(HIDDEN_TAG) 

1118 elif vdi_type == vhdutil.VDI_TYPE_VHD: 

1119 vhd_info = self._vhdutil.get_vhd_info(vdi_uuid) 

1120 managed = not vhd_info.hidden 

1121 if vhd_info.parentUuid: 

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

1123 else: 

1124 util.SMlog( 

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

1126 .format(vdi_uuid, vdi_type) 

1127 ) 

1128 continue 

1129 

1130 util.SMlog( 

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

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

1133 name_label, 

1134 volume_info.virtual_size, 

1135 volume_info.allocated_size 

1136 ) 

1137 ) 

1138 

1139 vdi_ref = xenapi.VDI.db_introduce( 

1140 vdi_uuid, 

1141 name_label, 

1142 volume_metadata.get(NAME_DESCRIPTION_TAG) or '', 

1143 self.sr_ref, 

1144 type, 

1145 False, # sharable 

1146 bool(volume_metadata.get(READ_ONLY_TAG)), 

1147 {}, # other_config 

1148 vdi_uuid, # location 

1149 {}, # xenstore_data 

1150 sm_config, 

1151 managed, 

1152 str(volume_info.virtual_size), 

1153 str(volume_info.allocated_size) 

1154 ) 

1155 

1156 is_a_snapshot = volume_metadata.get(IS_A_SNAPSHOT_TAG) 

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

1158 if is_a_snapshot: 

1159 xenapi.VDI.set_snapshot_time( 

1160 vdi_ref, 

1161 xmlrpc.client.DateTime( 

1162 volume_metadata[SNAPSHOT_TIME_TAG] or 

1163 '19700101T00:00:00Z' 

1164 ) 

1165 ) 

1166 

1167 snap_uuid = volume_metadata[SNAPSHOT_OF_TAG] 

1168 if snap_uuid in vdi_to_snaps: 

1169 vdi_to_snaps[snap_uuid].append(vdi_uuid) 

1170 else: 

1171 vdi_to_snaps[snap_uuid] = [vdi_uuid] 

1172 

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

1174 vdi = self.vdi(vdi_uuid) 

1175 self.vdis[vdi_uuid] = vdi 

1176 

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

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

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

1180 vdi.sm_config_override['key_hash'] = \ 

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

1182 

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

1184 # or already in XAPI. 

1185 cbt_uuid = volume_metadata.get(CBTLOG_TAG) 

1186 if cbt_uuid in cbt_vdis: 

1187 vdi_ref = xenapi.VDI.get_by_uuid(vdi_uuid) 

1188 xenapi.VDI.set_cbt_enabled(vdi_ref, True) 

1189 # For existing VDIs, update local state too. 

1190 # Scan in base class SR updates existing VDIs 

1191 # again based on local states. 

1192 self.vdis[vdi_uuid].cbt_enabled = True 

1193 cbt_vdis.remove(cbt_uuid) 

1194 

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

1196 for src_uuid in vdi_to_snaps: 

1197 try: 

1198 src_ref = xenapi.VDI.get_by_uuid(src_uuid) 

1199 except Exception: 

1200 # The source VDI no longer exists, continue. 

1201 continue 

1202 

1203 for snap_uuid in vdi_to_snaps[src_uuid]: 

1204 try: 

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

1206 snap_ref = xenapi.VDI.get_by_uuid(snap_uuid) 

1207 xenapi.VDI.set_snapshot_of(snap_ref, src_ref) 

1208 except Exception as e: 

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

1210 

1211 # TODO: Check correctly how to use CBT. 

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

1213 

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

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

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

1217 for cbt_uuid in cbt_vdis: 

1218 new_vdi = self.vdi(cbt_uuid) 

1219 new_vdi.ty = 'cbt_metadata' 

1220 new_vdi.cbt_enabled = True 

1221 self.vdis[cbt_uuid] = new_vdi 

1222 

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

1224 self.virtual_allocation = 0 

1225 

1226 # 8. Build geneology. 

1227 geneology = {} 

1228 

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

1230 if vdi.parent: 

1231 if vdi.parent in self.vdis: 

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

1233 if vdi.parent in geneology: 

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

1235 else: 

1236 geneology[vdi.parent] = [vdi_uuid] 

1237 if not vdi.hidden: 

1238 self.virtual_allocation += vdi.size 

1239 

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

1241 # will be GC'ed. 

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

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

1244 util.SMlog( 

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

1246 ) 

1247 del self.vdis[vdi_uuid] 

1248 

1249 # -------------------------------------------------------------------------- 

1250 # Journals. 

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

1252 

1253 def _get_vdi_path_and_parent(self, vdi_uuid, volume_name): 

1254 try: 

1255 device_path = self._linstor.build_device_path(volume_name) 

1256 if not util.pathexists(device_path): 

1257 return (None, None) 

1258 

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

1260 volume_metadata = self._linstor.get_volume_metadata(vdi_uuid) 

1261 vdi_type = volume_metadata[VDI_TYPE_TAG] 

1262 if vdi_type == vhdutil.VDI_TYPE_RAW: 

1263 return (device_path, None) 

1264 

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

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

1267 return (None, None) 

1268 

1269 vhd_info = self._vhdutil.get_vhd_info(vdi_uuid) 

1270 if vhd_info: 

1271 return (device_path, vhd_info.parentUuid) 

1272 except Exception as e: 

1273 util.SMlog( 

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

1275 .format(e) 

1276 ) 

1277 return (None, None) 

1278 

1279 def _undo_all_journal_transactions(self): 

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

1281 self.lock.acquire() 

1282 try: 

1283 self._handle_interrupted_inflate_ops() 

1284 self._handle_interrupted_clone_ops() 

1285 pass 

1286 finally: 

1287 self.lock.release() 

1288 

1289 def _handle_interrupted_inflate_ops(self): 

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

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

1292 self._handle_interrupted_inflate(vdi_uuid, old_size) 

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

1294 

1295 def _handle_interrupted_clone_ops(self): 

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

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

1298 self._handle_interrupted_clone(vdi_uuid, old_size) 

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

1300 

1301 def _handle_interrupted_inflate(self, vdi_uuid, old_size): 

1302 util.SMlog( 

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

1304 .format(vdi_uuid, old_size) 

1305 ) 

1306 

1307 vdi = self.vdis.get(vdi_uuid) 

1308 if not vdi: 

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

1310 return 

1311 

1312 assert not self._all_volume_info_cache 

1313 volume_info = self._linstor.get_volume_info(vdi_uuid) 

1314 

1315 current_size = volume_info.virtual_size 

1316 assert current_size > 0 

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

1318 

1319 def _handle_interrupted_clone( 

1320 self, vdi_uuid, clone_info, force_undo=False 

1321 ): 

1322 util.SMlog( 

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

1324 .format(vdi_uuid, clone_info) 

1325 ) 

1326 

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

1328 

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

1330 volume_names = self._linstor.get_volumes_with_name() 

1331 

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

1333 if base_uuid not in volume_names: 

1334 if vdi_uuid in volume_names: 

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

1336 return 

1337 raise util.SMException( 

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

1339 .format(base_uuid, vdi_uuid) 

1340 ) 

1341 

1342 if force_undo: 

1343 util.SMlog('Explicit revert') 

1344 self._undo_clone( 

1345 volume_names, vdi_uuid, base_uuid, snap_uuid 

1346 ) 

1347 return 

1348 

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

1350 if vdi_uuid not in volume_names or \ 

1351 (snap_uuid and snap_uuid not in volume_names): 

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

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

1354 return 

1355 

1356 vdi_path, vdi_parent_uuid = self._get_vdi_path_and_parent( 

1357 vdi_uuid, volume_names[vdi_uuid] 

1358 ) 

1359 snap_path, snap_parent_uuid = self._get_vdi_path_and_parent( 

1360 snap_uuid, volume_names[snap_uuid] 

1361 ) 

1362 

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

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

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

1366 return 

1367 

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

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

1370 

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

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

1373 base_metadata = self._linstor.get_volume_metadata(base_uuid) 

1374 base_type = base_metadata[VDI_TYPE_TAG] 

1375 

1376 if not util.pathexists(base_path): 

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

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

1379 return 

1380 

1381 # Un-hide the parent. 

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

1383 if base_type == vhdutil.VDI_TYPE_VHD: 

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

1385 if vhd_info.hidden: 

1386 self._vhdutil.set_hidden(base_path, False) 

1387 elif base_type == vhdutil.VDI_TYPE_RAW and \ 

1388 base_metadata.get(HIDDEN_TAG): 

1389 self._linstor.update_volume_metadata( 

1390 base_uuid, {HIDDEN_TAG: False} 

1391 ) 

1392 

1393 # Remove the child nodes. 

1394 if snap_uuid and snap_uuid in volume_names: 

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

1396 

1397 try: 

1398 self._linstor.destroy_volume(snap_uuid) 

1399 except Exception as e: 

1400 util.SMlog( 

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

1402 .format(snap_uuid, e) 

1403 ) 

1404 

1405 if vdi_uuid in volume_names: 

1406 try: 

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

1408 self._linstor.destroy_volume(vdi_uuid) 

1409 except Exception as e: 

1410 util.SMlog( 

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

1412 .format(vdi_uuid, e) 

1413 ) 

1414 # We can get an exception like this: 

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

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

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

1418 # UUID of this bad VDI before. 

1419 self._linstor.update_volume_uuid( 

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

1421 ) 

1422 

1423 # Rename! 

1424 self._linstor.update_volume_uuid(base_uuid, vdi_uuid) 

1425 

1426 # Inflate to the right size. 

1427 if base_type == vhdutil.VDI_TYPE_VHD: 

1428 vdi = self.vdi(vdi_uuid) 

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

1430 self._vhdutil.inflate( 

1431 self._journaler, vdi_uuid, vdi.path, 

1432 volume_size, vdi.capacity 

1433 ) 

1434 self.vdis[vdi_uuid] = vdi 

1435 

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

1437 # flag to facilitate vm deactivate. 

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

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

1440 

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

1442 

1443 # -------------------------------------------------------------------------- 

1444 # Cache. 

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

1446 

1447 def _create_linstor_cache(self): 

1448 reconnect = False 

1449 

1450 def create_cache(): 

1451 nonlocal reconnect 

1452 try: 

1453 if reconnect: 

1454 self._reconnect() 

1455 return self._linstor.get_volumes_with_info() 

1456 except Exception as e: 

1457 reconnect = True 

1458 raise e 

1459 

1460 self._all_volume_metadata_cache = \ 

1461 self._linstor.get_volumes_with_metadata() 

1462 self._all_volume_info_cache = util.retry( 

1463 create_cache, 

1464 maxretry=10, 

1465 period=3 

1466 ) 

1467 

1468 def _destroy_linstor_cache(self): 

1469 self._all_volume_info_cache = None 

1470 self._all_volume_metadata_cache = None 

1471 

1472 # -------------------------------------------------------------------------- 

1473 # Misc. 

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

1475 

1476 def _reconnect(self): 

1477 controller_uri = get_controller_uri() 

1478 

1479 self._journaler = LinstorJournaler( 

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

1481 ) 

1482 

1483 # Try to open SR if exists. 

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

1485 # we are trying to execute an exclusive operation. 

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

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

1488 # the SR is locked. 

1489 self._linstor = LinstorVolumeManager( 

1490 controller_uri, 

1491 self._group_name, 

1492 repair=( 

1493 self._is_master and 

1494 self.srcmd.cmd in self.ops_exclusive 

1495 ), 

1496 logger=util.SMlog 

1497 ) 

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

1499 

1500 def _ensure_space_available(self, amount_needed): 

1501 space_available = self._linstor.max_volume_size_allowed 

1502 if (space_available < amount_needed): 

1503 util.SMlog( 

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

1505 space_available, amount_needed 

1506 ) 

1507 ) 

1508 raise xs_errors.XenError('SRNoSpace') 

1509 

1510 def _kick_gc(self): 

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

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

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

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

1515 if not lock.acquireNoblock(): 

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

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

1518 return 

1519 

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

1521 try: 

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

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

1524 except util.CommandException as e: 

1525 if e.code != errno.ETIMEDOUT: 

1526 raise 

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

1528 else: 

1529 lock.release() 

1530 

1531 util.SMlog('Kicking GC') 

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

1533 

1534# ============================================================================== 

1535# LinstorSr VDI 

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

1537 

1538 

1539class LinstorVDI(VDI.VDI): 

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

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

1542 TYPE_RAW = 'raw' 

1543 TYPE_VHD = 'vhd' 

1544 

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

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

1547 # Increase the performance when resize is called. 

1548 MAX_METADATA_VIRT_SIZE = 2 * 1024 * 1024 

1549 

1550 # -------------------------------------------------------------------------- 

1551 # VDI methods. 

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

1553 

1554 def load(self, vdi_uuid): 

1555 self._lock = self.sr.lock 

1556 self._exists = True 

1557 self._linstor = self.sr._linstor 

1558 

1559 # Update hidden parent property. 

1560 self.hidden = False 

1561 

1562 def raise_bad_load(e): 

1563 util.SMlog( 

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

1565 ) 

1566 util.SMlog(traceback.format_exc()) 

1567 raise xs_errors.XenError( 

1568 'VDIUnavailable', 

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

1570 ) 

1571 

1572 # Try to load VDI. 

1573 try: 

1574 if ( 

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

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

1577 ): 

1578 self.vdi_type = vhdutil.VDI_TYPE_RAW 

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

1580 else: 

1581 self._determine_type_and_path() 

1582 self._load_this() 

1583 

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

1585 self.uuid, self.path, self.hidden 

1586 )) 

1587 except LinstorVolumeManagerError as e: 

1588 # 1. It may be a VDI deletion. 

1589 if e.code == LinstorVolumeManagerError.ERR_VOLUME_NOT_EXISTS: 

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

1591 self.deleted = True 

1592 return 

1593 

1594 # 2. Or maybe a creation. 

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

1596 # Set type attribute of VDI parent class. 

1597 # We use VHD by default. 

1598 self.vdi_type = vhdutil.VDI_TYPE_VHD 

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

1600 

1601 self._exists = False 

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

1603 if vdi_sm_config is not None: 

1604 type = vdi_sm_config.get('type') 

1605 if type is not None: 

1606 if type == self.TYPE_RAW: 

1607 self.vdi_type = vhdutil.VDI_TYPE_RAW 

1608 elif type == self.TYPE_VHD: 

1609 self.vdi_type = vhdutil.VDI_TYPE_VHD 

1610 else: 

1611 raise xs_errors.XenError( 

1612 'VDICreate', 

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

1614 ) 

1615 if self.vdi_type == vhdutil.VDI_TYPE_VHD: 

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

1617 

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

1619 self._update_device_name(None) 

1620 return 

1621 raise_bad_load(e) 

1622 except Exception as e: 

1623 raise_bad_load(e) 

1624 

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

1626 # Usage example: 

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

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

1629 

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

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

1632 if self._exists: 

1633 raise xs_errors.XenError('VDIExists') 

1634 

1635 assert self.uuid 

1636 assert self.ty 

1637 assert self.vdi_type 

1638 

1639 # 2. Compute size and check space available. 

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

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

1642 util.SMlog( 

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

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

1645 ) 

1646 self.sr._ensure_space_available(volume_size) 

1647 

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

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

1650 

1651 # 4. Create! 

1652 failed = False 

1653 try: 

1654 volume_name = None 

1655 if self.ty == 'ha_statefile': 

1656 volume_name = HA_VOLUME_NAME 

1657 elif self.ty == 'redo_log': 

1658 volume_name = REDO_LOG_VOLUME_NAME 

1659 

1660 self._linstor.create_volume( 

1661 self.uuid, volume_size, persistent=False, 

1662 volume_name=volume_name 

1663 ) 

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

1665 

1666 self._update_device_name(volume_info.name) 

1667 

1668 if self.vdi_type == vhdutil.VDI_TYPE_RAW: 

1669 self.size = volume_info.virtual_size 

1670 else: 

1671 self.sr._vhdutil.create( 

1672 self.path, size, False, self.MAX_METADATA_VIRT_SIZE 

1673 ) 

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

1675 

1676 if self._key_hash: 

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

1678 

1679 # Because vhdutil commands modify the volume data, 

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

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

1682 

1683 volume_metadata = { 

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

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

1686 IS_A_SNAPSHOT_TAG: False, 

1687 SNAPSHOT_OF_TAG: '', 

1688 SNAPSHOT_TIME_TAG: '', 

1689 TYPE_TAG: self.ty, 

1690 VDI_TYPE_TAG: self.vdi_type, 

1691 READ_ONLY_TAG: bool(self.read_only), 

1692 METADATA_OF_POOL_TAG: '' 

1693 } 

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

1695 

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

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

1698 # an already opened volume. 

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

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

1701 

1702 self._linstor.mark_volume_as_persistent(self.uuid) 

1703 except util.CommandException as e: 

1704 failed = True 

1705 raise xs_errors.XenError( 

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

1707 ) 

1708 except Exception as e: 

1709 failed = True 

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

1711 finally: 

1712 if failed: 

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

1714 try: 

1715 self._linstor.destroy_volume(self.uuid) 

1716 except Exception as e: 

1717 util.SMlog( 

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

1719 '{}'.format(e) 

1720 ) 

1721 

1722 self.utilisation = volume_info.allocated_size 

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

1724 

1725 self.ref = self._db_introduce() 

1726 self.sr._update_stats(self.size) 

1727 

1728 return VDI.VDI.get_params(self) 

1729 

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

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

1732 if self.attached: 

1733 raise xs_errors.XenError('VDIInUse') 

1734 

1735 if self.deleted: 

1736 return super(LinstorVDI, self).delete( 

1737 sr_uuid, vdi_uuid, data_only 

1738 ) 

1739 

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

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

1742 raise xs_errors.XenError( 

1743 'VDIDelete', 

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

1745 ) 

1746 

1747 try: 

1748 # Remove from XAPI and delete from LINSTOR. 

1749 self._linstor.destroy_volume(self.uuid) 

1750 if not data_only: 

1751 self._db_forget() 

1752 

1753 self.sr.lock.cleanupAll(vdi_uuid) 

1754 except Exception as e: 

1755 util.SMlog( 

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

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

1758 ) 

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

1760 

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

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

1763 

1764 # TODO: Check size after delete. 

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

1766 self.sr._kick_gc() 

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

1768 

1769 def attach(self, sr_uuid, vdi_uuid): 

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

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

1772 if ( 

1773 not attach_from_config or 

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

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

1776 raise xs_errors.XenError( 

1777 'VDIUnavailable', 

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

1779 'scan SR first to trigger auto-repair' 

1780 ) 

1781 

1782 if not attach_from_config or self.sr._is_master: 

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

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

1785 

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

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

1788 # than the VHD size + bitmap size. 

1789 need_inflate = True 

1790 if ( 

1791 self.vdi_type == vhdutil.VDI_TYPE_RAW or 

1792 not writable or 

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

1794 ): 

1795 need_inflate = False 

1796 

1797 if need_inflate: 

1798 try: 

1799 self._prepare_thin(True) 

1800 except Exception as e: 

1801 raise xs_errors.XenError( 

1802 'VDIUnavailable', 

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

1804 .format(e) 

1805 ) 

1806 

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

1808 self.xenstore_data = {} 

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

1810 

1811 if ( 

1812 USE_HTTP_NBD_SERVERS and 

1813 attach_from_config and 

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

1815 ): 

1816 return self._attach_using_http_nbd() 

1817 

1818 # Ensure we have a path... 

1819 while vdi_uuid: 

1820 path = self._linstor.get_device_path(vdi_uuid) 

1821 if not util.pathexists(path): 

1822 raise xs_errors.XenError( 

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

1824 ) 

1825 

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

1827 def check_volume_usable(): 

1828 while True: 

1829 try: 

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

1831 pass 

1832 except IOError as e: 

1833 if e.errno == errno.ENODATA: 

1834 time.sleep(2) 

1835 continue 

1836 if e.errno == errno.EROFS: 

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

1838 self.sr._linstor.get_volume_openers(vdi_uuid) 

1839 )) 

1840 raise 

1841 break 

1842 util.retry(check_volume_usable, 15, 2) 

1843 

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

1845 

1846 self.attached = True 

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

1848 

1849 def detach(self, sr_uuid, vdi_uuid): 

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

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

1852 self.attached = False 

1853 

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

1855 return self._detach_using_http_nbd() 

1856 

1857 if self.vdi_type == vhdutil.VDI_TYPE_RAW: 

1858 return 

1859 

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

1861 # equal to the LINSTOR volume size. 

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

1863 already_deflated = self.capacity <= volume_size 

1864 

1865 if already_deflated: 

1866 util.SMlog( 

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

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

1869 ) 

1870 

1871 need_deflate = True 

1872 if already_deflated: 

1873 need_deflate = False 

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

1875 need_deflate = False 

1876 

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

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

1879 need_deflate = True 

1880 

1881 if need_deflate: 

1882 try: 

1883 self._prepare_thin(False) 

1884 except Exception as e: 

1885 raise xs_errors.XenError( 

1886 'VDIUnavailable', 

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

1888 .format(e) 

1889 ) 

1890 

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

1892 if self.sr._is_master: 

1893 return 

1894 

1895 while vdi_uuid: 

1896 try: 

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

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

1899 except Exception: 

1900 break 

1901 

1902 if util.pathexists(path): 

1903 try: 

1904 self._linstor.remove_volume_if_diskless(vdi_uuid) 

1905 except Exception as e: 

1906 # Ensure we can always detach properly. 

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

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

1909 vdi_uuid = parent_vdi_uuid 

1910 

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

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

1913 if not self.sr._is_master: 

1914 raise xs_errors.XenError( 

1915 'VDISize', 

1916 opterr='resize on slave not allowed' 

1917 ) 

1918 

1919 if self.hidden: 

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

1921 

1922 # Compute the virtual VHD and DRBD volume size. 

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

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

1925 util.SMlog( 

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

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

1928 ) 

1929 

1930 if size < self.size: 

1931 util.SMlog( 

1932 'vdi_resize: shrinking not supported: ' 

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

1934 ) 

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

1936 

1937 if size == self.size: 

1938 return VDI.VDI.get_params(self) 

1939 

1940 if self.vdi_type == vhdutil.VDI_TYPE_RAW: 

1941 old_volume_size = self.size 

1942 new_volume_size = LinstorVolumeManager.round_up_volume_size(size) 

1943 else: 

1944 old_volume_size = self.utilisation 

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

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

1947 new_volume_size = old_volume_size 

1948 else: 

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

1950 assert new_volume_size >= old_volume_size 

1951 

1952 space_needed = new_volume_size - old_volume_size 

1953 self.sr._ensure_space_available(space_needed) 

1954 

1955 old_size = self.size 

1956 if self.vdi_type == vhdutil.VDI_TYPE_RAW: 

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

1958 else: 

1959 if new_volume_size != old_volume_size: 

1960 self.sr._vhdutil.inflate( 

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

1962 new_volume_size, old_volume_size 

1963 ) 

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

1965 

1966 # Reload size attributes. 

1967 self._load_this() 

1968 

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

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

1971 self.session.xenapi.VDI.set_physical_utilisation( 

1972 vdi_ref, str(self.utilisation) 

1973 ) 

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

1975 return VDI.VDI.get_params(self) 

1976 

1977 def clone(self, sr_uuid, vdi_uuid): 

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

1979 

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

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

1982 if self.vdi_type != vhdutil.VDI_TYPE_VHD: 

1983 raise xs_errors.XenError('Unimplemented') 

1984 

1985 parent_uuid = vdi1 

1986 parent_path = self._linstor.get_device_path(parent_uuid) 

1987 

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

1989 # have a readonly error. 

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

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

1992 

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

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

1995 try: 

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

1997 self.sr._vhdutil.set_hidden(parent_path) 

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

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

2000 ) 

2001 finally: 

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

2003 

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

2005 raise util.SMException( 

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

2007 ) 

2008 

2009 util.SMlog('Compose done') 

2010 

2011 def generate_config(self, sr_uuid, vdi_uuid): 

2012 """ 

2013 Generate the XML config required to attach and activate 

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

2015 activation is handled by vdi_attach_from_config below. 

2016 """ 

2017 

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

2019 

2020 resp = {} 

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

2022 resp['sr_uuid'] = sr_uuid 

2023 resp['vdi_uuid'] = self.uuid 

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

2025 resp['command'] = 'vdi_attach_from_config' 

2026 

2027 # By default, we generate a normal config. 

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

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

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

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

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

2033 # instead. 

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

2035 if not USE_HTTP_NBD_SERVERS or volume_name not in [ 

2036 HA_VOLUME_NAME, REDO_LOG_VOLUME_NAME 

2037 ]: 

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

2039 available = False 

2040 # Try to refresh symlink path... 

2041 try: 

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

2043 available = util.pathexists(self.path) 

2044 except Exception: 

2045 pass 

2046 if not available: 

2047 raise xs_errors.XenError('VDIUnavailable') 

2048 

2049 resp['vdi_path'] = self.path 

2050 else: 

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

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

2053 

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

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

2056 

2057 def attach_from_config(self, sr_uuid, vdi_uuid): 

2058 """ 

2059 Attach and activate a VDI using config generated by 

2060 vdi_generate_config above. This is used for cases such as 

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

2062 """ 

2063 

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

2065 

2066 try: 

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

2068 self.sr.attach(sr_uuid) 

2069 

2070 if not DRIVER_CONFIG['ATTACH_FROM_CONFIG_WITH_TAPDISK']: 

2071 return self.attach(sr_uuid, vdi_uuid) 

2072 except Exception: 

2073 util.logException('LinstorVDI.attach_from_config') 

2074 raise xs_errors.XenError( 

2075 'SRUnavailable', 

2076 opterr='Unable to attach from config' 

2077 ) 

2078 

2079 def reset_leaf(self, sr_uuid, vdi_uuid): 

2080 if self.vdi_type != vhdutil.VDI_TYPE_VHD: 

2081 raise xs_errors.XenError('Unimplemented') 

2082 

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

2084 raise util.SMException( 

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

2086 .format(self.uuid) 

2087 ) 

2088 

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

2090 

2091 def _load_this(self): 

2092 volume_metadata = None 

2093 if self.sr._all_volume_metadata_cache: 

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

2095 if volume_metadata is None: 

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

2097 

2098 volume_info = None 

2099 if self.sr._all_volume_info_cache: 

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

2101 if volume_info is None: 

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

2103 

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

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

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

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

2108 # be lower than virtual size at creation. 

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

2110 self.utilisation = volume_info.allocated_size 

2111 self.capacity = volume_info.virtual_size 

2112 

2113 if self.vdi_type == vhdutil.VDI_TYPE_RAW: 

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

2115 self.size = volume_info.virtual_size 

2116 self.parent = '' 

2117 else: 

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

2119 self.hidden = vhd_info.hidden 

2120 self.size = vhd_info.sizeVirt 

2121 self.parent = vhd_info.parentUuid 

2122 

2123 if self.hidden: 

2124 self.managed = False 

2125 

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

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

2128 

2129 # Update sm_config_override of VDI parent class. 

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

2131 

2132 def _mark_hidden(self, hidden=True): 

2133 if self.hidden == hidden: 

2134 return 

2135 

2136 if self.vdi_type == vhdutil.VDI_TYPE_VHD: 

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

2138 else: 

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

2140 HIDDEN_TAG: hidden 

2141 }) 

2142 self.hidden = hidden 

2143 

2144 def update(self, sr_uuid, vdi_uuid): 

2145 xenapi = self.session.xenapi 

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

2147 

2148 volume_metadata = { 

2149 NAME_LABEL_TAG: util.to_plain_string( 

2150 xenapi.VDI.get_name_label(vdi_ref) 

2151 ), 

2152 NAME_DESCRIPTION_TAG: util.to_plain_string( 

2153 xenapi.VDI.get_name_description(vdi_ref) 

2154 ) 

2155 } 

2156 

2157 try: 

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

2159 except LinstorVolumeManagerError as e: 

2160 if e.code == LinstorVolumeManagerError.ERR_VOLUME_NOT_EXISTS: 

2161 raise xs_errors.XenError( 

2162 'VDIUnavailable', 

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

2164 ) 

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

2166 

2167 # -------------------------------------------------------------------------- 

2168 # Thin provisioning. 

2169 # -------------------------------------------------------------------------- 

2170 

2171 def _prepare_thin(self, attach): 

2172 if self.sr._is_master: 

2173 if attach: 

2174 attach_thin( 

2175 self.session, self.sr._journaler, self._linstor, 

2176 self.sr.uuid, self.uuid 

2177 ) 

2178 else: 

2179 detach_thin( 

2180 self.session, self._linstor, self.sr.uuid, self.uuid 

2181 ) 

2182 else: 

2183 fn = 'attach' if attach else 'detach' 

2184 

2185 master = util.get_master_ref(self.session) 

2186 

2187 args = { 

2188 'groupName': self.sr._group_name, 

2189 'srUuid': self.sr.uuid, 

2190 'vdiUuid': self.uuid 

2191 } 

2192 

2193 try: 

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

2195 except Exception: 

2196 if fn != 'detach': 

2197 raise 

2198 

2199 # Reload size attrs after inflate or deflate! 

2200 self._load_this() 

2201 self.sr._update_physical_size() 

2202 

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

2204 self.session.xenapi.VDI.set_physical_utilisation( 

2205 vdi_ref, str(self.utilisation) 

2206 ) 

2207 

2208 self.session.xenapi.SR.set_physical_utilisation( 

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

2210 ) 

2211 

2212 # -------------------------------------------------------------------------- 

2213 # Generic helpers. 

2214 # -------------------------------------------------------------------------- 

2215 

2216 def _determine_type_and_path(self): 

2217 """ 

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

2219 """ 

2220 

2221 # 1. Check vdi_ref and vdi_type in config. 

2222 try: 

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

2224 if vdi_ref: 

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

2226 vdi_type = sm_config.get('vdi_type') 

2227 if vdi_type: 

2228 # Update parent fields. 

2229 self.vdi_type = vdi_type 

2230 self.sm_config_override = sm_config 

2231 self._update_device_name( 

2232 self._linstor.get_volume_name(self.uuid) 

2233 ) 

2234 return 

2235 except Exception: 

2236 pass 

2237 

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

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

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

2241 self.vdi_type = volume_metadata.get(VDI_TYPE_TAG) 

2242 if not self.vdi_type: 

2243 raise xs_errors.XenError( 

2244 'VDIUnavailable', 

2245 opterr='failed to get vdi_type in metadata' 

2246 ) 

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

2248 

2249 def _update_device_name(self, device_name): 

2250 self._device_name = device_name 

2251 

2252 # Mark path of VDI parent class. 

2253 if device_name: 

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

2255 else: 

2256 self.path = None 

2257 

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

2259 """ 

2260 Snapshot self and return the snapshot VDI object. 

2261 """ 

2262 

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

2264 snap_path = self._linstor.shallow_clone_volume( 

2265 self.uuid, snap_uuid, persistent=False 

2266 ) 

2267 

2268 # 2. Write the snapshot content. 

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

2270 self.sr._vhdutil.snapshot( 

2271 snap_path, self.path, is_raw, self.MAX_METADATA_VIRT_SIZE 

2272 ) 

2273 

2274 # 3. Get snapshot parent. 

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

2276 

2277 # 4. Update metadata. 

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

2279 volume_metadata = { 

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

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

2282 IS_A_SNAPSHOT_TAG: bool(snap_of_uuid), 

2283 SNAPSHOT_OF_TAG: snap_of_uuid, 

2284 SNAPSHOT_TIME_TAG: '', 

2285 TYPE_TAG: self.ty, 

2286 VDI_TYPE_TAG: vhdutil.VDI_TYPE_VHD, 

2287 READ_ONLY_TAG: False, 

2288 METADATA_OF_POOL_TAG: '' 

2289 } 

2290 self._linstor.set_volume_metadata(snap_uuid, volume_metadata) 

2291 

2292 # 5. Set size. 

2293 snap_vdi = LinstorVDI(self.sr, snap_uuid) 

2294 if not snap_vdi._exists: 

2295 raise xs_errors.XenError('VDISnapshot') 

2296 

2297 volume_info = self._linstor.get_volume_info(snap_uuid) 

2298 

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

2300 snap_vdi.utilisation = volume_info.allocated_size 

2301 

2302 # 6. Update sm config. 

2303 snap_vdi.sm_config = {} 

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

2305 if snap_parent: 

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

2307 snap_vdi.parent = snap_parent 

2308 

2309 snap_vdi.label = self.label 

2310 snap_vdi.description = self.description 

2311 

2312 self._linstor.mark_volume_as_persistent(snap_uuid) 

2313 

2314 return snap_vdi 

2315 

2316 # -------------------------------------------------------------------------- 

2317 # Implement specific SR methods. 

2318 # -------------------------------------------------------------------------- 

2319 

2320 def _rename(self, oldpath, newpath): 

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

2322 volume_uuid = self._linstor.get_volume_uuid_from_device_path(oldpath) 

2323 self._linstor.update_volume_name(volume_uuid, newpath) 

2324 

2325 def _do_snapshot( 

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

2327 ): 

2328 # If cbt enabled, save file consistency state. 

2329 if cbtlog is not None: 

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

2331 consistency_state = False 

2332 else: 

2333 consistency_state = True 

2334 util.SMlog( 

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

2336 .format(consistency_state, vdi_uuid) 

2337 ) 

2338 else: 

2339 consistency_state = None 

2340 

2341 if self.vdi_type != vhdutil.VDI_TYPE_VHD: 

2342 raise xs_errors.XenError('Unimplemented') 

2343 

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

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

2346 try: 

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

2348 finally: 

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

2350 

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

2352 util.SMlog( 

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

2354 .format(self.uuid, snap_type) 

2355 ) 

2356 

2357 # 1. Checks... 

2358 if self.hidden: 

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

2360 

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

2362 if depth == -1: 

2363 raise xs_errors.XenError( 

2364 'VDIUnavailable', 

2365 opterr='failed to get VHD depth' 

2366 ) 

2367 elif depth >= vhdutil.MAX_CHAIN_SIZE: 

2368 raise xs_errors.XenError('SnapshotChainTooLong') 

2369 

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

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

2372 

2373 volume_path = self.path 

2374 if not util.pathexists(volume_path): 

2375 raise xs_errors.XenError( 

2376 'EIO', 

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

2378 ) 

2379 

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

2381 base_uuid = util.gen_uuid() 

2382 snap_uuid = None 

2383 

2384 if snap_type == VDI.SNAPSHOT_DOUBLE: 

2385 snap_uuid = util.gen_uuid() 

2386 

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

2388 

2389 active_uuid = self.uuid 

2390 self.sr._journaler.create( 

2391 LinstorJournaler.CLONE, active_uuid, clone_info 

2392 ) 

2393 

2394 try: 

2395 # 3. Self becomes the new base. 

2396 # The device path remains the same. 

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

2398 self.uuid = base_uuid 

2399 self.location = self.uuid 

2400 self.read_only = True 

2401 self.managed = False 

2402 

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

2404 active_vdi = self._create_snapshot(active_uuid) 

2405 

2406 snap_vdi = None 

2407 if snap_type == VDI.SNAPSHOT_DOUBLE: 

2408 snap_vdi = self._create_snapshot(snap_uuid, active_uuid) 

2409 

2410 self.label = 'base copy' 

2411 self.description = '' 

2412 

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

2414 # in subsequent scans. 

2415 self._mark_hidden() 

2416 self._linstor.update_volume_metadata( 

2417 self.uuid, {READ_ONLY_TAG: True} 

2418 ) 

2419 

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

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

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

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

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

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

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

2427 active_vdi.sm_config[key] = sm_config[key] 

2428 

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

2430 # delete base if unused. 

2431 introduce_parent = True 

2432 try: 

2433 snap_parent = None 

2434 if snap_vdi: 

2435 snap_parent = snap_vdi.parent 

2436 

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

2438 snap_type == VDI.SNAPSHOT_SINGLE or 

2439 snap_type == VDI.SNAPSHOT_INTERNAL or 

2440 snap_parent != self.uuid 

2441 ): 

2442 util.SMlog( 

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

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

2445 ) 

2446 introduce_parent = False 

2447 self._linstor.destroy_volume(self.uuid) 

2448 except Exception as e: 

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

2450 pass 

2451 

2452 # 8. Introduce the new VDI records. 

2453 if snap_vdi: 

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

2455 # new snapshot disk. 

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

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

2458 # TODO: Maybe remove key_hash support. 

2459 if 'key_hash' in sm_config: 

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

2461 # If we have CBT enabled on the VDI, 

2462 # set CBT status for the new snapshot disk. 

2463 if cbtlog: 

2464 snap_vdi.cbt_enabled = True 

2465 

2466 if snap_vdi: 

2467 snap_vdi_ref = snap_vdi._db_introduce() 

2468 util.SMlog( 

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

2470 .format(snap_vdi_ref, snap_vdi.uuid) 

2471 ) 

2472 if introduce_parent: 

2473 base_vdi_ref = self._db_introduce() 

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

2475 util.SMlog( 

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

2477 .format(base_vdi_ref, self.uuid) 

2478 ) 

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

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

2481 NAME_DESCRIPTION_TAG: util.to_plain_string( 

2482 self.description 

2483 ), 

2484 READ_ONLY_TAG: True, 

2485 METADATA_OF_POOL_TAG: '' 

2486 }) 

2487 

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

2489 if snap_type == VDI.SNAPSHOT_DOUBLE and cbtlog: 

2490 try: 

2491 self._cbt_snapshot(snap_uuid, cbt_consistency) 

2492 except Exception: 

2493 # CBT operation failed. 

2494 # TODO: Implement me. 

2495 raise 

2496 

2497 if snap_type != VDI.SNAPSHOT_INTERNAL: 

2498 self.sr._update_stats(self.size) 

2499 

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

2501 ret_vdi = snap_vdi 

2502 if not ret_vdi: 

2503 ret_vdi = self 

2504 if not ret_vdi: 

2505 ret_vdi = active_vdi 

2506 

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

2508 self.session.xenapi.VDI.set_sm_config( 

2509 vdi_ref, active_vdi.sm_config 

2510 ) 

2511 except Exception as e: 

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

2513 try: 

2514 self.sr._handle_interrupted_clone( 

2515 active_uuid, clone_info, force_undo=True 

2516 ) 

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

2518 except Exception as e: 

2519 util.SMlog( 

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

2521 .format(e) 

2522 ) 

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

2524 

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

2526 

2527 return ret_vdi.get_params() 

2528 

2529 @staticmethod 

2530 def _start_persistent_http_server(volume_name): 

2531 pid_path = None 

2532 http_server = None 

2533 

2534 try: 

2535 if volume_name == HA_VOLUME_NAME: 

2536 port = '8076' 

2537 else: 

2538 port = '8077' 

2539 

2540 try: 

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

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

2543 # block indefinitely. 

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

2545 host_ip = util.get_this_host_address(session) 

2546 except: 

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

2548 host_ip, _ = get_ips_from_xha_config_file() 

2549 if not host_ip: 

2550 raise Exception( 

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

2552 ) 

2553 

2554 arguments = [ 

2555 'http-disk-server', 

2556 '--disk', 

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

2558 '--ip', 

2559 host_ip, 

2560 '--port', 

2561 port 

2562 ] 

2563 

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

2565 http_server = subprocess.Popen( 

2566 [FORK_LOG_DAEMON] + arguments, 

2567 stdout=subprocess.PIPE, 

2568 stderr=subprocess.STDOUT, 

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

2570 # touch the current one. 

2571 preexec_fn=os.setsid 

2572 ) 

2573 

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

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

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

2577 

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

2579 def is_ready(): 

2580 while http_server.poll() is None: 

2581 line = http_server.stdout.readline() 

2582 if reg_server_ready.search(line): 

2583 return True 

2584 return False 

2585 try: 

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

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

2588 except util.TimeoutException: 

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

2590 except Exception as e: 

2591 if pid_path: 

2592 try: 

2593 os.remove(pid_path) 

2594 except Exception: 

2595 pass 

2596 

2597 if http_server: 

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

2599 try: 

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

2601 except: 

2602 pass 

2603 

2604 raise xs_errors.XenError( 

2605 'VDIUnavailable', 

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

2607 ) 

2608 

2609 def _start_persistent_nbd_server(self, volume_name): 

2610 pid_path = None 

2611 nbd_path = None 

2612 nbd_server = None 

2613 

2614 try: 

2615 # We use a precomputed device size. 

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

2617 if volume_name == HA_VOLUME_NAME: 

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

2619 port = '8076' 

2620 device_size = 4 * 1024 * 1024 

2621 else: 

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

2623 port = '8077' 

2624 device_size = 256 * 1024 * 1024 

2625 

2626 try: 

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

2628 ips = util.get_host_addresses(session) 

2629 except Exception as e: 

2630 _, ips = get_ips_from_xha_config_file() 

2631 if not ips: 

2632 raise Exception( 

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

2634 ) 

2635 ips = ips.values() 

2636 

2637 arguments = [ 

2638 'nbd-http-server', 

2639 '--socket-path', 

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

2641 '--nbd-name', 

2642 volume_name, 

2643 '--urls', 

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

2645 '--device-size', 

2646 str(device_size) 

2647 ] 

2648 

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

2650 nbd_server = subprocess.Popen( 

2651 [FORK_LOG_DAEMON] + arguments, 

2652 stdout=subprocess.PIPE, 

2653 stderr=subprocess.STDOUT, 

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

2655 # touch the current one. 

2656 preexec_fn=os.setsid 

2657 ) 

2658 

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

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

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

2662 

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

2664 def get_nbd_path(): 

2665 while nbd_server.poll() is None: 

2666 line = nbd_server.stdout.readline() 

2667 match = reg_nbd_path.search(line) 

2668 if match: 

2669 return match.group(1) 

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

2671 try: 

2672 nbd_path = util.timeout_call(10, get_nbd_path) 

2673 if nbd_path is None: 

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

2675 except util.TimeoutException: 

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

2677 

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

2679 os.symlink(nbd_path, self.path) 

2680 except Exception as e: 

2681 if pid_path: 

2682 try: 

2683 os.remove(pid_path) 

2684 except Exception: 

2685 pass 

2686 

2687 if nbd_path: 

2688 try: 

2689 os.remove(nbd_path) 

2690 except Exception: 

2691 pass 

2692 

2693 if nbd_server: 

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

2695 try: 

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

2697 except: 

2698 pass 

2699 

2700 raise xs_errors.XenError( 

2701 'VDIUnavailable', 

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

2703 ) 

2704 

2705 @classmethod 

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

2707 try: 

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

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

2710 return 

2711 

2712 pid = None 

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

2714 try: 

2715 pid = int(pid_file.read()) 

2716 except Exception: 

2717 pass 

2718 

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

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

2721 try: 

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

2723 except Exception as e: 

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

2725 

2726 os.remove(path) 

2727 except: 

2728 pass 

2729 

2730 @classmethod 

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

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

2733 

2734 @classmethod 

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

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

2737 

2738 def _check_http_nbd_volume_name(self): 

2739 volume_name = self.path[14:] 

2740 if volume_name not in [ 

2741 HA_VOLUME_NAME, REDO_LOG_VOLUME_NAME 

2742 ]: 

2743 raise xs_errors.XenError( 

2744 'VDIUnavailable', 

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

2746 ) 

2747 return volume_name 

2748 

2749 def _attach_using_http_nbd(self): 

2750 volume_name = self._check_http_nbd_volume_name() 

2751 

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

2753 self._kill_persistent_nbd_server(volume_name) 

2754 self._kill_persistent_http_server(volume_name) 

2755 

2756 # 0. Fetch drbd path. 

2757 must_get_device_path = True 

2758 if not self.sr._is_master: 

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

2760 try: 

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

2762 except Exception as e: 

2763 raise xs_errors.XenError( 

2764 'VDIUnavailable', 

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

2766 .format(self.uuid, e) 

2767 ) 

2768 

2769 hostname = socket.gethostname() 

2770 must_get_device_path = hostname in volume_info.diskful 

2771 

2772 drbd_path = None 

2773 if must_get_device_path or self.sr._is_master: 

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

2775 # or diskful available to init HA. 

2776 # It also avoid this error in xensource.log 

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

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

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

2780 available = False 

2781 try: 

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

2783 available = util.pathexists(drbd_path) 

2784 except Exception: 

2785 pass 

2786 

2787 if not available: 

2788 raise xs_errors.XenError( 

2789 'VDIUnavailable', 

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

2791 ) 

2792 

2793 # 1. Prepare http-nbd folder. 

2794 try: 

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

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

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

2798 os.remove(self.path) 

2799 except OSError as e: 

2800 if e.errno != errno.EEXIST: 

2801 raise xs_errors.XenError( 

2802 'VDIUnavailable', 

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

2804 ) 

2805 

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

2807 http_service = None 

2808 if drbd_path: 

2809 assert(drbd_path in ( 

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

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

2812 )) 

2813 self._start_persistent_http_server(volume_name) 

2814 

2815 # 3. Start NBD server in all cases. 

2816 try: 

2817 self._start_persistent_nbd_server(volume_name) 

2818 except Exception as e: 

2819 if drbd_path: 

2820 self._kill_persistent_http_server(volume_name) 

2821 raise 

2822 

2823 self.attached = True 

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

2825 

2826 def _detach_using_http_nbd(self): 

2827 volume_name = self._check_http_nbd_volume_name() 

2828 self._kill_persistent_nbd_server(volume_name) 

2829 self._kill_persistent_http_server(volume_name) 

2830 

2831# ------------------------------------------------------------------------------ 

2832 

2833 

2834if __name__ == '__main__': 

2835 def run(): 

2836 SRCommand.run(LinstorSR, DRIVER_INFO) 

2837 

2838 if not TRACE_PERFS: 

2839 run() 

2840 else: 

2841 util.make_profile('LinstorSR', run) 

2842else: 

2843 SR.registerSR(LinstorSR)