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, already used by PBD {}'.format( 

573 xenapi.PBD.get_uuid(pbd) 

574 ) 

575 ) 

576 

577 if srs: 

578 raise xs_errors.XenError( 

579 'LinstorSRCreate', 

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

581 ) 

582 

583 online_hosts = util.get_online_hosts(self.session) 

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

585 raise xs_errors.XenError( 

586 'LinstorSRCreate', 

587 opterr='Not enough online hosts' 

588 ) 

589 

590 ips = {} 

591 for host_ref in online_hosts: 

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

593 hostname = record['hostname'] 

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

595 

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

597 raise xs_errors.XenError( 

598 'LinstorSRCreate', 

599 opterr='Multiple hosts with same hostname' 

600 ) 

601 

602 # Ensure ports are opened and LINSTOR satellites 

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

604 # must be stopped. 

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

606 

607 # Create SR. 

608 # Throw if the SR already exists. 

609 try: 

610 self._linstor = LinstorVolumeManager.create_sr( 

611 self._group_name, 

612 ips, 

613 self._redundancy, 

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

615 auto_quorum=self._monitor_db_quorum, 

616 logger=util.SMlog 

617 ) 

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

619 except Exception as e: 

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

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

622 

623 try: 

624 util.SMlog( 

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

626 ) 

627 self._update_drbd_reactor_on_all_hosts(enabled=True) 

628 except Exception as e: 

629 try: 

630 self._linstor.destroy() 

631 except Exception as e2: 

632 util.SMlog( 

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

634 .format(e2) 

635 ) 

636 raise e 

637 

638 @_locked_load 

639 def delete(self, uuid): 

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

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

642 

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

644 raise xs_errors.XenError('SRNotEmpty') 

645 

646 node_name = get_controller_node_name() 

647 if not node_name: 

648 raise xs_errors.XenError( 

649 'LinstorSRDelete', 

650 opterr='Cannot get controller node name' 

651 ) 

652 

653 host = None 

654 if node_name == 'localhost': 

655 host = util.get_this_host_ref(self.session) 

656 else: 

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

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

659 if r_name == node_name: 

660 host = slave 

661 break 

662 

663 if not host: 

664 raise xs_errors.XenError( 

665 'LinstorSRDelete', 

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

667 node_name 

668 ) 

669 ) 

670 

671 try: 

672 self._update_drbd_reactor_on_all_hosts( 

673 controller_node_name=node_name, enabled=False 

674 ) 

675 

676 args = { 

677 'groupName': self._group_name, 

678 } 

679 self._exec_manager_command( 

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

681 ) 

682 except Exception as e: 

683 try: 

684 self._update_drbd_reactor_on_all_hosts( 

685 controller_node_name=node_name, enabled=True 

686 ) 

687 except Exception as e2: 

688 util.SMlog( 

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

690 .format(e2) 

691 ) 

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

693 raise xs_errors.XenError( 

694 'LinstorSRDelete', 

695 opterr=str(e) 

696 ) 

697 

698 Lock.cleanupAll(self.uuid) 

699 

700 @_locked_load 

701 def update(self, uuid): 

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

703 

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

705 if not self._linstor: 

706 raise xs_errors.XenError( 

707 'SRUnavailable', 

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

709 ) 

710 

711 self._update_stats(0) 

712 

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

714 xenapi = self.session.xenapi 

715 self._linstor.metadata = { 

716 NAME_LABEL_TAG: util.to_plain_string( 

717 xenapi.SR.get_name_label(self.sr_ref) 

718 ), 

719 NAME_DESCRIPTION_TAG: util.to_plain_string( 

720 xenapi.SR.get_name_description(self.sr_ref) 

721 ) 

722 } 

723 

724 @_locked_load 

725 def attach(self, uuid): 

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

727 

728 if not self._linstor: 

729 raise xs_errors.XenError( 

730 'SRUnavailable', 

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

732 ) 

733 

734 @_locked_load 

735 def detach(self, uuid): 

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

737 cleanup.abort(self.uuid) 

738 

739 @_locked_load 

740 def probe(self): 

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

742 # TODO 

743 

744 @_locked_load 

745 def scan(self, uuid): 

746 if self._init_status == self.INIT_STATUS_FAIL: 

747 return 

748 

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

750 if not self._linstor: 

751 raise xs_errors.XenError( 

752 'SRUnavailable', 

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

754 ) 

755 

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

757 # are loaded. 

758 self._load_vdis() 

759 self._update_physical_size() 

760 

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

762 if self.vdis[vdi_uuid].deleted: 

763 del self.vdis[vdi_uuid] 

764 

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

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

767 try: 

768 self._linstor.get_database_path() 

769 except Exception: 

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

771 # VDIs in the XAPI database... 

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

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

774 ): 

775 raise xs_errors.XenError( 

776 'SRUnavailable', 

777 opterr='Database is not mounted' 

778 ) 

779 

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

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

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

783 self._kick_gc() 

784 

785 @_locked_load 

786 def vdi(self, uuid): 

787 return LinstorVDI(self, uuid) 

788 

789 _locked_load = staticmethod(_locked_load) 

790 

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

792 # Lock. 

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

794 

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

796 master = util.get_master_ref(self.session) 

797 

798 command = 'lockVdi' 

799 args = { 

800 'groupName': self._group_name, 

801 'srUuid': self.uuid, 

802 'vdiUuid': vdi_uuid, 

803 'locked': str(locked) 

804 } 

805 

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

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

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

809 # 

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

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

812 # to implement without impacting performance. 

813 if not locked: 

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

815 timeout = LinstorVolumeManager.LOCKED_EXPIRATION_DELAY * 0.7 

816 if elapsed_time >= timeout: 

817 util.SMlog( 

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

819 .format(vdi_uuid) 

820 ) 

821 return 

822 

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

824 

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

826 # Network. 

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

828 

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

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

831 host_uuid = host_rec['uuid'] 

832 

833 try: 

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

835 host_ref, self.MANAGER_PLUGIN, command, args 

836 ) 

837 except Exception as e: 

838 util.SMlog( 

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

840 host_uuid, self.MANAGER_PLUGIN, command, args 

841 ) 

842 ) 

843 raise e 

844 

845 util.SMlog( 

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

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

848 ) 

849 ) 

850 if ret == 'False': 

851 raise xs_errors.XenError( 

852 error, 

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

854 ) 

855 

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

857 self._exec_manager_command( 

858 host, 

859 'prepareSr' if enabled else 'releaseSr', 

860 {'groupName': group_name}, 

861 'SRUnavailable' 

862 ) 

863 

864 def _prepare_sr_on_all_hosts(self, group_name, enabled): 

865 master = util.get_master_ref(self.session) 

866 self._prepare_sr(master, group_name, enabled) 

867 

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

869 self._prepare_sr(slave, group_name, enabled) 

870 

871 def _update_drbd_reactor(self, host, enabled): 

872 self._exec_manager_command( 

873 host, 

874 'updateDrbdReactor', 

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

876 'SRUnavailable' 

877 ) 

878 

879 def _update_drbd_reactor_on_all_hosts( 

880 self, enabled, controller_node_name=None 

881 ): 

882 if controller_node_name == 'localhost': 

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

884 util.get_this_host_ref(self.session) 

885 )['hostname'] 

886 assert controller_node_name 

887 assert controller_node_name != 'localhost' 

888 

889 controller_host = None 

890 secondary_hosts = [] 

891 

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

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

894 hostname = host_rec['hostname'] 

895 if controller_node_name == hostname: 

896 controller_host = host_ref 

897 else: 

898 secondary_hosts.append((host_ref, hostname)) 

899 

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

901 if controller_node_name and not controller_host: 

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

903 controller_node_name 

904 )) 

905 

906 if enabled and controller_host: 

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

908 action_name, controller_node_name 

909 )) 

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

911 # node name first. 

912 self._update_drbd_reactor(controller_host, enabled) 

913 

914 for host_ref, hostname in secondary_hosts: 

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

916 action_name, hostname 

917 )) 

918 self._update_drbd_reactor(host_ref, enabled) 

919 

920 if not enabled and controller_host: 

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

922 action_name, controller_node_name 

923 )) 

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

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

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

927 self._update_drbd_reactor(controller_host, enabled) 

928 

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

930 # Metadata. 

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

932 

933 def _synchronize_metadata_and_xapi(self): 

934 try: 

935 # First synch SR parameters. 

936 self.update(self.uuid) 

937 

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

939 xenapi = self.session.xenapi 

940 volumes_metadata = self._linstor.get_volumes_with_metadata() 

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

942 try: 

943 vdi_ref = xenapi.VDI.get_by_uuid(vdi_uuid) 

944 except Exception: 

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

946 continue 

947 

948 label = util.to_plain_string( 

949 xenapi.VDI.get_name_label(vdi_ref) 

950 ) 

951 description = util.to_plain_string( 

952 xenapi.VDI.get_name_description(vdi_ref) 

953 ) 

954 

955 if ( 

956 volume_metadata.get(NAME_LABEL_TAG) != label or 

957 volume_metadata.get(NAME_DESCRIPTION_TAG) != description 

958 ): 

959 self._linstor.update_volume_metadata(vdi_uuid, { 

960 NAME_LABEL_TAG: label, 

961 NAME_DESCRIPTION_TAG: description 

962 }) 

963 except Exception as e: 

964 raise xs_errors.XenError( 

965 'MetadataError', 

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

967 ) 

968 

969 def _synchronize_metadata(self): 

970 if not self._is_master: 

971 return 

972 

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

974 if self.cmd == 'sr_attach': 

975 try: 

976 util.SMlog( 

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

978 ) 

979 self._synchronize_metadata_and_xapi() 

980 except Exception as e: 

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

982 

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

984 # Stats. 

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

986 

987 def _update_stats(self, virt_alloc_delta): 

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

989 self.sr_ref 

990 )) 

991 

992 # Update size attributes of the SR parent class. 

993 self.virtual_allocation = valloc + virt_alloc_delta 

994 

995 self._update_physical_size() 

996 

997 # Notify SR parent class. 

998 self._db_update() 

999 

1000 def _update_physical_size(self): 

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

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

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

1004 self.physical_size = min_physical_size * pool_count // \ 

1005 self._linstor.redundancy 

1006 

1007 self.physical_utilisation = self._linstor.allocated_volume_size 

1008 

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

1010 # VDIs. 

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

1012 

1013 def _load_vdis(self): 

1014 if self._vdis_loaded: 

1015 return 

1016 

1017 assert self._is_master 

1018 

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

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

1021 # enjoy it with a few lines. 

1022 self._create_linstor_cache() 

1023 self._load_vdis_ex() 

1024 self._destroy_linstor_cache() 

1025 

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

1027 self._vdis_loaded = True 

1028 

1029 self._undo_all_journal_transactions() 

1030 

1031 def _load_vdis_ex(self): 

1032 # 1. Get existing VDIs in XAPI. 

1033 xenapi = self.session.xenapi 

1034 xapi_vdi_uuids = set() 

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

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

1037 

1038 # 2. Get volumes info. 

1039 all_volume_info = self._all_volume_info_cache 

1040 volumes_metadata = self._all_volume_metadata_cache 

1041 

1042 # 3. Get CBT vdis. 

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

1044 cbt_vdis = set() 

1045 for volume_metadata in volumes_metadata.values(): 

1046 cbt_uuid = volume_metadata.get(CBTLOG_TAG) 

1047 if cbt_uuid: 

1048 cbt_vdis.add(cbt_uuid) 

1049 

1050 introduce = False 

1051 

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

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

1054 has_clone_entries = list(self._journaler.get_all( 

1055 LinstorJournaler.CLONE 

1056 ).items()) 

1057 

1058 if has_clone_entries: 

1059 util.SMlog( 

1060 'Cannot introduce VDIs during scan because it exists ' 

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

1062 ) 

1063 else: 

1064 introduce = True 

1065 

1066 # 4. Now check all volume info. 

1067 vdi_to_snaps = {} 

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

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

1070 continue 

1071 

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

1073 if vdi_uuid not in xapi_vdi_uuids: 

1074 if not introduce: 

1075 continue 

1076 

1077 if vdi_uuid.startswith('DELETED_'): 

1078 continue 

1079 

1080 volume_metadata = volumes_metadata.get(vdi_uuid) 

1081 if not volume_metadata: 

1082 util.SMlog( 

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

1084 .format(vdi_uuid) 

1085 ) 

1086 continue 

1087 

1088 util.SMlog( 

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

1090 'LINSTOR and not in XAPI...' 

1091 .format(vdi_uuid) 

1092 ) 

1093 

1094 try: 

1095 self._linstor.get_device_path(vdi_uuid) 

1096 except Exception as e: 

1097 util.SMlog( 

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

1099 .format(vdi_uuid, e) 

1100 ) 

1101 continue 

1102 

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

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

1105 vdi_type = volume_metadata.get(VDI_TYPE_TAG) 

1106 

1107 if not vdi_type: 

1108 util.SMlog( 

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

1110 'without vdi_type' 

1111 ) 

1112 continue 

1113 

1114 sm_config = { 

1115 'vdi_type': vdi_type 

1116 } 

1117 

1118 if vdi_type == vhdutil.VDI_TYPE_RAW: 

1119 managed = not volume_metadata.get(HIDDEN_TAG) 

1120 elif vdi_type == vhdutil.VDI_TYPE_VHD: 

1121 vhd_info = self._vhdutil.get_vhd_info(vdi_uuid) 

1122 managed = not vhd_info.hidden 

1123 if vhd_info.parentUuid: 

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

1125 else: 

1126 util.SMlog( 

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

1128 .format(vdi_uuid, vdi_type) 

1129 ) 

1130 continue 

1131 

1132 util.SMlog( 

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

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

1135 name_label, 

1136 volume_info.virtual_size, 

1137 volume_info.allocated_size 

1138 ) 

1139 ) 

1140 

1141 vdi_ref = xenapi.VDI.db_introduce( 

1142 vdi_uuid, 

1143 name_label, 

1144 volume_metadata.get(NAME_DESCRIPTION_TAG) or '', 

1145 self.sr_ref, 

1146 type, 

1147 False, # sharable 

1148 bool(volume_metadata.get(READ_ONLY_TAG)), 

1149 {}, # other_config 

1150 vdi_uuid, # location 

1151 {}, # xenstore_data 

1152 sm_config, 

1153 managed, 

1154 str(volume_info.virtual_size), 

1155 str(volume_info.allocated_size) 

1156 ) 

1157 

1158 is_a_snapshot = volume_metadata.get(IS_A_SNAPSHOT_TAG) 

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

1160 if is_a_snapshot: 

1161 xenapi.VDI.set_snapshot_time( 

1162 vdi_ref, 

1163 xmlrpc.client.DateTime( 

1164 volume_metadata[SNAPSHOT_TIME_TAG] or 

1165 '19700101T00:00:00Z' 

1166 ) 

1167 ) 

1168 

1169 snap_uuid = volume_metadata[SNAPSHOT_OF_TAG] 

1170 if snap_uuid in vdi_to_snaps: 

1171 vdi_to_snaps[snap_uuid].append(vdi_uuid) 

1172 else: 

1173 vdi_to_snaps[snap_uuid] = [vdi_uuid] 

1174 

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

1176 vdi = self.vdi(vdi_uuid) 

1177 self.vdis[vdi_uuid] = vdi 

1178 

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

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

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

1182 vdi.sm_config_override['key_hash'] = \ 

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

1184 

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

1186 # or already in XAPI. 

1187 cbt_uuid = volume_metadata.get(CBTLOG_TAG) 

1188 if cbt_uuid in cbt_vdis: 

1189 vdi_ref = xenapi.VDI.get_by_uuid(vdi_uuid) 

1190 xenapi.VDI.set_cbt_enabled(vdi_ref, True) 

1191 # For existing VDIs, update local state too. 

1192 # Scan in base class SR updates existing VDIs 

1193 # again based on local states. 

1194 self.vdis[vdi_uuid].cbt_enabled = True 

1195 cbt_vdis.remove(cbt_uuid) 

1196 

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

1198 for src_uuid in vdi_to_snaps: 

1199 try: 

1200 src_ref = xenapi.VDI.get_by_uuid(src_uuid) 

1201 except Exception: 

1202 # The source VDI no longer exists, continue. 

1203 continue 

1204 

1205 for snap_uuid in vdi_to_snaps[src_uuid]: 

1206 try: 

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

1208 snap_ref = xenapi.VDI.get_by_uuid(snap_uuid) 

1209 xenapi.VDI.set_snapshot_of(snap_ref, src_ref) 

1210 except Exception as e: 

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

1212 

1213 # TODO: Check correctly how to use CBT. 

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

1215 

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

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

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

1219 for cbt_uuid in cbt_vdis: 

1220 new_vdi = self.vdi(cbt_uuid) 

1221 new_vdi.ty = 'cbt_metadata' 

1222 new_vdi.cbt_enabled = True 

1223 self.vdis[cbt_uuid] = new_vdi 

1224 

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

1226 self.virtual_allocation = 0 

1227 

1228 # 8. Build geneology. 

1229 geneology = {} 

1230 

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

1232 if vdi.parent: 

1233 if vdi.parent in self.vdis: 

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

1235 if vdi.parent in geneology: 

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

1237 else: 

1238 geneology[vdi.parent] = [vdi_uuid] 

1239 if not vdi.hidden: 

1240 self.virtual_allocation += vdi.size 

1241 

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

1243 # will be GC'ed. 

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

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

1246 util.SMlog( 

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

1248 ) 

1249 del self.vdis[vdi_uuid] 

1250 

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

1252 # Journals. 

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

1254 

1255 def _get_vdi_path_and_parent(self, vdi_uuid, volume_name): 

1256 try: 

1257 device_path = self._linstor.build_device_path(volume_name) 

1258 if not util.pathexists(device_path): 

1259 return (None, None) 

1260 

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

1262 volume_metadata = self._linstor.get_volume_metadata(vdi_uuid) 

1263 vdi_type = volume_metadata[VDI_TYPE_TAG] 

1264 if vdi_type == vhdutil.VDI_TYPE_RAW: 

1265 return (device_path, None) 

1266 

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

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

1269 return (None, None) 

1270 

1271 vhd_info = self._vhdutil.get_vhd_info(vdi_uuid) 

1272 if vhd_info: 

1273 return (device_path, vhd_info.parentUuid) 

1274 except Exception as e: 

1275 util.SMlog( 

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

1277 .format(e) 

1278 ) 

1279 return (None, None) 

1280 

1281 def _undo_all_journal_transactions(self): 

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

1283 self.lock.acquire() 

1284 try: 

1285 self._handle_interrupted_inflate_ops() 

1286 self._handle_interrupted_clone_ops() 

1287 pass 

1288 finally: 

1289 self.lock.release() 

1290 

1291 def _handle_interrupted_inflate_ops(self): 

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

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

1294 self._handle_interrupted_inflate(vdi_uuid, old_size) 

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

1296 

1297 def _handle_interrupted_clone_ops(self): 

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

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

1300 self._handle_interrupted_clone(vdi_uuid, old_size) 

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

1302 

1303 def _handle_interrupted_inflate(self, vdi_uuid, old_size): 

1304 util.SMlog( 

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

1306 .format(vdi_uuid, old_size) 

1307 ) 

1308 

1309 vdi = self.vdis.get(vdi_uuid) 

1310 if not vdi: 

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

1312 return 

1313 

1314 assert not self._all_volume_info_cache 

1315 volume_info = self._linstor.get_volume_info(vdi_uuid) 

1316 

1317 current_size = volume_info.virtual_size 

1318 assert current_size > 0 

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

1320 

1321 def _handle_interrupted_clone( 

1322 self, vdi_uuid, clone_info, force_undo=False 

1323 ): 

1324 util.SMlog( 

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

1326 .format(vdi_uuid, clone_info) 

1327 ) 

1328 

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

1330 

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

1332 volume_names = self._linstor.get_volumes_with_name() 

1333 

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

1335 if base_uuid not in volume_names: 

1336 if vdi_uuid in volume_names: 

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

1338 return 

1339 raise util.SMException( 

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

1341 .format(base_uuid, vdi_uuid) 

1342 ) 

1343 

1344 if force_undo: 

1345 util.SMlog('Explicit revert') 

1346 self._undo_clone( 

1347 volume_names, vdi_uuid, base_uuid, snap_uuid 

1348 ) 

1349 return 

1350 

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

1352 if vdi_uuid not in volume_names or \ 

1353 (snap_uuid and snap_uuid not in volume_names): 

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

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

1356 return 

1357 

1358 vdi_path, vdi_parent_uuid = self._get_vdi_path_and_parent( 

1359 vdi_uuid, volume_names[vdi_uuid] 

1360 ) 

1361 snap_path, snap_parent_uuid = self._get_vdi_path_and_parent( 

1362 snap_uuid, volume_names[snap_uuid] 

1363 ) 

1364 

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

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

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

1368 return 

1369 

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

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

1372 

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

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

1375 base_metadata = self._linstor.get_volume_metadata(base_uuid) 

1376 base_type = base_metadata[VDI_TYPE_TAG] 

1377 

1378 if not util.pathexists(base_path): 

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

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

1381 return 

1382 

1383 # Un-hide the parent. 

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

1385 if base_type == vhdutil.VDI_TYPE_VHD: 

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

1387 if vhd_info.hidden: 

1388 self._vhdutil.set_hidden(base_path, False) 

1389 elif base_type == vhdutil.VDI_TYPE_RAW and \ 

1390 base_metadata.get(HIDDEN_TAG): 

1391 self._linstor.update_volume_metadata( 

1392 base_uuid, {HIDDEN_TAG: False} 

1393 ) 

1394 

1395 # Remove the child nodes. 

1396 if snap_uuid and snap_uuid in volume_names: 

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

1398 

1399 try: 

1400 self._linstor.destroy_volume(snap_uuid) 

1401 except Exception as e: 

1402 util.SMlog( 

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

1404 .format(snap_uuid, e) 

1405 ) 

1406 

1407 if vdi_uuid in volume_names: 

1408 try: 

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

1410 self._linstor.destroy_volume(vdi_uuid) 

1411 except Exception as e: 

1412 util.SMlog( 

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

1414 .format(vdi_uuid, e) 

1415 ) 

1416 # We can get an exception like this: 

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

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

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

1420 # UUID of this bad VDI before. 

1421 self._linstor.update_volume_uuid( 

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

1423 ) 

1424 

1425 # Rename! 

1426 self._linstor.update_volume_uuid(base_uuid, vdi_uuid) 

1427 

1428 # Inflate to the right size. 

1429 if base_type == vhdutil.VDI_TYPE_VHD: 

1430 vdi = self.vdi(vdi_uuid) 

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

1432 self._vhdutil.inflate( 

1433 self._journaler, vdi_uuid, vdi.path, 

1434 volume_size, vdi.capacity 

1435 ) 

1436 self.vdis[vdi_uuid] = vdi 

1437 

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

1439 # flag to facilitate vm deactivate. 

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

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

1442 

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

1444 

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

1446 # Cache. 

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

1448 

1449 def _create_linstor_cache(self): 

1450 reconnect = False 

1451 

1452 def create_cache(): 

1453 nonlocal reconnect 

1454 try: 

1455 if reconnect: 

1456 self._reconnect() 

1457 return self._linstor.get_volumes_with_info() 

1458 except Exception as e: 

1459 reconnect = True 

1460 raise e 

1461 

1462 self._all_volume_metadata_cache = \ 

1463 self._linstor.get_volumes_with_metadata() 

1464 self._all_volume_info_cache = util.retry( 

1465 create_cache, 

1466 maxretry=10, 

1467 period=3 

1468 ) 

1469 

1470 def _destroy_linstor_cache(self): 

1471 self._all_volume_info_cache = None 

1472 self._all_volume_metadata_cache = None 

1473 

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

1475 # Misc. 

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

1477 

1478 def _reconnect(self): 

1479 controller_uri = get_controller_uri() 

1480 

1481 self._journaler = LinstorJournaler( 

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

1483 ) 

1484 

1485 # Try to open SR if exists. 

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

1487 # we are trying to execute an exclusive operation. 

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

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

1490 # the SR is locked. 

1491 self._linstor = LinstorVolumeManager( 

1492 controller_uri, 

1493 self._group_name, 

1494 repair=( 

1495 self._is_master and 

1496 self.srcmd.cmd in self.ops_exclusive 

1497 ), 

1498 logger=util.SMlog 

1499 ) 

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

1501 

1502 def _ensure_space_available(self, amount_needed): 

1503 space_available = self._linstor.max_volume_size_allowed 

1504 if (space_available < amount_needed): 

1505 util.SMlog( 

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

1507 space_available, amount_needed 

1508 ) 

1509 ) 

1510 raise xs_errors.XenError('SRNoSpace') 

1511 

1512 def _kick_gc(self): 

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

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

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

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

1517 if not lock.acquireNoblock(): 

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

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

1520 return 

1521 

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

1523 try: 

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

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

1526 except util.CommandException as e: 

1527 if e.code != errno.ETIMEDOUT: 

1528 raise 

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

1530 else: 

1531 lock.release() 

1532 

1533 util.SMlog('Kicking GC') 

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

1535 

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

1537# LinstorSr VDI 

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

1539 

1540 

1541class LinstorVDI(VDI.VDI): 

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

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

1544 TYPE_RAW = 'raw' 

1545 TYPE_VHD = 'vhd' 

1546 

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

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

1549 # Increase the performance when resize is called. 

1550 MAX_METADATA_VIRT_SIZE = 2 * 1024 * 1024 

1551 

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

1553 # VDI methods. 

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

1555 

1556 def load(self, vdi_uuid): 

1557 self._lock = self.sr.lock 

1558 self._exists = True 

1559 self._linstor = self.sr._linstor 

1560 

1561 # Update hidden parent property. 

1562 self.hidden = False 

1563 

1564 def raise_bad_load(e): 

1565 util.SMlog( 

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

1567 ) 

1568 util.SMlog(traceback.format_exc()) 

1569 raise xs_errors.XenError( 

1570 'VDIUnavailable', 

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

1572 ) 

1573 

1574 # Try to load VDI. 

1575 try: 

1576 if ( 

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

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

1579 ): 

1580 self.vdi_type = vhdutil.VDI_TYPE_RAW 

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

1582 else: 

1583 self._determine_type_and_path() 

1584 self._load_this() 

1585 

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

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

1588 )) 

1589 except LinstorVolumeManagerError as e: 

1590 # 1. It may be a VDI deletion. 

1591 if e.code == LinstorVolumeManagerError.ERR_VOLUME_NOT_EXISTS: 

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

1593 self.deleted = True 

1594 return 

1595 

1596 # 2. Or maybe a creation. 

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

1598 # Set type attribute of VDI parent class. 

1599 # We use VHD by default. 

1600 self.vdi_type = vhdutil.VDI_TYPE_VHD 

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

1602 

1603 self._exists = False 

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

1605 if vdi_sm_config is not None: 

1606 type = vdi_sm_config.get('type') 

1607 if type is not None: 

1608 if type == self.TYPE_RAW: 

1609 self.vdi_type = vhdutil.VDI_TYPE_RAW 

1610 elif type == self.TYPE_VHD: 

1611 self.vdi_type = vhdutil.VDI_TYPE_VHD 

1612 else: 

1613 raise xs_errors.XenError( 

1614 'VDICreate', 

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

1616 ) 

1617 if self.vdi_type == vhdutil.VDI_TYPE_VHD: 

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

1619 

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

1621 self._update_device_name(None) 

1622 return 

1623 raise_bad_load(e) 

1624 except Exception as e: 

1625 raise_bad_load(e) 

1626 

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

1628 # Usage example: 

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

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

1631 

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

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

1634 if self._exists: 

1635 raise xs_errors.XenError('VDIExists') 

1636 

1637 assert self.uuid 

1638 assert self.ty 

1639 assert self.vdi_type 

1640 

1641 # 2. Compute size and check space available. 

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

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

1644 util.SMlog( 

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

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

1647 ) 

1648 self.sr._ensure_space_available(volume_size) 

1649 

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

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

1652 

1653 # 4. Create! 

1654 failed = False 

1655 try: 

1656 volume_name = None 

1657 if self.ty == 'ha_statefile': 

1658 volume_name = HA_VOLUME_NAME 

1659 elif self.ty == 'redo_log': 

1660 volume_name = REDO_LOG_VOLUME_NAME 

1661 

1662 self._linstor.create_volume( 

1663 self.uuid, volume_size, persistent=False, 

1664 volume_name=volume_name 

1665 ) 

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

1667 

1668 self._update_device_name(volume_info.name) 

1669 

1670 if self.vdi_type == vhdutil.VDI_TYPE_RAW: 

1671 self.size = volume_info.virtual_size 

1672 else: 

1673 self.sr._vhdutil.create( 

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

1675 ) 

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

1677 

1678 if self._key_hash: 

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

1680 

1681 # Because vhdutil commands modify the volume data, 

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

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

1684 

1685 volume_metadata = { 

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

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

1688 IS_A_SNAPSHOT_TAG: False, 

1689 SNAPSHOT_OF_TAG: '', 

1690 SNAPSHOT_TIME_TAG: '', 

1691 TYPE_TAG: self.ty, 

1692 VDI_TYPE_TAG: self.vdi_type, 

1693 READ_ONLY_TAG: bool(self.read_only), 

1694 METADATA_OF_POOL_TAG: '' 

1695 } 

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

1697 

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

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

1700 # an already opened volume. 

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

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

1703 

1704 self._linstor.mark_volume_as_persistent(self.uuid) 

1705 except util.CommandException as e: 

1706 failed = True 

1707 raise xs_errors.XenError( 

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

1709 ) 

1710 except Exception as e: 

1711 failed = True 

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

1713 finally: 

1714 if failed: 

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

1716 try: 

1717 self._linstor.destroy_volume(self.uuid) 

1718 except Exception as e: 

1719 util.SMlog( 

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

1721 '{}'.format(e) 

1722 ) 

1723 

1724 self.utilisation = volume_info.allocated_size 

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

1726 

1727 self.ref = self._db_introduce() 

1728 self.sr._update_stats(self.size) 

1729 

1730 return VDI.VDI.get_params(self) 

1731 

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

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

1734 if self.attached: 

1735 raise xs_errors.XenError('VDIInUse') 

1736 

1737 if self.deleted: 

1738 return super(LinstorVDI, self).delete( 

1739 sr_uuid, vdi_uuid, data_only 

1740 ) 

1741 

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

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

1744 raise xs_errors.XenError( 

1745 'VDIDelete', 

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

1747 ) 

1748 

1749 try: 

1750 # Remove from XAPI and delete from LINSTOR. 

1751 self._linstor.destroy_volume(self.uuid) 

1752 if not data_only: 

1753 self._db_forget() 

1754 

1755 self.sr.lock.cleanupAll(vdi_uuid) 

1756 except Exception as e: 

1757 util.SMlog( 

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

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

1760 ) 

1761 

1762 try: 

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

1764 except LinstorVolumeManagerError as e: 

1765 if e.code != LinstorVolumeManagerError.ERR_VOLUME_DESTROY: 

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

1767 

1768 return 

1769 

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

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

1772 

1773 # TODO: Check size after delete. 

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

1775 self.sr._kick_gc() 

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

1777 

1778 def attach(self, sr_uuid, vdi_uuid): 

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

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

1781 if ( 

1782 not attach_from_config or 

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

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

1785 raise xs_errors.XenError( 

1786 'VDIUnavailable', 

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

1788 'scan SR first to trigger auto-repair' 

1789 ) 

1790 

1791 if not attach_from_config or self.sr._is_master: 

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

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

1794 

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

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

1797 # than the VHD size + bitmap size. 

1798 need_inflate = True 

1799 if ( 

1800 self.vdi_type == vhdutil.VDI_TYPE_RAW or 

1801 not writable or 

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

1803 ): 

1804 need_inflate = False 

1805 

1806 if need_inflate: 

1807 try: 

1808 self._prepare_thin(True) 

1809 except Exception as e: 

1810 raise xs_errors.XenError( 

1811 'VDIUnavailable', 

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

1813 .format(e) 

1814 ) 

1815 

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

1817 self.xenstore_data = {} 

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

1819 

1820 if ( 

1821 USE_HTTP_NBD_SERVERS and 

1822 attach_from_config and 

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

1824 ): 

1825 return self._attach_using_http_nbd() 

1826 

1827 # Ensure we have a path... 

1828 while vdi_uuid: 

1829 path = self._linstor.get_device_path(vdi_uuid) 

1830 if not util.pathexists(path): 

1831 raise xs_errors.XenError( 

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

1833 ) 

1834 

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

1836 def check_volume_usable(): 

1837 while True: 

1838 try: 

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

1840 pass 

1841 except IOError as e: 

1842 if e.errno == errno.ENODATA: 

1843 time.sleep(2) 

1844 continue 

1845 if e.errno == errno.EROFS: 

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

1847 self.sr._linstor.get_volume_openers(vdi_uuid) 

1848 )) 

1849 raise 

1850 break 

1851 util.retry(check_volume_usable, 15, 2) 

1852 

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

1854 

1855 self.attached = True 

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

1857 

1858 def detach(self, sr_uuid, vdi_uuid): 

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

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

1861 self.attached = False 

1862 

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

1864 return self._detach_using_http_nbd() 

1865 

1866 if self.vdi_type == vhdutil.VDI_TYPE_RAW: 

1867 return 

1868 

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

1870 # equal to the LINSTOR volume size. 

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

1872 already_deflated = self.capacity <= volume_size 

1873 

1874 if already_deflated: 

1875 util.SMlog( 

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

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

1878 ) 

1879 

1880 need_deflate = True 

1881 if already_deflated: 

1882 need_deflate = False 

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

1884 need_deflate = False 

1885 

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

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

1888 need_deflate = True 

1889 

1890 if need_deflate: 

1891 try: 

1892 self._prepare_thin(False) 

1893 except Exception as e: 

1894 raise xs_errors.XenError( 

1895 'VDIUnavailable', 

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

1897 .format(e) 

1898 ) 

1899 

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

1901 if self.sr._is_master: 

1902 return 

1903 

1904 while vdi_uuid: 

1905 try: 

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

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

1908 except Exception: 

1909 break 

1910 

1911 if util.pathexists(path): 

1912 try: 

1913 self._linstor.remove_volume_if_diskless(vdi_uuid) 

1914 except Exception as e: 

1915 # Ensure we can always detach properly. 

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

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

1918 vdi_uuid = parent_vdi_uuid 

1919 

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

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

1922 if not self.sr._is_master: 

1923 raise xs_errors.XenError( 

1924 'VDISize', 

1925 opterr='resize on slave not allowed' 

1926 ) 

1927 

1928 if self.hidden: 

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

1930 

1931 # Compute the virtual VHD and DRBD volume size. 

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

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

1934 util.SMlog( 

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

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

1937 ) 

1938 

1939 if size < self.size: 

1940 util.SMlog( 

1941 'vdi_resize: shrinking not supported: ' 

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

1943 ) 

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

1945 

1946 if size == self.size: 

1947 return VDI.VDI.get_params(self) 

1948 

1949 if self.vdi_type == vhdutil.VDI_TYPE_RAW: 

1950 old_volume_size = self.size 

1951 new_volume_size = LinstorVolumeManager.round_up_volume_size(size) 

1952 else: 

1953 old_volume_size = self.utilisation 

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

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

1956 new_volume_size = old_volume_size 

1957 else: 

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

1959 assert new_volume_size >= old_volume_size 

1960 

1961 space_needed = new_volume_size - old_volume_size 

1962 self.sr._ensure_space_available(space_needed) 

1963 

1964 old_size = self.size 

1965 if self.vdi_type == vhdutil.VDI_TYPE_RAW: 

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

1967 else: 

1968 if new_volume_size != old_volume_size: 

1969 self.sr._vhdutil.inflate( 

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

1971 new_volume_size, old_volume_size 

1972 ) 

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

1974 

1975 # Reload size attributes. 

1976 self._load_this() 

1977 

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

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

1980 self.session.xenapi.VDI.set_physical_utilisation( 

1981 vdi_ref, str(self.utilisation) 

1982 ) 

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

1984 return VDI.VDI.get_params(self) 

1985 

1986 def clone(self, sr_uuid, vdi_uuid): 

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

1988 

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

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

1991 if self.vdi_type != vhdutil.VDI_TYPE_VHD: 

1992 raise xs_errors.XenError('Unimplemented') 

1993 

1994 parent_uuid = vdi1 

1995 parent_path = self._linstor.get_device_path(parent_uuid) 

1996 

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

1998 # have a readonly error. 

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

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

2001 

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

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

2004 try: 

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

2006 self.sr._vhdutil.set_hidden(parent_path) 

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

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

2009 ) 

2010 finally: 

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

2012 

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

2014 raise util.SMException( 

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

2016 ) 

2017 

2018 util.SMlog('Compose done') 

2019 

2020 def generate_config(self, sr_uuid, vdi_uuid): 

2021 """ 

2022 Generate the XML config required to attach and activate 

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

2024 activation is handled by vdi_attach_from_config below. 

2025 """ 

2026 

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

2028 

2029 resp = {} 

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

2031 resp['sr_uuid'] = sr_uuid 

2032 resp['vdi_uuid'] = self.uuid 

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

2034 resp['command'] = 'vdi_attach_from_config' 

2035 

2036 # By default, we generate a normal config. 

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

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

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

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

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

2042 # instead. 

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

2044 if not USE_HTTP_NBD_SERVERS or volume_name not in [ 

2045 HA_VOLUME_NAME, REDO_LOG_VOLUME_NAME 

2046 ]: 

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

2048 available = False 

2049 # Try to refresh symlink path... 

2050 try: 

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

2052 available = util.pathexists(self.path) 

2053 except Exception: 

2054 pass 

2055 if not available: 

2056 raise xs_errors.XenError('VDIUnavailable') 

2057 

2058 resp['vdi_path'] = self.path 

2059 else: 

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

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

2062 

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

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

2065 

2066 def attach_from_config(self, sr_uuid, vdi_uuid): 

2067 """ 

2068 Attach and activate a VDI using config generated by 

2069 vdi_generate_config above. This is used for cases such as 

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

2071 """ 

2072 

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

2074 

2075 try: 

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

2077 self.sr.attach(sr_uuid) 

2078 

2079 if not DRIVER_CONFIG['ATTACH_FROM_CONFIG_WITH_TAPDISK']: 

2080 return self.attach(sr_uuid, vdi_uuid) 

2081 except Exception: 

2082 util.logException('LinstorVDI.attach_from_config') 

2083 raise xs_errors.XenError( 

2084 'SRUnavailable', 

2085 opterr='Unable to attach from config' 

2086 ) 

2087 

2088 def reset_leaf(self, sr_uuid, vdi_uuid): 

2089 if self.vdi_type != vhdutil.VDI_TYPE_VHD: 

2090 raise xs_errors.XenError('Unimplemented') 

2091 

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

2093 raise util.SMException( 

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

2095 .format(self.uuid) 

2096 ) 

2097 

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

2099 

2100 def _load_this(self): 

2101 volume_metadata = None 

2102 if self.sr._all_volume_metadata_cache: 

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

2104 if volume_metadata is None: 

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

2106 

2107 volume_info = None 

2108 if self.sr._all_volume_info_cache: 

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

2110 if volume_info is None: 

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

2112 

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

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

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

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

2117 # be lower than virtual size at creation. 

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

2119 self.utilisation = volume_info.allocated_size 

2120 self.capacity = volume_info.virtual_size 

2121 

2122 if self.vdi_type == vhdutil.VDI_TYPE_RAW: 

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

2124 self.size = volume_info.virtual_size 

2125 self.parent = '' 

2126 else: 

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

2128 self.hidden = vhd_info.hidden 

2129 self.size = vhd_info.sizeVirt 

2130 self.parent = vhd_info.parentUuid 

2131 

2132 if self.hidden: 

2133 self.managed = False 

2134 

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

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

2137 

2138 # Update sm_config_override of VDI parent class. 

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

2140 

2141 def _mark_hidden(self, hidden=True): 

2142 if self.hidden == hidden: 

2143 return 

2144 

2145 if self.vdi_type == vhdutil.VDI_TYPE_VHD: 

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

2147 else: 

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

2149 HIDDEN_TAG: hidden 

2150 }) 

2151 self.hidden = hidden 

2152 

2153 def update(self, sr_uuid, vdi_uuid): 

2154 xenapi = self.session.xenapi 

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

2156 

2157 volume_metadata = { 

2158 NAME_LABEL_TAG: util.to_plain_string( 

2159 xenapi.VDI.get_name_label(vdi_ref) 

2160 ), 

2161 NAME_DESCRIPTION_TAG: util.to_plain_string( 

2162 xenapi.VDI.get_name_description(vdi_ref) 

2163 ) 

2164 } 

2165 

2166 try: 

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

2168 except LinstorVolumeManagerError as e: 

2169 if e.code == LinstorVolumeManagerError.ERR_VOLUME_NOT_EXISTS: 

2170 raise xs_errors.XenError( 

2171 'VDIUnavailable', 

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

2173 ) 

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

2175 

2176 # -------------------------------------------------------------------------- 

2177 # Thin provisioning. 

2178 # -------------------------------------------------------------------------- 

2179 

2180 def _prepare_thin(self, attach): 

2181 if self.sr._is_master: 

2182 if attach: 

2183 attach_thin( 

2184 self.session, self.sr._journaler, self._linstor, 

2185 self.sr.uuid, self.uuid 

2186 ) 

2187 else: 

2188 detach_thin( 

2189 self.session, self._linstor, self.sr.uuid, self.uuid 

2190 ) 

2191 else: 

2192 fn = 'attach' if attach else 'detach' 

2193 

2194 master = util.get_master_ref(self.session) 

2195 

2196 args = { 

2197 'groupName': self.sr._group_name, 

2198 'srUuid': self.sr.uuid, 

2199 'vdiUuid': self.uuid 

2200 } 

2201 

2202 try: 

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

2204 except Exception: 

2205 if fn != 'detach': 

2206 raise 

2207 

2208 # Reload size attrs after inflate or deflate! 

2209 self._load_this() 

2210 self.sr._update_physical_size() 

2211 

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

2213 self.session.xenapi.VDI.set_physical_utilisation( 

2214 vdi_ref, str(self.utilisation) 

2215 ) 

2216 

2217 self.session.xenapi.SR.set_physical_utilisation( 

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

2219 ) 

2220 

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

2222 # Generic helpers. 

2223 # -------------------------------------------------------------------------- 

2224 

2225 def _determine_type_and_path(self): 

2226 """ 

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

2228 """ 

2229 

2230 # 1. Check vdi_ref and vdi_type in config. 

2231 try: 

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

2233 if vdi_ref: 

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

2235 vdi_type = sm_config.get('vdi_type') 

2236 if vdi_type: 

2237 # Update parent fields. 

2238 self.vdi_type = vdi_type 

2239 self.sm_config_override = sm_config 

2240 self._update_device_name( 

2241 self._linstor.get_volume_name(self.uuid) 

2242 ) 

2243 return 

2244 except Exception: 

2245 pass 

2246 

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

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

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

2250 self.vdi_type = volume_metadata.get(VDI_TYPE_TAG) 

2251 if not self.vdi_type: 

2252 raise xs_errors.XenError( 

2253 'VDIUnavailable', 

2254 opterr='failed to get vdi_type in metadata' 

2255 ) 

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

2257 

2258 def _update_device_name(self, device_name): 

2259 self._device_name = device_name 

2260 

2261 # Mark path of VDI parent class. 

2262 if device_name: 

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

2264 else: 

2265 self.path = None 

2266 

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

2268 """ 

2269 Snapshot self and return the snapshot VDI object. 

2270 """ 

2271 

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

2273 snap_path = self._linstor.shallow_clone_volume( 

2274 self.uuid, snap_uuid, persistent=False 

2275 ) 

2276 

2277 # 2. Write the snapshot content. 

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

2279 self.sr._vhdutil.snapshot( 

2280 snap_path, self.path, is_raw, self.MAX_METADATA_VIRT_SIZE 

2281 ) 

2282 

2283 # 3. Get snapshot parent. 

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

2285 

2286 # 4. Update metadata. 

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

2288 volume_metadata = { 

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

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

2291 IS_A_SNAPSHOT_TAG: bool(snap_of_uuid), 

2292 SNAPSHOT_OF_TAG: snap_of_uuid, 

2293 SNAPSHOT_TIME_TAG: '', 

2294 TYPE_TAG: self.ty, 

2295 VDI_TYPE_TAG: vhdutil.VDI_TYPE_VHD, 

2296 READ_ONLY_TAG: False, 

2297 METADATA_OF_POOL_TAG: '' 

2298 } 

2299 self._linstor.set_volume_metadata(snap_uuid, volume_metadata) 

2300 

2301 # 5. Set size. 

2302 snap_vdi = LinstorVDI(self.sr, snap_uuid) 

2303 if not snap_vdi._exists: 

2304 raise xs_errors.XenError('VDISnapshot') 

2305 

2306 volume_info = self._linstor.get_volume_info(snap_uuid) 

2307 

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

2309 snap_vdi.utilisation = volume_info.allocated_size 

2310 

2311 # 6. Update sm config. 

2312 snap_vdi.sm_config = {} 

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

2314 if snap_parent: 

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

2316 snap_vdi.parent = snap_parent 

2317 

2318 snap_vdi.label = self.label 

2319 snap_vdi.description = self.description 

2320 

2321 self._linstor.mark_volume_as_persistent(snap_uuid) 

2322 

2323 return snap_vdi 

2324 

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

2326 # Implement specific SR methods. 

2327 # -------------------------------------------------------------------------- 

2328 

2329 def _rename(self, oldpath, newpath): 

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

2331 volume_uuid = self._linstor.get_volume_uuid_from_device_path(oldpath) 

2332 self._linstor.update_volume_name(volume_uuid, newpath) 

2333 

2334 def _do_snapshot( 

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

2336 ): 

2337 # If cbt enabled, save file consistency state. 

2338 if cbtlog is not None: 

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

2340 consistency_state = False 

2341 else: 

2342 consistency_state = True 

2343 util.SMlog( 

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

2345 .format(consistency_state, vdi_uuid) 

2346 ) 

2347 else: 

2348 consistency_state = None 

2349 

2350 if self.vdi_type != vhdutil.VDI_TYPE_VHD: 

2351 raise xs_errors.XenError('Unimplemented') 

2352 

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

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

2355 try: 

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

2357 finally: 

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

2359 

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

2361 util.SMlog( 

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

2363 .format(self.uuid, snap_type) 

2364 ) 

2365 

2366 # 1. Checks... 

2367 if self.hidden: 

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

2369 

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

2371 if depth == -1: 

2372 raise xs_errors.XenError( 

2373 'VDIUnavailable', 

2374 opterr='failed to get VHD depth' 

2375 ) 

2376 elif depth >= vhdutil.MAX_CHAIN_SIZE: 

2377 raise xs_errors.XenError('SnapshotChainTooLong') 

2378 

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

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

2381 

2382 volume_path = self.path 

2383 if not util.pathexists(volume_path): 

2384 raise xs_errors.XenError( 

2385 'EIO', 

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

2387 ) 

2388 

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

2390 base_uuid = util.gen_uuid() 

2391 snap_uuid = None 

2392 

2393 if snap_type == VDI.SNAPSHOT_DOUBLE: 

2394 snap_uuid = util.gen_uuid() 

2395 

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

2397 

2398 active_uuid = self.uuid 

2399 self.sr._journaler.create( 

2400 LinstorJournaler.CLONE, active_uuid, clone_info 

2401 ) 

2402 

2403 try: 

2404 # 3. Self becomes the new base. 

2405 # The device path remains the same. 

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

2407 self.uuid = base_uuid 

2408 self.location = self.uuid 

2409 self.read_only = True 

2410 self.managed = False 

2411 

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

2413 active_vdi = self._create_snapshot(active_uuid) 

2414 

2415 snap_vdi = None 

2416 if snap_type == VDI.SNAPSHOT_DOUBLE: 

2417 snap_vdi = self._create_snapshot(snap_uuid, active_uuid) 

2418 

2419 self.label = 'base copy' 

2420 self.description = '' 

2421 

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

2423 # in subsequent scans. 

2424 self._mark_hidden() 

2425 self._linstor.update_volume_metadata( 

2426 self.uuid, {READ_ONLY_TAG: True} 

2427 ) 

2428 

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

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

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

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

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

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

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

2436 active_vdi.sm_config[key] = sm_config[key] 

2437 

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

2439 # delete base if unused. 

2440 introduce_parent = True 

2441 try: 

2442 snap_parent = None 

2443 if snap_vdi: 

2444 snap_parent = snap_vdi.parent 

2445 

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

2447 snap_type == VDI.SNAPSHOT_SINGLE or 

2448 snap_type == VDI.SNAPSHOT_INTERNAL or 

2449 snap_parent != self.uuid 

2450 ): 

2451 util.SMlog( 

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

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

2454 ) 

2455 introduce_parent = False 

2456 self._linstor.destroy_volume(self.uuid) 

2457 except Exception as e: 

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

2459 pass 

2460 

2461 # 8. Introduce the new VDI records. 

2462 if snap_vdi: 

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

2464 # new snapshot disk. 

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

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

2467 # TODO: Maybe remove key_hash support. 

2468 if 'key_hash' in sm_config: 

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

2470 # If we have CBT enabled on the VDI, 

2471 # set CBT status for the new snapshot disk. 

2472 if cbtlog: 

2473 snap_vdi.cbt_enabled = True 

2474 

2475 if snap_vdi: 

2476 snap_vdi_ref = snap_vdi._db_introduce() 

2477 util.SMlog( 

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

2479 .format(snap_vdi_ref, snap_vdi.uuid) 

2480 ) 

2481 if introduce_parent: 

2482 base_vdi_ref = self._db_introduce() 

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

2484 util.SMlog( 

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

2486 .format(base_vdi_ref, self.uuid) 

2487 ) 

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

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

2490 NAME_DESCRIPTION_TAG: util.to_plain_string( 

2491 self.description 

2492 ), 

2493 READ_ONLY_TAG: True, 

2494 METADATA_OF_POOL_TAG: '' 

2495 }) 

2496 

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

2498 if snap_type == VDI.SNAPSHOT_DOUBLE and cbtlog: 

2499 try: 

2500 self._cbt_snapshot(snap_uuid, cbt_consistency) 

2501 except Exception: 

2502 # CBT operation failed. 

2503 # TODO: Implement me. 

2504 raise 

2505 

2506 if snap_type != VDI.SNAPSHOT_INTERNAL: 

2507 self.sr._update_stats(self.size) 

2508 

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

2510 ret_vdi = snap_vdi 

2511 if not ret_vdi: 

2512 ret_vdi = self 

2513 if not ret_vdi: 

2514 ret_vdi = active_vdi 

2515 

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

2517 self.session.xenapi.VDI.set_sm_config( 

2518 vdi_ref, active_vdi.sm_config 

2519 ) 

2520 except Exception as e: 

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

2522 try: 

2523 self.sr._handle_interrupted_clone( 

2524 active_uuid, clone_info, force_undo=True 

2525 ) 

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

2527 except Exception as e: 

2528 util.SMlog( 

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

2530 .format(e) 

2531 ) 

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

2533 

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

2535 

2536 return ret_vdi.get_params() 

2537 

2538 @staticmethod 

2539 def _start_persistent_http_server(volume_name): 

2540 pid_path = None 

2541 http_server = None 

2542 

2543 try: 

2544 if volume_name == HA_VOLUME_NAME: 

2545 port = '8076' 

2546 else: 

2547 port = '8077' 

2548 

2549 try: 

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

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

2552 # block indefinitely. 

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

2554 host_ip = util.get_this_host_address(session) 

2555 except: 

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

2557 host_ip, _ = get_ips_from_xha_config_file() 

2558 if not host_ip: 

2559 raise Exception( 

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

2561 ) 

2562 

2563 arguments = [ 

2564 'http-disk-server', 

2565 '--disk', 

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

2567 '--ip', 

2568 host_ip, 

2569 '--port', 

2570 port 

2571 ] 

2572 

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

2574 http_server = subprocess.Popen( 

2575 [FORK_LOG_DAEMON] + arguments, 

2576 stdout=subprocess.PIPE, 

2577 stderr=subprocess.STDOUT, 

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

2579 # touch the current one. 

2580 preexec_fn=os.setsid 

2581 ) 

2582 

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

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

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

2586 

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

2588 def is_ready(): 

2589 while http_server.poll() is None: 

2590 line = http_server.stdout.readline() 

2591 if reg_server_ready.search(line): 

2592 return True 

2593 return False 

2594 try: 

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

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

2597 except util.TimeoutException: 

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

2599 except Exception as e: 

2600 if pid_path: 

2601 try: 

2602 os.remove(pid_path) 

2603 except Exception: 

2604 pass 

2605 

2606 if http_server: 

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

2608 try: 

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

2610 except: 

2611 pass 

2612 

2613 raise xs_errors.XenError( 

2614 'VDIUnavailable', 

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

2616 ) 

2617 

2618 def _start_persistent_nbd_server(self, volume_name): 

2619 pid_path = None 

2620 nbd_path = None 

2621 nbd_server = None 

2622 

2623 try: 

2624 # We use a precomputed device size. 

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

2626 if volume_name == HA_VOLUME_NAME: 

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

2628 port = '8076' 

2629 device_size = 4 * 1024 * 1024 

2630 else: 

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

2632 port = '8077' 

2633 device_size = 256 * 1024 * 1024 

2634 

2635 try: 

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

2637 ips = util.get_host_addresses(session) 

2638 except Exception as e: 

2639 _, ips = get_ips_from_xha_config_file() 

2640 if not ips: 

2641 raise Exception( 

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

2643 ) 

2644 ips = ips.values() 

2645 

2646 arguments = [ 

2647 'nbd-http-server', 

2648 '--socket-path', 

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

2650 '--nbd-name', 

2651 volume_name, 

2652 '--urls', 

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

2654 '--device-size', 

2655 str(device_size) 

2656 ] 

2657 

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

2659 nbd_server = subprocess.Popen( 

2660 [FORK_LOG_DAEMON] + arguments, 

2661 stdout=subprocess.PIPE, 

2662 stderr=subprocess.STDOUT, 

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

2664 # touch the current one. 

2665 preexec_fn=os.setsid 

2666 ) 

2667 

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

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

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

2671 

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

2673 def get_nbd_path(): 

2674 while nbd_server.poll() is None: 

2675 line = nbd_server.stdout.readline() 

2676 match = reg_nbd_path.search(line) 

2677 if match: 

2678 return match.group(1) 

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

2680 try: 

2681 nbd_path = util.timeout_call(10, get_nbd_path) 

2682 if nbd_path is None: 

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

2684 except util.TimeoutException: 

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

2686 

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

2688 os.symlink(nbd_path, self.path) 

2689 except Exception as e: 

2690 if pid_path: 

2691 try: 

2692 os.remove(pid_path) 

2693 except Exception: 

2694 pass 

2695 

2696 if nbd_path: 

2697 try: 

2698 os.remove(nbd_path) 

2699 except Exception: 

2700 pass 

2701 

2702 if nbd_server: 

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

2704 try: 

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

2706 except: 

2707 pass 

2708 

2709 raise xs_errors.XenError( 

2710 'VDIUnavailable', 

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

2712 ) 

2713 

2714 @classmethod 

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

2716 try: 

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

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

2719 return 

2720 

2721 pid = None 

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

2723 try: 

2724 pid = int(pid_file.read()) 

2725 except Exception: 

2726 pass 

2727 

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

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

2730 try: 

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

2732 except Exception as e: 

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

2734 

2735 os.remove(path) 

2736 except: 

2737 pass 

2738 

2739 @classmethod 

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

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

2742 

2743 @classmethod 

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

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

2746 

2747 def _check_http_nbd_volume_name(self): 

2748 volume_name = self.path[14:] 

2749 if volume_name not in [ 

2750 HA_VOLUME_NAME, REDO_LOG_VOLUME_NAME 

2751 ]: 

2752 raise xs_errors.XenError( 

2753 'VDIUnavailable', 

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

2755 ) 

2756 return volume_name 

2757 

2758 def _attach_using_http_nbd(self): 

2759 volume_name = self._check_http_nbd_volume_name() 

2760 

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

2762 self._kill_persistent_nbd_server(volume_name) 

2763 self._kill_persistent_http_server(volume_name) 

2764 

2765 # 0. Fetch drbd path. 

2766 must_get_device_path = True 

2767 if not self.sr._is_master: 

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

2769 try: 

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

2771 except Exception as e: 

2772 raise xs_errors.XenError( 

2773 'VDIUnavailable', 

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

2775 .format(self.uuid, e) 

2776 ) 

2777 

2778 hostname = socket.gethostname() 

2779 must_get_device_path = hostname in volume_info.diskful 

2780 

2781 drbd_path = None 

2782 if must_get_device_path or self.sr._is_master: 

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

2784 # or diskful available to init HA. 

2785 # It also avoid this error in xensource.log 

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

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

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

2789 available = False 

2790 try: 

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

2792 available = util.pathexists(drbd_path) 

2793 except Exception: 

2794 pass 

2795 

2796 if not available: 

2797 raise xs_errors.XenError( 

2798 'VDIUnavailable', 

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

2800 ) 

2801 

2802 # 1. Prepare http-nbd folder. 

2803 try: 

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

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

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

2807 os.remove(self.path) 

2808 except OSError as e: 

2809 if e.errno != errno.EEXIST: 

2810 raise xs_errors.XenError( 

2811 'VDIUnavailable', 

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

2813 ) 

2814 

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

2816 http_service = None 

2817 if drbd_path: 

2818 assert(drbd_path in ( 

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

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

2821 )) 

2822 self._start_persistent_http_server(volume_name) 

2823 

2824 # 3. Start NBD server in all cases. 

2825 try: 

2826 self._start_persistent_nbd_server(volume_name) 

2827 except Exception as e: 

2828 if drbd_path: 

2829 self._kill_persistent_http_server(volume_name) 

2830 raise 

2831 

2832 self.attached = True 

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

2834 

2835 def _detach_using_http_nbd(self): 

2836 volume_name = self._check_http_nbd_volume_name() 

2837 self._kill_persistent_nbd_server(volume_name) 

2838 self._kill_persistent_http_server(volume_name) 

2839 

2840# ------------------------------------------------------------------------------ 

2841 

2842 

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

2844 def run(): 

2845 SRCommand.run(LinstorSR, DRIVER_INFO) 

2846 

2847 if not TRACE_PERFS: 

2848 run() 

2849 else: 

2850 util.make_profile('LinstorSR', run) 

2851else: 

2852 SR.registerSR(LinstorSR)