Coverage for drivers/LinstorSR.py : 7%

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/>.
17from constants import CBTLOG_TAG
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
28 LINSTOR_AVAILABLE = True
29except ImportError:
30 PERSISTENT_PREFIX = 'unknown'
32 LINSTOR_AVAILABLE = False
34from lock import Lock, LOCK_TYPE_GC_RUNNING
35import blktap2
36import cleanup
37import distutils
38import errno
39import functools
40import lvutil
41import os
42import re
43import scsiutil
44import signal
45import socket
46import SR
47import SRCommand
48import subprocess
49import time
50import traceback
51import util
52import VDI
53import vhdutil
54import xml.etree.ElementTree as xml_parser
55import xmlrpc.client
56import xs_errors
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
63HIDDEN_TAG = 'hidden'
65XHA_CONFIG_PATH = '/etc/xensource/xhad.conf'
67FORK_LOG_DAEMON = '/opt/xensource/libexec/fork-log-daemon'
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
76# Useful flag to trace calls using cProfile.
77TRACE_PERFS = False
79# Enable/Disable VHD key hash support.
80USE_KEY_HASH = False
82# Special volumes.
83HA_VOLUME_NAME = PERSISTENT_PREFIX + 'ha-statefile'
84REDO_LOG_VOLUME_NAME = PERSISTENT_PREFIX + 'redo-log'
86# ==============================================================================
88# TODO: Supports 'VDI_INTRODUCE', 'VDI_RESET_ON_BOOT/2', 'SR_TRIM',
89# 'VDI_CONFIG_CBT', 'SR_PROBE'
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]
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]
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}
126DRIVER_CONFIG = {'ATTACH_FROM_CONFIG_WITH_TAPDISK': False}
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]
134# ==============================================================================
135# Misc helpers used by LinstorSR and linstor-thin plugin.
136# ==============================================================================
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
145 device_path = linstor.get_device_path(vdi_uuid)
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 )
156 volume_info = linstor.get_volume_info(vdi_uuid)
157 volume_size = volume_info.virtual_size
159 if vhd_size > volume_size:
160 LinstorVhdUtil(session, linstor).inflate(
161 journaler, vdi_uuid, device_path, vhd_size, volume_size
162 )
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
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 )
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 )
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)
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 )
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)
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))
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)
229 def parse_host_nodes(ips, node):
230 current_id = None
231 current_ip = None
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
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')
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)
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
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
266 if ips and host_id:
267 break
269 return (host_id and ips.get(host_id), ips)
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))
280# ==============================================================================
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
288class LinstorSR(SR.SR):
289 DRIVER_TYPE = 'linstor'
291 PROVISIONING_TYPES = ['thin', 'thick']
292 PROVISIONING_DEFAULT = 'thin'
294 MANAGER_PLUGIN = 'linstor-manager'
296 INIT_STATUS_NOT_SET = 0
297 INIT_STATUS_IN_PROGRESS = 1
298 INIT_STATUS_OK = 2
299 INIT_STATUS_FAIL = 3
301 # --------------------------------------------------------------------------
302 # SR methods.
303 # --------------------------------------------------------------------------
305 @staticmethod
306 def handles(type):
307 return type == LinstorSR.DRIVER_TYPE
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 )
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')
321 self.driver_config = DRIVER_CONFIG
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
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)
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 {}
350 provisioning = self.sm_config.get('provisioning')
351 if provisioning in self.PROVISIONING_TYPES:
352 self._provisioning = provisioning
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
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
365 self._group_name = self.dconf['group-name']
367 self._vdi_shared_time = 0
369 self._init_status = self.INIT_STATUS_NOT_SET
371 self._vdis_loaded = False
372 self._all_volume_info_cache = None
373 self._all_volume_metadata_cache = None
375 def _locked_load(method):
376 def wrapped_method(self, *args, **kwargs):
377 self._init_status = self.INIT_STATUS_OK
378 return method(self, *args, **kwargs)
380 def load(self, *args, **kwargs):
381 # Activate all LVMs to make drbd-reactor happy.
382 if self.srcmd.cmd in ('sr_attach', 'vdi_attach_from_config'):
383 activate_lvm_group(self._group_name)
385 if not self._has_session:
386 if self.srcmd.cmd in (
387 'vdi_attach_from_config',
388 'vdi_detach_from_config',
389 # When on-slave (is_open) is executed we have an
390 # empty command.
391 None
392 ):
393 def create_linstor(uri, attempt_count=30):
394 self._linstor = LinstorVolumeManager(
395 uri,
396 self._group_name,
397 logger=util.SMlog,
398 attempt_count=attempt_count
399 )
400 # Only required if we are attaching from config using a non-special VDI.
401 # I.e. not an HA volume.
402 self._vhdutil = LinstorVhdUtil(self.session, self._linstor)
404 controller_uri = get_controller_uri()
405 if controller_uri:
406 create_linstor(controller_uri)
407 else:
408 def connect():
409 # We must have a valid LINSTOR instance here without using
410 # the XAPI. Fallback with the HA config file.
411 for ip in get_ips_from_xha_config_file()[1].values():
412 controller_uri = 'linstor://' + ip
413 try:
414 util.SMlog('Connecting from config to LINSTOR controller using: {}'.format(ip))
415 create_linstor(controller_uri, attempt_count=0)
416 return controller_uri
417 except:
418 pass
420 controller_uri = util.retry(connect, maxretry=30, period=1)
421 if not controller_uri:
422 raise xs_errors.XenError(
423 'SRUnavailable',
424 opterr='No valid controller URI to attach/detach from config'
425 )
427 self._journaler = LinstorJournaler(
428 controller_uri, self._group_name, logger=util.SMlog
429 )
431 if self.srcmd.cmd is None:
432 # Only useful on on-slave plugin (is_open).
433 self._vhdutil = LinstorVhdUtil(self.session, self._linstor)
435 return wrapped_method(self, *args, **kwargs)
437 if not self.is_master():
438 if self.cmd in [
439 'sr_create', 'sr_delete', 'sr_update', 'sr_probe',
440 'sr_scan', 'vdi_create', 'vdi_delete', 'vdi_resize',
441 'vdi_snapshot', 'vdi_clone'
442 ]:
443 util.SMlog('{} blocked for non-master'.format(self.cmd))
444 raise xs_errors.XenError('LinstorMaster')
446 # Because the LINSTOR KV objects cache all values, we must lock
447 # the VDI before the LinstorJournaler/LinstorVolumeManager
448 # instantiation and before any action on the master to avoid a
449 # bad read. The lock is also necessary to avoid strange
450 # behaviors if the GC is executed during an action on a slave.
451 if self.cmd.startswith('vdi_'):
452 self._shared_lock_vdi(self.srcmd.params['vdi_uuid'])
453 self._vdi_shared_time = time.time()
455 if self.srcmd.cmd != 'sr_create' and self.srcmd.cmd != 'sr_detach':
456 try:
457 self._reconnect()
458 except Exception as e:
459 raise xs_errors.XenError('SRUnavailable', opterr=str(e))
461 if self._linstor:
462 try:
463 hosts = self._linstor.disconnected_hosts
464 except Exception as e:
465 raise xs_errors.XenError('SRUnavailable', opterr=str(e))
467 if hosts:
468 util.SMlog('Failed to join node(s): {}'.format(hosts))
470 # Ensure we use a non-locked volume when vhdutil is called.
471 if (
472 self.is_master() and self.cmd.startswith('vdi_') and
473 self.cmd != 'vdi_create'
474 ):
475 self._linstor.ensure_volume_is_not_locked(
476 self.srcmd.params['vdi_uuid']
477 )
479 try:
480 # If the command is a SR scan command on the master,
481 # we must load all VDIs and clean journal transactions.
482 # We must load the VDIs in the snapshot case too only if
483 # there is at least one entry in the journal.
484 #
485 # If the command is a SR command we want at least to remove
486 # resourceless volumes.
487 if self.is_master() and self.cmd not in [
488 'vdi_attach', 'vdi_detach',
489 'vdi_activate', 'vdi_deactivate',
490 'vdi_epoch_begin', 'vdi_epoch_end',
491 'vdi_update', 'vdi_destroy'
492 ]:
493 load_vdis = (
494 self.cmd == 'sr_scan' or
495 self.cmd == 'sr_attach'
496 ) or len(
497 self._journaler.get_all(LinstorJournaler.INFLATE)
498 ) or len(
499 self._journaler.get_all(LinstorJournaler.CLONE)
500 )
502 if load_vdis:
503 self._load_vdis()
505 self._linstor.remove_resourceless_volumes()
507 self._synchronize_metadata()
508 except Exception as e:
509 if self.cmd == 'sr_scan' or self.cmd == 'sr_attach':
510 # Always raise, we don't want to remove VDIs
511 # from the XAPI database otherwise.
512 raise e
513 util.SMlog(
514 'Ignoring exception in LinstorSR.load: {}'.format(e)
515 )
516 util.SMlog(traceback.format_exc())
518 return wrapped_method(self, *args, **kwargs)
520 @functools.wraps(wrapped_method)
521 def wrap(self, *args, **kwargs):
522 if self._init_status in \
523 (self.INIT_STATUS_OK, self.INIT_STATUS_IN_PROGRESS):
524 return wrapped_method(self, *args, **kwargs)
525 if self._init_status == self.INIT_STATUS_FAIL:
526 util.SMlog(
527 'Can\'t call method {} because initialization failed'
528 .format(method)
529 )
530 else:
531 try:
532 self._init_status = self.INIT_STATUS_IN_PROGRESS
533 return load(self, *args, **kwargs)
534 except Exception:
535 if self._init_status != self.INIT_STATUS_OK:
536 self._init_status = self.INIT_STATUS_FAIL
537 raise
539 return wrap
541 def cleanup(self):
542 if self._vdi_shared_time:
543 self._shared_lock_vdi(self.srcmd.params['vdi_uuid'], locked=False)
545 @_locked_load
546 def create(self, uuid, size):
547 util.SMlog('LinstorSR.create for {}'.format(self.uuid))
549 host_adresses = util.get_host_addresses(self.session)
550 if self._redundancy > len(host_adresses):
551 raise xs_errors.XenError(
552 'LinstorSRCreate',
553 opterr='Redundancy greater than host count'
554 )
556 xenapi = self.session.xenapi
557 srs = xenapi.SR.get_all_records_where(
558 'field "type" = "{}"'.format(self.DRIVER_TYPE)
559 )
560 srs = dict([e for e in srs.items() if e[1]['uuid'] != self.uuid])
562 for sr in srs.values():
563 for pbd in sr['PBDs']:
564 device_config = xenapi.PBD.get_device_config(pbd)
565 group_name = device_config.get('group-name')
566 if group_name and group_name == self._group_name:
567 raise xs_errors.XenError(
568 'LinstorSRCreate',
569 opterr='group name must be unique, already used by PBD {}'.format(
570 xenapi.PBD.get_uuid(pbd)
571 )
572 )
574 if srs:
575 raise xs_errors.XenError(
576 'LinstorSRCreate',
577 opterr='LINSTOR SR must be unique in a pool'
578 )
580 online_hosts = util.get_online_hosts(self.session)
581 if len(online_hosts) < len(host_adresses):
582 raise xs_errors.XenError(
583 'LinstorSRCreate',
584 opterr='Not enough online hosts'
585 )
587 ips = {}
588 for host_ref in online_hosts:
589 record = self.session.xenapi.host.get_record(host_ref)
590 hostname = record['hostname']
591 ips[hostname] = record['address']
593 if len(ips) != len(online_hosts):
594 raise xs_errors.XenError(
595 'LinstorSRCreate',
596 opterr='Multiple hosts with same hostname'
597 )
599 # Ensure ports are opened and LINSTOR satellites
600 # are activated. In the same time the drbd-reactor instances
601 # must be stopped.
602 self._prepare_sr_on_all_hosts(self._group_name, enabled=True)
604 # Create SR.
605 # Throw if the SR already exists.
606 try:
607 self._linstor = LinstorVolumeManager.create_sr(
608 self._group_name,
609 ips,
610 self._redundancy,
611 thin_provisioning=self._provisioning == 'thin',
612 auto_quorum=self._monitor_db_quorum,
613 logger=util.SMlog
614 )
615 self._vhdutil = LinstorVhdUtil(self.session, self._linstor)
616 except Exception as e:
617 util.SMlog('Failed to create LINSTOR SR: {}'.format(e))
618 raise xs_errors.XenError('LinstorSRCreate', opterr=str(e))
620 try:
621 util.SMlog(
622 "Finishing SR creation, enable drbd-reactor on all hosts..."
623 )
624 self._update_drbd_reactor_on_all_hosts(enabled=True)
625 except Exception as e:
626 try:
627 self._linstor.destroy()
628 except Exception as e2:
629 util.SMlog(
630 'Failed to destroy LINSTOR SR after creation fail: {}'
631 .format(e2)
632 )
633 raise e
635 @_locked_load
636 def delete(self, uuid):
637 util.SMlog('LinstorSR.delete for {}'.format(self.uuid))
638 cleanup.gc_force(self.session, self.uuid)
640 if self.vdis or self._linstor._volumes:
641 raise xs_errors.XenError('SRNotEmpty')
643 node_name = get_controller_node_name()
644 if not node_name:
645 raise xs_errors.XenError(
646 'LinstorSRDelete',
647 opterr='Cannot get controller node name'
648 )
650 host_ref = None
651 if node_name == 'localhost':
652 host_ref = util.get_this_host_ref(self.session)
653 else:
654 for slave in util.get_all_slaves(self.session):
655 r_name = self.session.xenapi.host.get_record(slave)['hostname']
656 if r_name == node_name:
657 host_ref = slave
658 break
660 if not host_ref:
661 raise xs_errors.XenError(
662 'LinstorSRDelete',
663 opterr='Failed to find host with hostname: {}'.format(
664 node_name
665 )
666 )
668 try:
669 self._update_drbd_reactor_on_all_hosts(
670 controller_node_name=node_name, enabled=False
671 )
673 args = {
674 'groupName': self._group_name,
675 }
676 self._exec_manager_command(
677 host_ref, 'destroy', args, 'LinstorSRDelete'
678 )
679 except Exception as e:
680 try:
681 self._update_drbd_reactor_on_all_hosts(
682 controller_node_name=node_name, enabled=True
683 )
684 except Exception as e2:
685 util.SMlog(
686 'Failed to restart drbd-reactor after destroy fail: {}'
687 .format(e2)
688 )
689 util.SMlog('Failed to delete LINSTOR SR: {}'.format(e))
690 raise xs_errors.XenError(
691 'LinstorSRDelete',
692 opterr=str(e)
693 )
695 Lock.cleanupAll(self.uuid)
697 @_locked_load
698 def update(self, uuid):
699 util.SMlog('LinstorSR.update for {}'.format(self.uuid))
701 # Well, how can we update a SR if it doesn't exist? :thinking:
702 if not self._linstor:
703 raise xs_errors.XenError(
704 'SRUnavailable',
705 opterr='no such volume group: {}'.format(self._group_name)
706 )
708 self._update_stats(0)
710 # Update the SR name and description only in LINSTOR metadata.
711 xenapi = self.session.xenapi
712 self._linstor.metadata = {
713 NAME_LABEL_TAG: util.to_plain_string(
714 xenapi.SR.get_name_label(self.sr_ref)
715 ),
716 NAME_DESCRIPTION_TAG: util.to_plain_string(
717 xenapi.SR.get_name_description(self.sr_ref)
718 )
719 }
721 @_locked_load
722 def attach(self, uuid):
723 util.SMlog('LinstorSR.attach for {}'.format(self.uuid))
725 if not self._linstor:
726 raise xs_errors.XenError(
727 'SRUnavailable',
728 opterr='no such group: {}'.format(self._group_name)
729 )
731 @_locked_load
732 def detach(self, uuid):
733 util.SMlog('LinstorSR.detach for {}'.format(self.uuid))
734 cleanup.abort(self.uuid)
736 @_locked_load
737 def probe(self):
738 util.SMlog('LinstorSR.probe for {}'.format(self.uuid))
739 # TODO
741 @_locked_load
742 def scan(self, uuid):
743 if self._init_status == self.INIT_STATUS_FAIL:
744 return
746 util.SMlog('LinstorSR.scan for {}'.format(self.uuid))
747 if not self._linstor:
748 raise xs_errors.XenError(
749 'SRUnavailable',
750 opterr='no such volume group: {}'.format(self._group_name)
751 )
753 # Note: `scan` can be called outside this module, so ensure the VDIs
754 # are loaded.
755 self._load_vdis()
756 self._update_physical_size()
758 for vdi_uuid in list(self.vdis.keys()):
759 if self.vdis[vdi_uuid].deleted:
760 del self.vdis[vdi_uuid]
762 # Security to prevent VDIs from being forgotten if the controller
763 # is started without a shared and mounted /var/lib/linstor path.
764 try:
765 self._linstor.get_database_path()
766 except Exception as e:
767 # Failed to get database path, ensure we don't have
768 # VDIs in the XAPI database...
769 if self.session.xenapi.SR.get_VDIs(
770 self.session.xenapi.SR.get_by_uuid(self.uuid)
771 ):
772 raise xs_errors.XenError(
773 'SRUnavailable',
774 opterr='Database is not mounted or node name is invalid ({})'.format(e)
775 )
777 # Update the database before the restart of the GC to avoid
778 # bad sync in the process if new VDIs have been introduced.
779 super(LinstorSR, self).scan(self.uuid)
780 self._kick_gc()
782 def is_master(self):
783 if not hasattr(self, '_is_master'):
784 if 'SRmaster' not in self.dconf:
785 self._is_master = self.session is not None and util.is_master(self.session)
786 else:
787 self._is_master = self.dconf['SRmaster'] == 'true'
789 return self._is_master
791 @_locked_load
792 def vdi(self, uuid):
793 return LinstorVDI(self, uuid)
795 _locked_load = staticmethod(_locked_load)
797 # --------------------------------------------------------------------------
798 # Lock.
799 # --------------------------------------------------------------------------
801 def _shared_lock_vdi(self, vdi_uuid, locked=True):
802 master = util.get_master_ref(self.session)
804 command = 'lockVdi'
805 args = {
806 'groupName': self._group_name,
807 'srUuid': self.uuid,
808 'vdiUuid': vdi_uuid,
809 'locked': str(locked)
810 }
812 # Note: We must avoid to unlock the volume if the timeout is reached
813 # because during volume unlock, the SR lock is not used. Otherwise
814 # we could destroy a valid lock acquired from another host...
815 #
816 # This code is not very clean, the ideal solution would be to acquire
817 # the SR lock during volume unlock (like lock) but it's not easy
818 # to implement without impacting performance.
819 if not locked:
820 elapsed_time = time.time() - self._vdi_shared_time
821 timeout = LinstorVolumeManager.LOCKED_EXPIRATION_DELAY * 0.7
822 if elapsed_time >= timeout:
823 util.SMlog(
824 'Avoid unlock call of {} because timeout has been reached'
825 .format(vdi_uuid)
826 )
827 return
829 self._exec_manager_command(master, command, args, 'VDIUnavailable')
831 # --------------------------------------------------------------------------
832 # Network.
833 # --------------------------------------------------------------------------
835 def _exec_manager_command(self, host_ref, command, args, error):
836 host_rec = self.session.xenapi.host.get_record(host_ref)
837 host_uuid = host_rec['uuid']
839 try:
840 ret = self.session.xenapi.host.call_plugin(
841 host_ref, self.MANAGER_PLUGIN, command, args
842 )
843 except Exception as e:
844 util.SMlog(
845 'call-plugin on {} ({}:{} with {}) raised'.format(
846 host_uuid, self.MANAGER_PLUGIN, command, args
847 )
848 )
849 raise e
851 util.SMlog(
852 'call-plugin on {} ({}:{} with {}) returned: {}'.format(
853 host_uuid, self.MANAGER_PLUGIN, command, args, ret
854 )
855 )
856 if ret == 'False':
857 raise xs_errors.XenError(
858 error,
859 opterr='Plugin {} failed'.format(self.MANAGER_PLUGIN)
860 )
862 def _prepare_sr(self, host, group_name, enabled):
863 self._exec_manager_command(
864 host,
865 'prepareSr' if enabled else 'releaseSr',
866 {'groupName': group_name},
867 'SRUnavailable'
868 )
870 def _prepare_sr_on_all_hosts(self, group_name, enabled):
871 master = util.get_master_ref(self.session)
872 self._prepare_sr(master, group_name, enabled)
874 for slave in util.get_all_slaves(self.session):
875 self._prepare_sr(slave, group_name, enabled)
877 def _update_drbd_reactor(self, host, enabled):
878 self._exec_manager_command(
879 host,
880 'updateDrbdReactor',
881 {'enabled': str(enabled)},
882 'SRUnavailable'
883 )
885 def _update_drbd_reactor_on_all_hosts(
886 self, enabled, controller_node_name=None
887 ):
888 if controller_node_name == 'localhost':
889 controller_node_name = self.session.xenapi.host.get_record(
890 util.get_this_host_ref(self.session)
891 )['hostname']
892 assert controller_node_name
893 assert controller_node_name != 'localhost'
895 controller_host = None
896 secondary_hosts = []
898 hosts = self.session.xenapi.host.get_all_records()
899 for host_ref, host_rec in hosts.items():
900 hostname = host_rec['hostname']
901 if controller_node_name == hostname:
902 controller_host = host_ref
903 else:
904 secondary_hosts.append((host_ref, hostname))
906 action_name = 'Starting' if enabled else 'Stopping'
907 if controller_node_name and not controller_host:
908 util.SMlog('Failed to find controller host: `{}`'.format(
909 controller_node_name
910 ))
912 if enabled and controller_host:
913 util.SMlog('{} drbd-reactor on controller host `{}`...'.format(
914 action_name, controller_node_name
915 ))
916 # If enabled is true, we try to start the controller on the desired
917 # node name first.
918 self._update_drbd_reactor(controller_host, enabled)
920 for host_ref, hostname in secondary_hosts:
921 util.SMlog('{} drbd-reactor on host {}...'.format(
922 action_name, hostname
923 ))
924 self._update_drbd_reactor(host_ref, enabled)
926 if not enabled and controller_host:
927 util.SMlog('{} drbd-reactor on controller host `{}`...'.format(
928 action_name, controller_node_name
929 ))
930 # If enabled is false, we disable the drbd-reactor service of
931 # the controller host last. Why? Otherwise the linstor-controller
932 # of other nodes can be started, and we don't want that.
933 self._update_drbd_reactor(controller_host, enabled)
935 # --------------------------------------------------------------------------
936 # Metadata.
937 # --------------------------------------------------------------------------
939 def _synchronize_metadata_and_xapi(self):
940 try:
941 # First synch SR parameters.
942 self.update(self.uuid)
944 # Now update the VDI information in the metadata if required.
945 xenapi = self.session.xenapi
946 volumes_metadata = self._linstor.get_volumes_with_metadata()
947 for vdi_uuid, volume_metadata in volumes_metadata.items():
948 try:
949 vdi_ref = xenapi.VDI.get_by_uuid(vdi_uuid)
950 except Exception:
951 # May be the VDI is not in XAPI yet dont bother.
952 continue
954 label = util.to_plain_string(
955 xenapi.VDI.get_name_label(vdi_ref)
956 )
957 description = util.to_plain_string(
958 xenapi.VDI.get_name_description(vdi_ref)
959 )
961 if (
962 volume_metadata.get(NAME_LABEL_TAG) != label or
963 volume_metadata.get(NAME_DESCRIPTION_TAG) != description
964 ):
965 self._linstor.update_volume_metadata(vdi_uuid, {
966 NAME_LABEL_TAG: label,
967 NAME_DESCRIPTION_TAG: description
968 })
969 except Exception as e:
970 raise xs_errors.XenError(
971 'MetadataError',
972 opterr='Error synching SR Metadata and XAPI: {}'.format(e)
973 )
975 def _synchronize_metadata(self):
976 if not self.is_master():
977 return
979 util.SMlog('Synchronize metadata...')
980 if self.cmd == 'sr_attach':
981 try:
982 util.SMlog(
983 'Synchronize SR metadata and the state on the storage.'
984 )
985 self._synchronize_metadata_and_xapi()
986 except Exception as e:
987 util.SMlog('Failed to synchronize metadata: {}'.format(e))
989 # --------------------------------------------------------------------------
990 # Stats.
991 # --------------------------------------------------------------------------
993 def _update_stats(self, virt_alloc_delta):
994 valloc = int(self.session.xenapi.SR.get_virtual_allocation(
995 self.sr_ref
996 ))
998 # Update size attributes of the SR parent class.
999 self.virtual_allocation = valloc + virt_alloc_delta
1001 self._update_physical_size()
1003 # Notify SR parent class.
1004 self._db_update()
1006 def _update_physical_size(self):
1007 # We use the size of the smallest disk, this is an approximation that
1008 # ensures the displayed physical size is reachable by the user.
1009 (min_physical_size, pool_count) = self._linstor.get_min_physical_size()
1010 self.physical_size = min_physical_size * pool_count // \
1011 self._linstor.redundancy
1013 self.physical_utilisation = self._linstor.allocated_volume_size
1015 # --------------------------------------------------------------------------
1016 # VDIs.
1017 # --------------------------------------------------------------------------
1019 def _load_vdis(self):
1020 if self._vdis_loaded:
1021 return
1023 assert self.is_master()
1025 # We use a cache to avoid repeated JSON parsing.
1026 # The performance gain is not big but we can still
1027 # enjoy it with a few lines.
1028 self._create_linstor_cache()
1029 self._load_vdis_ex()
1030 self._destroy_linstor_cache()
1032 # We must mark VDIs as loaded only if the load is a success.
1033 self._vdis_loaded = True
1035 self._undo_all_journal_transactions()
1037 def _load_vdis_ex(self):
1038 # 1. Get existing VDIs in XAPI.
1039 xenapi = self.session.xenapi
1040 xapi_vdi_uuids = set()
1041 for vdi in xenapi.SR.get_VDIs(self.sr_ref):
1042 xapi_vdi_uuids.add(xenapi.VDI.get_uuid(vdi))
1044 # 2. Get volumes info.
1045 all_volume_info = self._all_volume_info_cache
1046 volumes_metadata = self._all_volume_metadata_cache
1048 # 3. Get CBT vdis.
1049 # See: https://support.citrix.com/article/CTX230619
1050 cbt_vdis = set()
1051 for volume_metadata in volumes_metadata.values():
1052 cbt_uuid = volume_metadata.get(CBTLOG_TAG)
1053 if cbt_uuid:
1054 cbt_vdis.add(cbt_uuid)
1056 introduce = False
1058 # Try to introduce VDIs only during scan/attach.
1059 if self.cmd == 'sr_scan' or self.cmd == 'sr_attach':
1060 has_clone_entries = list(self._journaler.get_all(
1061 LinstorJournaler.CLONE
1062 ).items())
1064 if has_clone_entries:
1065 util.SMlog(
1066 'Cannot introduce VDIs during scan because it exists '
1067 'CLONE entries in journaler on SR {}'.format(self.uuid)
1068 )
1069 else:
1070 introduce = True
1072 # 4. Now check all volume info.
1073 vdi_to_snaps = {}
1074 for vdi_uuid, volume_info in all_volume_info.items():
1075 if vdi_uuid.startswith(cleanup.SR.TMP_RENAME_PREFIX):
1076 continue
1078 # 4.a. Check if the VDI in LINSTOR is in XAPI VDIs.
1079 if vdi_uuid not in xapi_vdi_uuids:
1080 if not introduce:
1081 continue
1083 if vdi_uuid.startswith('DELETED_'):
1084 continue
1086 volume_metadata = volumes_metadata.get(vdi_uuid)
1087 if not volume_metadata:
1088 util.SMlog(
1089 'Skipping volume {} because no metadata could be found'
1090 .format(vdi_uuid)
1091 )
1092 continue
1094 util.SMlog(
1095 'Trying to introduce VDI {} as it is present in '
1096 'LINSTOR and not in XAPI...'
1097 .format(vdi_uuid)
1098 )
1100 try:
1101 self._linstor.get_device_path(vdi_uuid)
1102 except Exception as e:
1103 util.SMlog(
1104 'Cannot introduce {}, unable to get path: {}'
1105 .format(vdi_uuid, e)
1106 )
1107 continue
1109 name_label = volume_metadata.get(NAME_LABEL_TAG) or ''
1110 type = volume_metadata.get(TYPE_TAG) or 'user'
1111 vdi_type = volume_metadata.get(VDI_TYPE_TAG)
1113 if not vdi_type:
1114 util.SMlog(
1115 'Cannot introduce {} '.format(vdi_uuid) +
1116 'without vdi_type'
1117 )
1118 continue
1120 sm_config = {
1121 'vdi_type': vdi_type
1122 }
1124 if vdi_type == vhdutil.VDI_TYPE_RAW:
1125 managed = not volume_metadata.get(HIDDEN_TAG)
1126 elif vdi_type == vhdutil.VDI_TYPE_VHD:
1127 vhd_info = self._vhdutil.get_vhd_info(vdi_uuid)
1128 managed = not vhd_info.hidden
1129 if vhd_info.parentUuid:
1130 sm_config['vhd-parent'] = vhd_info.parentUuid
1131 else:
1132 util.SMlog(
1133 'Cannot introduce {} with invalid VDI type {}'
1134 .format(vdi_uuid, vdi_type)
1135 )
1136 continue
1138 util.SMlog(
1139 'Introducing VDI {} '.format(vdi_uuid) +
1140 ' (name={}, virtual_size={}, allocated_size={})'.format(
1141 name_label,
1142 volume_info.virtual_size,
1143 volume_info.allocated_size
1144 )
1145 )
1147 vdi_ref = xenapi.VDI.db_introduce(
1148 vdi_uuid,
1149 name_label,
1150 volume_metadata.get(NAME_DESCRIPTION_TAG) or '',
1151 self.sr_ref,
1152 type,
1153 False, # sharable
1154 bool(volume_metadata.get(READ_ONLY_TAG)),
1155 {}, # other_config
1156 vdi_uuid, # location
1157 {}, # xenstore_data
1158 sm_config,
1159 managed,
1160 str(volume_info.virtual_size),
1161 str(volume_info.allocated_size)
1162 )
1164 is_a_snapshot = volume_metadata.get(IS_A_SNAPSHOT_TAG)
1165 xenapi.VDI.set_is_a_snapshot(vdi_ref, bool(is_a_snapshot))
1166 if is_a_snapshot:
1167 xenapi.VDI.set_snapshot_time(
1168 vdi_ref,
1169 xmlrpc.client.DateTime(
1170 volume_metadata[SNAPSHOT_TIME_TAG] or
1171 '19700101T00:00:00Z'
1172 )
1173 )
1175 snap_uuid = volume_metadata[SNAPSHOT_OF_TAG]
1176 if snap_uuid in vdi_to_snaps:
1177 vdi_to_snaps[snap_uuid].append(vdi_uuid)
1178 else:
1179 vdi_to_snaps[snap_uuid] = [vdi_uuid]
1181 # 4.b. Add the VDI in the list.
1182 vdi = self.vdi(vdi_uuid)
1183 self.vdis[vdi_uuid] = vdi
1185 if USE_KEY_HASH and vdi.vdi_type == vhdutil.VDI_TYPE_VHD:
1186 # TODO: Replace pylint comment with this feature when possible:
1187 # https://github.com/PyCQA/pylint/pull/2926
1188 vdi.sm_config_override['key_hash'] = \
1189 self._vhdutil.get_key_hash(vdi_uuid) # pylint: disable = E1120
1191 # 4.c. Update CBT status of disks either just added
1192 # or already in XAPI.
1193 cbt_uuid = volume_metadata.get(CBTLOG_TAG)
1194 if cbt_uuid in cbt_vdis:
1195 vdi_ref = xenapi.VDI.get_by_uuid(vdi_uuid)
1196 xenapi.VDI.set_cbt_enabled(vdi_ref, True)
1197 # For existing VDIs, update local state too.
1198 # Scan in base class SR updates existing VDIs
1199 # again based on local states.
1200 self.vdis[vdi_uuid].cbt_enabled = True
1201 cbt_vdis.remove(cbt_uuid)
1203 # 5. Now set the snapshot statuses correctly in XAPI.
1204 for src_uuid in vdi_to_snaps:
1205 try:
1206 src_ref = xenapi.VDI.get_by_uuid(src_uuid)
1207 except Exception:
1208 # The source VDI no longer exists, continue.
1209 continue
1211 for snap_uuid in vdi_to_snaps[src_uuid]:
1212 try:
1213 # This might fail in cases where its already set.
1214 snap_ref = xenapi.VDI.get_by_uuid(snap_uuid)
1215 xenapi.VDI.set_snapshot_of(snap_ref, src_ref)
1216 except Exception as e:
1217 util.SMlog('Setting snapshot failed: {}'.format(e))
1219 # TODO: Check correctly how to use CBT.
1220 # Update cbt_enabled on the right VDI, check LVM/FileSR code.
1222 # 6. If we have items remaining in this list,
1223 # they are cbt_metadata VDI that XAPI doesn't know about.
1224 # Add them to self.vdis and they'll get added to the DB.
1225 for cbt_uuid in cbt_vdis:
1226 new_vdi = self.vdi(cbt_uuid)
1227 new_vdi.ty = 'cbt_metadata'
1228 new_vdi.cbt_enabled = True
1229 self.vdis[cbt_uuid] = new_vdi
1231 # 7. Update virtual allocation, build geneology and remove useless VDIs
1232 self.virtual_allocation = 0
1234 # 8. Build geneology.
1235 geneology = {}
1237 for vdi_uuid, vdi in self.vdis.items():
1238 if vdi.parent:
1239 if vdi.parent in self.vdis:
1240 self.vdis[vdi.parent].read_only = True
1241 if vdi.parent in geneology:
1242 geneology[vdi.parent].append(vdi_uuid)
1243 else:
1244 geneology[vdi.parent] = [vdi_uuid]
1245 if not vdi.hidden:
1246 self.virtual_allocation += vdi.size
1248 # 9. Remove all hidden leaf nodes to avoid introducing records that
1249 # will be GC'ed.
1250 for vdi_uuid in list(self.vdis.keys()):
1251 if vdi_uuid not in geneology and self.vdis[vdi_uuid].hidden:
1252 util.SMlog(
1253 'Scan found hidden leaf ({}), ignoring'.format(vdi_uuid)
1254 )
1255 del self.vdis[vdi_uuid]
1257 # --------------------------------------------------------------------------
1258 # Journals.
1259 # --------------------------------------------------------------------------
1261 def _get_vdi_path_and_parent(self, vdi_uuid, volume_name):
1262 try:
1263 device_path = self._linstor.build_device_path(volume_name)
1264 if not util.pathexists(device_path):
1265 return (None, None)
1267 # If it's a RAW VDI, there is no parent.
1268 volume_metadata = self._linstor.get_volume_metadata(vdi_uuid)
1269 vdi_type = volume_metadata[VDI_TYPE_TAG]
1270 if vdi_type == vhdutil.VDI_TYPE_RAW:
1271 return (device_path, None)
1273 # Otherwise it's a VHD and a parent can exist.
1274 if not self._vhdutil.check(vdi_uuid):
1275 return (None, None)
1277 vhd_info = self._vhdutil.get_vhd_info(vdi_uuid)
1278 if vhd_info:
1279 return (device_path, vhd_info.parentUuid)
1280 except Exception as e:
1281 util.SMlog(
1282 'Failed to get VDI path and parent, ignoring: {}'
1283 .format(e)
1284 )
1285 return (None, None)
1287 def _undo_all_journal_transactions(self):
1288 util.SMlog('Undoing all journal transactions...')
1289 self.lock.acquire()
1290 try:
1291 self._handle_interrupted_inflate_ops()
1292 self._handle_interrupted_clone_ops()
1293 pass
1294 finally:
1295 self.lock.release()
1297 def _handle_interrupted_inflate_ops(self):
1298 transactions = self._journaler.get_all(LinstorJournaler.INFLATE)
1299 for vdi_uuid, old_size in transactions.items():
1300 self._handle_interrupted_inflate(vdi_uuid, old_size)
1301 self._journaler.remove(LinstorJournaler.INFLATE, vdi_uuid)
1303 def _handle_interrupted_clone_ops(self):
1304 transactions = self._journaler.get_all(LinstorJournaler.CLONE)
1305 for vdi_uuid, old_size in transactions.items():
1306 self._handle_interrupted_clone(vdi_uuid, old_size)
1307 self._journaler.remove(LinstorJournaler.CLONE, vdi_uuid)
1309 def _handle_interrupted_inflate(self, vdi_uuid, old_size):
1310 util.SMlog(
1311 '*** INTERRUPTED INFLATE OP: for {} ({})'
1312 .format(vdi_uuid, old_size)
1313 )
1315 vdi = self.vdis.get(vdi_uuid)
1316 if not vdi:
1317 util.SMlog('Cannot deflate missing VDI {}'.format(vdi_uuid))
1318 return
1320 assert not self._all_volume_info_cache
1321 volume_info = self._linstor.get_volume_info(vdi_uuid)
1323 current_size = volume_info.virtual_size
1324 assert current_size > 0
1325 self._vhdutil.force_deflate(vdi.path, old_size, current_size, zeroize=True)
1327 def _handle_interrupted_clone(
1328 self, vdi_uuid, clone_info, force_undo=False
1329 ):
1330 util.SMlog(
1331 '*** INTERRUPTED CLONE OP: for {} ({})'
1332 .format(vdi_uuid, clone_info)
1333 )
1335 base_uuid, snap_uuid = clone_info.split('_')
1337 # Use LINSTOR data because new VDIs may not be in the XAPI.
1338 volume_names = self._linstor.get_volumes_with_name()
1340 # Check if we don't have a base VDI. (If clone failed at startup.)
1341 if base_uuid not in volume_names:
1342 if vdi_uuid in volume_names:
1343 util.SMlog('*** INTERRUPTED CLONE OP: nothing to do')
1344 return
1345 raise util.SMException(
1346 'Base copy {} not present, but no original {} found'
1347 .format(base_uuid, vdi_uuid)
1348 )
1350 if force_undo:
1351 util.SMlog('Explicit revert')
1352 self._undo_clone(
1353 volume_names, vdi_uuid, base_uuid, snap_uuid
1354 )
1355 return
1357 # If VDI or snap uuid is missing...
1358 if vdi_uuid not in volume_names or \
1359 (snap_uuid and snap_uuid not in volume_names):
1360 util.SMlog('One or both leaves missing => revert')
1361 self._undo_clone(volume_names, vdi_uuid, base_uuid, snap_uuid)
1362 return
1364 vdi_path, vdi_parent_uuid = self._get_vdi_path_and_parent(
1365 vdi_uuid, volume_names[vdi_uuid]
1366 )
1367 snap_path, snap_parent_uuid = self._get_vdi_path_and_parent(
1368 snap_uuid, volume_names[snap_uuid]
1369 )
1371 if not vdi_path or (snap_uuid and not snap_path):
1372 util.SMlog('One or both leaves invalid (and path(s)) => revert')
1373 self._undo_clone(volume_names, vdi_uuid, base_uuid, snap_uuid)
1374 return
1376 util.SMlog('Leaves valid but => revert')
1377 self._undo_clone(volume_names, vdi_uuid, base_uuid, snap_uuid)
1379 def _undo_clone(self, volume_names, vdi_uuid, base_uuid, snap_uuid):
1380 base_path = self._linstor.build_device_path(volume_names[base_uuid])
1381 base_metadata = self._linstor.get_volume_metadata(base_uuid)
1382 base_type = base_metadata[VDI_TYPE_TAG]
1384 if not util.pathexists(base_path):
1385 util.SMlog('Base not found! Exit...')
1386 util.SMlog('*** INTERRUPTED CLONE OP: rollback fail')
1387 return
1389 # Un-hide the parent.
1390 self._linstor.update_volume_metadata(base_uuid, {READ_ONLY_TAG: False})
1391 if base_type == vhdutil.VDI_TYPE_VHD:
1392 vhd_info = self._vhdutil.get_vhd_info(base_uuid, False)
1393 if vhd_info.hidden:
1394 self._vhdutil.set_hidden(base_path, False)
1395 elif base_type == vhdutil.VDI_TYPE_RAW and \
1396 base_metadata.get(HIDDEN_TAG):
1397 self._linstor.update_volume_metadata(
1398 base_uuid, {HIDDEN_TAG: False}
1399 )
1401 # Remove the child nodes.
1402 if snap_uuid and snap_uuid in volume_names:
1403 util.SMlog('Destroying snap {}...'.format(snap_uuid))
1405 try:
1406 self._linstor.destroy_volume(snap_uuid)
1407 except Exception as e:
1408 util.SMlog(
1409 'Cannot destroy snap {} during undo clone: {}'
1410 .format(snap_uuid, e)
1411 )
1413 if vdi_uuid in volume_names:
1414 try:
1415 util.SMlog('Destroying {}...'.format(vdi_uuid))
1416 self._linstor.destroy_volume(vdi_uuid)
1417 except Exception as e:
1418 util.SMlog(
1419 'Cannot destroy VDI {} during undo clone: {}'
1420 .format(vdi_uuid, e)
1421 )
1422 # We can get an exception like this:
1423 # "Shutdown of the DRBD resource 'XXX failed", so the
1424 # volume info remains... The problem is we can't rename
1425 # properly the base VDI below this line, so we must change the
1426 # UUID of this bad VDI before.
1427 self._linstor.update_volume_uuid(
1428 vdi_uuid, 'DELETED_' + vdi_uuid, force=True
1429 )
1431 # Rename!
1432 self._linstor.update_volume_uuid(base_uuid, vdi_uuid)
1434 # Inflate to the right size.
1435 if base_type == vhdutil.VDI_TYPE_VHD:
1436 vdi = self.vdi(vdi_uuid)
1437 volume_size = LinstorVhdUtil.compute_volume_size(vdi.size, vdi.vdi_type)
1438 self._vhdutil.inflate(
1439 self._journaler, vdi_uuid, vdi.path,
1440 volume_size, vdi.capacity
1441 )
1442 self.vdis[vdi_uuid] = vdi
1444 # At this stage, tapdisk and SM vdi will be in paused state. Remove
1445 # flag to facilitate vm deactivate.
1446 vdi_ref = self.session.xenapi.VDI.get_by_uuid(vdi_uuid)
1447 self.session.xenapi.VDI.remove_from_sm_config(vdi_ref, 'paused')
1449 util.SMlog('*** INTERRUPTED CLONE OP: rollback success')
1451 # --------------------------------------------------------------------------
1452 # Cache.
1453 # --------------------------------------------------------------------------
1455 def _create_linstor_cache(self):
1456 reconnect = False
1458 def create_cache():
1459 nonlocal reconnect
1460 try:
1461 if reconnect:
1462 self._reconnect()
1463 return self._linstor.get_volumes_with_info()
1464 except Exception as e:
1465 reconnect = True
1466 raise e
1468 self._all_volume_metadata_cache = \
1469 self._linstor.get_volumes_with_metadata()
1470 self._all_volume_info_cache = util.retry(
1471 create_cache,
1472 maxretry=10,
1473 period=3
1474 )
1476 def _destroy_linstor_cache(self):
1477 self._all_volume_info_cache = None
1478 self._all_volume_metadata_cache = None
1480 # --------------------------------------------------------------------------
1481 # Misc.
1482 # --------------------------------------------------------------------------
1484 def _reconnect(self):
1485 controller_uri = get_controller_uri()
1487 self._journaler = LinstorJournaler(
1488 controller_uri, self._group_name, logger=util.SMlog
1489 )
1491 # Try to open SR if exists.
1492 # We can repair only if we are on the master AND if
1493 # we are trying to execute an exclusive operation.
1494 # Otherwise we could try to delete a VDI being created or
1495 # during a snapshot. An exclusive op is the guarantee that
1496 # the SR is locked.
1497 self._linstor = LinstorVolumeManager(
1498 controller_uri,
1499 self._group_name,
1500 repair=(
1501 self.is_master() and
1502 self.srcmd.cmd in self.ops_exclusive
1503 ),
1504 logger=util.SMlog
1505 )
1506 self._vhdutil = LinstorVhdUtil(self.session, self._linstor)
1508 def _ensure_space_available(self, amount_needed):
1509 space_available = self._linstor.max_volume_size_allowed
1510 if (space_available < amount_needed):
1511 util.SMlog(
1512 'Not enough space! Free space: {}, need: {}'.format(
1513 space_available, amount_needed
1514 )
1515 )
1516 raise xs_errors.XenError('SRNoSpace')
1518 def _kick_gc(self):
1519 # Don't bother if an instance already running. This is just an
1520 # optimization to reduce the overhead of forking a new process if we
1521 # don't have to, but the process will check the lock anyways.
1522 lock = Lock(LOCK_TYPE_GC_RUNNING, self.uuid)
1523 if not lock.acquireNoblock():
1524 if not cleanup.should_preempt(self.session, self.uuid):
1525 util.SMlog('A GC instance already running, not kicking')
1526 return
1528 util.SMlog('Aborting currently-running coalesce of garbage VDI')
1529 try:
1530 if not cleanup.abort(self.uuid, soft=True):
1531 util.SMlog('The GC has already been scheduled to re-start')
1532 except util.CommandException as e:
1533 if e.code != errno.ETIMEDOUT:
1534 raise
1535 util.SMlog('Failed to abort the GC')
1536 else:
1537 lock.release()
1539 util.SMlog('Kicking GC')
1540 cleanup.gc(self.session, self.uuid, True)
1542# ==============================================================================
1543# LinstorSr VDI
1544# ==============================================================================
1547class LinstorVDI(VDI.VDI):
1548 # Warning: Not the same values than vhdutil.VDI_TYPE_*.
1549 # These values represents the types given on the command line.
1550 TYPE_RAW = 'raw'
1551 TYPE_VHD = 'vhd'
1553 # Metadata size given to the "S" param of vhd-util create.
1554 # "-S size (MB) for metadata preallocation".
1555 # Increase the performance when resize is called.
1556 MAX_METADATA_VIRT_SIZE = 2 * 1024 * 1024
1558 # --------------------------------------------------------------------------
1559 # VDI methods.
1560 # --------------------------------------------------------------------------
1562 def load(self, vdi_uuid):
1563 self._lock = self.sr.lock
1564 self._exists = True
1565 self._linstor = self.sr._linstor
1567 # Update hidden parent property.
1568 self.hidden = False
1570 def raise_bad_load(e):
1571 util.SMlog(
1572 'Got exception in LinstorVDI.load: {}'.format(e)
1573 )
1574 util.SMlog(traceback.format_exc())
1575 raise xs_errors.XenError(
1576 'VDIUnavailable',
1577 opterr='Could not load {} because: {}'.format(self.uuid, e)
1578 )
1580 # Try to load VDI.
1581 try:
1582 if (
1583 self.sr.srcmd.cmd == 'vdi_attach_from_config' or
1584 self.sr.srcmd.cmd == 'vdi_detach_from_config'
1585 ):
1586 self.vdi_type = vhdutil.VDI_TYPE_RAW
1587 self.path = self.sr.srcmd.params['vdi_path']
1588 else:
1589 self._determine_type_and_path()
1590 self._load_this()
1592 util.SMlog('VDI {} loaded! (path={}, hidden={})'.format(
1593 self.uuid, self.path, self.hidden
1594 ))
1595 except LinstorVolumeManagerError as e:
1596 # 1. It may be a VDI deletion.
1597 if e.code == LinstorVolumeManagerError.ERR_VOLUME_NOT_EXISTS:
1598 if self.sr.srcmd.cmd == 'vdi_delete':
1599 self.deleted = True
1600 return
1602 # 2. Or maybe a creation.
1603 if self.sr.srcmd.cmd == 'vdi_create':
1604 # Set type attribute of VDI parent class.
1605 # We use VHD by default.
1606 self.vdi_type = vhdutil.VDI_TYPE_VHD
1607 self._key_hash = None # Only used in create.
1609 self._exists = False
1610 vdi_sm_config = self.sr.srcmd.params.get('vdi_sm_config')
1611 if vdi_sm_config is not None:
1612 type = vdi_sm_config.get('type')
1613 if type is not None:
1614 if type == self.TYPE_RAW:
1615 self.vdi_type = vhdutil.VDI_TYPE_RAW
1616 elif type == self.TYPE_VHD:
1617 self.vdi_type = vhdutil.VDI_TYPE_VHD
1618 else:
1619 raise xs_errors.XenError(
1620 'VDICreate',
1621 opterr='Invalid VDI type {}'.format(type)
1622 )
1623 if self.vdi_type == vhdutil.VDI_TYPE_VHD:
1624 self._key_hash = vdi_sm_config.get('key_hash')
1626 # For the moment we don't have a path.
1627 self._update_device_name(None)
1628 return
1629 raise_bad_load(e)
1630 except Exception as e:
1631 raise_bad_load(e)
1633 def create(self, sr_uuid, vdi_uuid, size):
1634 # Usage example:
1635 # xe vdi-create sr-uuid=39a5826b-5a90-73eb-dd09-51e3a116f937
1636 # name-label="linstor-vdi-1" virtual-size=4096MiB sm-config:type=vhd
1638 # 1. Check if we are on the master and if the VDI doesn't exist.
1639 util.SMlog('LinstorVDI.create for {}'.format(self.uuid))
1640 if self._exists:
1641 raise xs_errors.XenError('VDIExists')
1643 assert self.uuid
1644 assert self.ty
1645 assert self.vdi_type
1647 # 2. Compute size and check space available.
1648 size = vhdutil.validate_and_round_vhd_size(int(size))
1649 volume_size = LinstorVhdUtil.compute_volume_size(size, self.vdi_type)
1650 util.SMlog(
1651 'LinstorVDI.create: type={}, vhd-size={}, volume-size={}'
1652 .format(self.vdi_type, size, volume_size)
1653 )
1654 self.sr._ensure_space_available(volume_size)
1656 # 3. Set sm_config attribute of VDI parent class.
1657 self.sm_config = self.sr.srcmd.params['vdi_sm_config']
1659 # 4. Create!
1660 failed = False
1661 try:
1662 volume_name = None
1663 if self.ty == 'ha_statefile':
1664 volume_name = HA_VOLUME_NAME
1665 elif self.ty == 'redo_log':
1666 volume_name = REDO_LOG_VOLUME_NAME
1668 self._linstor.create_volume(
1669 self.uuid,
1670 volume_size,
1671 persistent=False,
1672 volume_name=volume_name,
1673 high_availability=volume_name is not None
1674 )
1675 volume_info = self._linstor.get_volume_info(self.uuid)
1677 self._update_device_name(volume_info.name)
1679 if self.vdi_type == vhdutil.VDI_TYPE_RAW:
1680 self.size = volume_info.virtual_size
1681 else:
1682 self.sr._vhdutil.create(
1683 self.path, size, False, self.MAX_METADATA_VIRT_SIZE
1684 )
1685 self.size = self.sr._vhdutil.get_size_virt(self.uuid)
1687 if self._key_hash:
1688 self.sr._vhdutil.set_key(self.path, self._key_hash)
1690 # Because vhdutil commands modify the volume data,
1691 # we must retrieve a new time the utilization size.
1692 volume_info = self._linstor.get_volume_info(self.uuid)
1694 volume_metadata = {
1695 NAME_LABEL_TAG: util.to_plain_string(self.label),
1696 NAME_DESCRIPTION_TAG: util.to_plain_string(self.description),
1697 IS_A_SNAPSHOT_TAG: False,
1698 SNAPSHOT_OF_TAG: '',
1699 SNAPSHOT_TIME_TAG: '',
1700 TYPE_TAG: self.ty,
1701 VDI_TYPE_TAG: self.vdi_type,
1702 READ_ONLY_TAG: bool(self.read_only),
1703 METADATA_OF_POOL_TAG: ''
1704 }
1705 self._linstor.set_volume_metadata(self.uuid, volume_metadata)
1707 # Set the open timeout to 1min to reduce CPU usage
1708 # in http-disk-server when a secondary server tries to open
1709 # an already opened volume.
1710 if self.ty == 'ha_statefile' or self.ty == 'redo_log':
1711 self._linstor.set_auto_promote_timeout(self.uuid, 600)
1713 self._linstor.mark_volume_as_persistent(self.uuid)
1714 except util.CommandException as e:
1715 failed = True
1716 raise xs_errors.XenError(
1717 'VDICreate', opterr='error {}'.format(e.code)
1718 )
1719 except Exception as e:
1720 failed = True
1721 raise xs_errors.XenError('VDICreate', opterr='error {}'.format(e))
1722 finally:
1723 if failed:
1724 util.SMlog('Unable to create VDI {}'.format(self.uuid))
1725 try:
1726 self._linstor.destroy_volume(self.uuid)
1727 except Exception as e:
1728 util.SMlog(
1729 'Ignoring exception after fail in LinstorVDI.create: '
1730 '{}'.format(e)
1731 )
1733 self.utilisation = volume_info.allocated_size
1734 self.sm_config['vdi_type'] = self.vdi_type
1736 self.ref = self._db_introduce()
1737 self.sr._update_stats(self.size)
1739 return VDI.VDI.get_params(self)
1741 def delete(self, sr_uuid, vdi_uuid, data_only=False):
1742 util.SMlog('LinstorVDI.delete for {}'.format(self.uuid))
1743 if self.attached:
1744 raise xs_errors.XenError('VDIInUse')
1746 if self.deleted:
1747 return super(LinstorVDI, self).delete(
1748 sr_uuid, vdi_uuid, data_only
1749 )
1751 vdi_ref = self.sr.srcmd.params['vdi_ref']
1752 if not self.session.xenapi.VDI.get_managed(vdi_ref):
1753 raise xs_errors.XenError(
1754 'VDIDelete',
1755 opterr='Deleting non-leaf node not permitted'
1756 )
1758 try:
1759 # Remove from XAPI and delete from LINSTOR.
1760 self._linstor.destroy_volume(self.uuid)
1761 if not data_only:
1762 self._db_forget()
1764 self.sr.lock.cleanupAll(vdi_uuid)
1765 except Exception as e:
1766 util.SMlog(
1767 'Failed to remove the volume (maybe is leaf coalescing) '
1768 'for {} err: {}'.format(self.uuid, e)
1769 )
1771 try:
1772 raise xs_errors.XenError('VDIDelete', opterr=str(e))
1773 except LinstorVolumeManagerError as e:
1774 if e.code != LinstorVolumeManagerError.ERR_VOLUME_DESTROY:
1775 raise xs_errors.XenError('VDIDelete', opterr=str(e))
1777 return
1779 if self.uuid in self.sr.vdis:
1780 del self.sr.vdis[self.uuid]
1782 # TODO: Check size after delete.
1783 self.sr._update_stats(-self.size)
1784 self.sr._kick_gc()
1785 return super(LinstorVDI, self).delete(sr_uuid, vdi_uuid, data_only)
1787 def attach(self, sr_uuid, vdi_uuid):
1788 util.SMlog('LinstorVDI.attach for {}'.format(self.uuid))
1789 attach_from_config = self.sr.srcmd.cmd == 'vdi_attach_from_config'
1790 if (
1791 not attach_from_config or
1792 self.sr.srcmd.params['vdi_uuid'] != self.uuid
1793 ) and self.sr._journaler.has_entries(self.uuid):
1794 raise xs_errors.XenError(
1795 'VDIUnavailable',
1796 opterr='Interrupted operation detected on this VDI, '
1797 'scan SR first to trigger auto-repair'
1798 )
1800 if not attach_from_config or self.sr._is_master():
1801 writable = 'args' not in self.sr.srcmd.params or \
1802 self.sr.srcmd.params['args'][0] == 'true'
1804 if not attach_from_config or self.sr.is_master():
1805 # We need to inflate the volume if we don't have enough place
1806 # to mount the VHD image. I.e. the volume capacity must be greater
1807 # than the VHD size + bitmap size.
1808 need_inflate = True
1809 if (
1810 self.vdi_type == vhdutil.VDI_TYPE_RAW or
1811 not writable or
1812 self.capacity >= LinstorVhdUtil.compute_volume_size(self.size, self.vdi_type)
1813 ):
1814 need_inflate = False
1816 if need_inflate:
1817 try:
1818 self._prepare_thin(True)
1819 except Exception as e:
1820 raise xs_errors.XenError(
1821 'VDIUnavailable',
1822 opterr='Failed to attach VDI during "prepare thin": {}'
1823 .format(e)
1824 )
1826 if not hasattr(self, 'xenstore_data'):
1827 self.xenstore_data = {}
1828 self.xenstore_data['storage-type'] = LinstorSR.DRIVER_TYPE
1830 if (
1831 USE_HTTP_NBD_SERVERS and
1832 attach_from_config and
1833 self.path.startswith('/dev/http-nbd/')
1834 ):
1835 return self._attach_using_http_nbd()
1837 # Ensure we have a path...
1838 self.sr._vhdutil.create_chain_paths(self.uuid, readonly=not writable)
1840 self.attached = True
1841 return VDI.VDI.attach(self, self.sr.uuid, self.uuid)
1843 def detach(self, sr_uuid, vdi_uuid):
1844 util.SMlog('LinstorVDI.detach for {}'.format(self.uuid))
1845 detach_from_config = self.sr.srcmd.cmd == 'vdi_detach_from_config'
1846 self.attached = False
1848 if detach_from_config and self.path.startswith('/dev/http-nbd/'):
1849 return self._detach_using_http_nbd()
1851 if self.vdi_type == vhdutil.VDI_TYPE_RAW:
1852 return
1854 # The VDI is already deflated if the VHD image size + metadata is
1855 # equal to the LINSTOR volume size.
1856 volume_size = LinstorVhdUtil.compute_volume_size(self.size, self.vdi_type)
1857 already_deflated = self.capacity <= volume_size
1859 if already_deflated:
1860 util.SMlog(
1861 'VDI {} already deflated (old volume size={}, volume size={})'
1862 .format(self.uuid, self.capacity, volume_size)
1863 )
1865 need_deflate = True
1866 if already_deflated:
1867 need_deflate = False
1868 elif self.sr._provisioning == 'thick':
1869 need_deflate = False
1871 vdi_ref = self.sr.srcmd.params['vdi_ref']
1872 if self.session.xenapi.VDI.get_is_a_snapshot(vdi_ref):
1873 need_deflate = True
1875 if need_deflate:
1876 try:
1877 self._prepare_thin(False)
1878 except Exception as e:
1879 raise xs_errors.XenError(
1880 'VDIUnavailable',
1881 opterr='Failed to detach VDI during "prepare thin": {}'
1882 .format(e)
1883 )
1885 # We remove only on slaves because the volume can be used by the GC.
1886 if self.sr.is_master():
1887 return
1889 while vdi_uuid:
1890 try:
1891 path = self._linstor.build_device_path(self._linstor.get_volume_name(vdi_uuid))
1892 parent_vdi_uuid = self.sr._vhdutil.get_vhd_info(vdi_uuid).parentUuid
1893 except Exception:
1894 break
1896 if util.pathexists(path):
1897 try:
1898 self._linstor.remove_volume_if_diskless(vdi_uuid)
1899 except Exception as e:
1900 # Ensure we can always detach properly.
1901 # I don't want to corrupt the XAPI info.
1902 util.SMlog('Failed to clean VDI {} during detach: {}'.format(vdi_uuid, e))
1903 vdi_uuid = parent_vdi_uuid
1905 def resize(self, sr_uuid, vdi_uuid, size):
1906 util.SMlog('LinstorVDI.resize for {}'.format(self.uuid))
1907 if not self.sr.is_master():
1908 raise xs_errors.XenError(
1909 'VDISize',
1910 opterr='resize on slave not allowed'
1911 )
1913 if self.hidden:
1914 raise xs_errors.XenError('VDIUnavailable', opterr='hidden VDI')
1916 # Compute the virtual VHD and DRBD volume size.
1917 size = vhdutil.validate_and_round_vhd_size(int(size))
1918 volume_size = LinstorVhdUtil.compute_volume_size(size, self.vdi_type)
1919 util.SMlog(
1920 'LinstorVDI.resize: type={}, vhd-size={}, volume-size={}'
1921 .format(self.vdi_type, size, volume_size)
1922 )
1924 if size < self.size:
1925 util.SMlog(
1926 'vdi_resize: shrinking not supported: '
1927 '(current size: {}, new size: {})'.format(self.size, size)
1928 )
1929 raise xs_errors.XenError('VDISize', opterr='shrinking not allowed')
1931 if size == self.size:
1932 return VDI.VDI.get_params(self)
1934 if self.vdi_type == vhdutil.VDI_TYPE_RAW:
1935 old_volume_size = self.size
1936 new_volume_size = LinstorVolumeManager.round_up_volume_size(size)
1937 else:
1938 old_volume_size = self.utilisation
1939 if self.sr._provisioning == 'thin':
1940 # VDI is currently deflated, so keep it deflated.
1941 new_volume_size = old_volume_size
1942 else:
1943 new_volume_size = LinstorVhdUtil.compute_volume_size(size, self.vdi_type)
1944 assert new_volume_size >= old_volume_size
1946 space_needed = new_volume_size - old_volume_size
1947 self.sr._ensure_space_available(space_needed)
1949 old_size = self.size
1950 if self.vdi_type == vhdutil.VDI_TYPE_RAW:
1951 self._linstor.resize(self.uuid, new_volume_size)
1952 else:
1953 if new_volume_size != old_volume_size:
1954 self.sr._vhdutil.inflate(
1955 self.sr._journaler, self.uuid, self.path,
1956 new_volume_size, old_volume_size
1957 )
1958 self.sr._vhdutil.set_size_virt_fast(self.path, size)
1960 # Reload size attributes.
1961 self._load_this()
1963 vdi_ref = self.sr.srcmd.params['vdi_ref']
1964 self.session.xenapi.VDI.set_virtual_size(vdi_ref, str(self.size))
1965 self.session.xenapi.VDI.set_physical_utilisation(
1966 vdi_ref, str(self.utilisation)
1967 )
1968 self.sr._update_stats(self.size - old_size)
1969 return VDI.VDI.get_params(self)
1971 def clone(self, sr_uuid, vdi_uuid):
1972 return self._do_snapshot(sr_uuid, vdi_uuid, VDI.SNAPSHOT_DOUBLE)
1974 def compose(self, sr_uuid, vdi1, vdi2):
1975 util.SMlog('VDI.compose for {} -> {}'.format(vdi2, vdi1))
1976 if self.vdi_type != vhdutil.VDI_TYPE_VHD:
1977 raise xs_errors.XenError('Unimplemented')
1979 parent_uuid = vdi1
1980 parent_path = self._linstor.get_device_path(parent_uuid)
1982 # We must pause tapdisk to correctly change the parent. Otherwise we
1983 # have a readonly error.
1984 # See: https://github.com/xapi-project/xen-api/blob/b3169a16d36dae0654881b336801910811a399d9/ocaml/xapi/storage_migrate.ml#L928-L929
1985 # and: https://github.com/xapi-project/xen-api/blob/b3169a16d36dae0654881b336801910811a399d9/ocaml/xapi/storage_migrate.ml#L775
1987 if not blktap2.VDI.tap_pause(self.session, self.sr.uuid, self.uuid):
1988 raise util.SMException('Failed to pause VDI {}'.format(self.uuid))
1989 try:
1990 self.sr._vhdutil.set_parent(self.path, parent_path, False)
1991 self.sr._vhdutil.set_hidden(parent_path)
1992 self.sr.session.xenapi.VDI.set_managed(
1993 self.sr.srcmd.params['args'][0], False
1994 )
1995 finally:
1996 blktap2.VDI.tap_unpause(self.session, self.sr.uuid, self.uuid)
1998 if not blktap2.VDI.tap_refresh(self.session, self.sr.uuid, self.uuid):
1999 raise util.SMException(
2000 'Failed to refresh VDI {}'.format(self.uuid)
2001 )
2003 util.SMlog('Compose done')
2005 def generate_config(self, sr_uuid, vdi_uuid):
2006 """
2007 Generate the XML config required to attach and activate
2008 a VDI for use when XAPI is not running. Attach and
2009 activation is handled by vdi_attach_from_config below.
2010 """
2012 util.SMlog('LinstorVDI.generate_config for {}'.format(self.uuid))
2014 resp = {}
2015 resp['device_config'] = self.sr.dconf
2016 resp['sr_uuid'] = sr_uuid
2017 resp['vdi_uuid'] = self.uuid
2018 resp['sr_sm_config'] = self.sr.sm_config
2019 resp['command'] = 'vdi_attach_from_config'
2021 # By default, we generate a normal config.
2022 # But if the disk is persistent, we must use a HTTP/NBD
2023 # server to ensure we can always write or read data.
2024 # Why? DRBD is unsafe when used with more than 4 hosts:
2025 # We are limited to use 1 diskless and 3 full.
2026 # We can't increase this limitation, so we use a NBD/HTTP device
2027 # instead.
2028 volume_name = self._linstor.get_volume_name(self.uuid)
2029 if not USE_HTTP_NBD_SERVERS or volume_name not in [
2030 HA_VOLUME_NAME, REDO_LOG_VOLUME_NAME
2031 ]:
2032 if not self.path or not util.pathexists(self.path):
2033 available = False
2034 # Try to refresh symlink path...
2035 try:
2036 self.path = self._linstor.get_device_path(vdi_uuid)
2037 available = util.pathexists(self.path)
2038 except Exception:
2039 pass
2040 if not available:
2041 raise xs_errors.XenError('VDIUnavailable')
2043 resp['vdi_path'] = self.path
2044 else:
2045 # Axiom: DRBD device is present on at least one host.
2046 resp['vdi_path'] = '/dev/http-nbd/' + volume_name
2048 config = xmlrpc.client.dumps(tuple([resp]), 'vdi_attach_from_config')
2049 return xmlrpc.client.dumps((config,), "", True)
2051 def attach_from_config(self, sr_uuid, vdi_uuid):
2052 """
2053 Attach and activate a VDI using config generated by
2054 vdi_generate_config above. This is used for cases such as
2055 the HA state-file and the redo-log.
2056 """
2058 util.SMlog('LinstorVDI.attach_from_config for {}'.format(vdi_uuid))
2060 try:
2061 if not util.pathexists(self.sr.path):
2062 self.sr.attach(sr_uuid)
2064 if not DRIVER_CONFIG['ATTACH_FROM_CONFIG_WITH_TAPDISK']:
2065 return self.attach(sr_uuid, vdi_uuid)
2066 except Exception:
2067 util.logException('LinstorVDI.attach_from_config')
2068 raise xs_errors.XenError(
2069 'SRUnavailable',
2070 opterr='Unable to attach from config'
2071 )
2073 def reset_leaf(self, sr_uuid, vdi_uuid):
2074 if self.vdi_type != vhdutil.VDI_TYPE_VHD:
2075 raise xs_errors.XenError('Unimplemented')
2077 if not self.sr._vhdutil.has_parent(self.uuid):
2078 raise util.SMException(
2079 'ERROR: VDI {} has no parent, will not reset contents'
2080 .format(self.uuid)
2081 )
2083 self.sr._vhdutil.kill_data(self.path)
2085 def _load_this(self):
2086 volume_metadata = None
2087 if self.sr._all_volume_metadata_cache:
2088 volume_metadata = self.sr._all_volume_metadata_cache.get(self.uuid)
2089 if volume_metadata is None:
2090 volume_metadata = self._linstor.get_volume_metadata(self.uuid)
2092 volume_info = None
2093 if self.sr._all_volume_info_cache:
2094 volume_info = self.sr._all_volume_info_cache.get(self.uuid)
2095 if volume_info is None:
2096 volume_info = self._linstor.get_volume_info(self.uuid)
2098 # Contains the max physical size used on a disk.
2099 # When LINSTOR LVM driver is used, the size should be similar to
2100 # virtual size (i.e. the LINSTOR max volume size).
2101 # When LINSTOR Thin LVM driver is used, the used physical size should
2102 # be lower than virtual size at creation.
2103 # The physical size increases after each write in a new block.
2104 self.utilisation = volume_info.allocated_size
2105 self.capacity = volume_info.virtual_size
2107 if self.vdi_type == vhdutil.VDI_TYPE_RAW:
2108 self.hidden = int(volume_metadata.get(HIDDEN_TAG) or 0)
2109 self.size = volume_info.virtual_size
2110 self.parent = ''
2111 else:
2112 vhd_info = self.sr._vhdutil.get_vhd_info(self.uuid)
2113 self.hidden = vhd_info.hidden
2114 self.size = vhd_info.sizeVirt
2115 self.parent = vhd_info.parentUuid
2117 if self.hidden:
2118 self.managed = False
2120 self.label = volume_metadata.get(NAME_LABEL_TAG) or ''
2121 self.description = volume_metadata.get(NAME_DESCRIPTION_TAG) or ''
2123 # Update sm_config_override of VDI parent class.
2124 self.sm_config_override = {'vhd-parent': self.parent or None}
2126 def _mark_hidden(self, hidden=True):
2127 if self.hidden == hidden:
2128 return
2130 if self.vdi_type == vhdutil.VDI_TYPE_VHD:
2131 self.sr._vhdutil.set_hidden(self.path, hidden)
2132 else:
2133 self._linstor.update_volume_metadata(self.uuid, {
2134 HIDDEN_TAG: hidden
2135 })
2136 self.hidden = hidden
2138 def update(self, sr_uuid, vdi_uuid):
2139 xenapi = self.session.xenapi
2140 vdi_ref = xenapi.VDI.get_by_uuid(self.uuid)
2142 volume_metadata = {
2143 NAME_LABEL_TAG: util.to_plain_string(
2144 xenapi.VDI.get_name_label(vdi_ref)
2145 ),
2146 NAME_DESCRIPTION_TAG: util.to_plain_string(
2147 xenapi.VDI.get_name_description(vdi_ref)
2148 )
2149 }
2151 try:
2152 self._linstor.update_volume_metadata(self.uuid, volume_metadata)
2153 except LinstorVolumeManagerError as e:
2154 if e.code == LinstorVolumeManagerError.ERR_VOLUME_NOT_EXISTS:
2155 raise xs_errors.XenError(
2156 'VDIUnavailable',
2157 opterr='LINSTOR volume {} not found'.format(self.uuid)
2158 )
2159 raise xs_errors.XenError('VDIUnavailable', opterr=str(e))
2161 # --------------------------------------------------------------------------
2162 # Thin provisioning.
2163 # --------------------------------------------------------------------------
2165 def _prepare_thin(self, attach):
2166 if self.sr.is_master():
2167 if attach:
2168 attach_thin(
2169 self.session, self.sr._journaler, self._linstor,
2170 self.sr.uuid, self.uuid
2171 )
2172 else:
2173 detach_thin(
2174 self.session, self._linstor, self.sr.uuid, self.uuid
2175 )
2176 else:
2177 fn = 'attach' if attach else 'detach'
2179 master = util.get_master_ref(self.session)
2181 args = {
2182 'groupName': self.sr._group_name,
2183 'srUuid': self.sr.uuid,
2184 'vdiUuid': self.uuid
2185 }
2187 try:
2188 self.sr._exec_manager_command(master, fn, args, 'VDIUnavailable')
2189 except Exception:
2190 if fn != 'detach':
2191 raise
2193 # Reload size attrs after inflate or deflate!
2194 self._load_this()
2195 self.sr._update_physical_size()
2197 vdi_ref = self.sr.srcmd.params['vdi_ref']
2198 self.session.xenapi.VDI.set_physical_utilisation(
2199 vdi_ref, str(self.utilisation)
2200 )
2202 self.session.xenapi.SR.set_physical_utilisation(
2203 self.sr.sr_ref, str(self.sr.physical_utilisation)
2204 )
2206 # --------------------------------------------------------------------------
2207 # Generic helpers.
2208 # --------------------------------------------------------------------------
2210 def _determine_type_and_path(self):
2211 """
2212 Determine whether this is a RAW or a VHD VDI.
2213 """
2215 # 1. Check vdi_ref and vdi_type in config.
2216 try:
2217 vdi_ref = self.session.xenapi.VDI.get_by_uuid(self.uuid)
2218 if vdi_ref:
2219 sm_config = self.session.xenapi.VDI.get_sm_config(vdi_ref)
2220 vdi_type = sm_config.get('vdi_type')
2221 if vdi_type:
2222 # Update parent fields.
2223 self.vdi_type = vdi_type
2224 self.sm_config_override = sm_config
2225 self._update_device_name(
2226 self._linstor.get_volume_name(self.uuid)
2227 )
2228 return
2229 except Exception:
2230 pass
2232 # 2. Otherwise use the LINSTOR volume manager directly.
2233 # It's probably a new VDI created via snapshot.
2234 volume_metadata = self._linstor.get_volume_metadata(self.uuid)
2235 self.vdi_type = volume_metadata.get(VDI_TYPE_TAG)
2236 if not self.vdi_type:
2237 raise xs_errors.XenError(
2238 'VDIUnavailable',
2239 opterr='failed to get vdi_type in metadata'
2240 )
2241 self._update_device_name(self._linstor.get_volume_name(self.uuid))
2243 def _update_device_name(self, device_name):
2244 self._device_name = device_name
2246 # Mark path of VDI parent class.
2247 if device_name:
2248 self.path = self._linstor.build_device_path(self._device_name)
2249 else:
2250 self.path = None
2252 def _create_snapshot(self, snap_uuid, snap_of_uuid=None):
2253 """
2254 Snapshot self and return the snapshot VDI object.
2255 """
2257 # 1. Create a new LINSTOR volume with the same size than self.
2258 snap_path = self._linstor.shallow_clone_volume(
2259 self.uuid, snap_uuid, persistent=False
2260 )
2262 # 2. Write the snapshot content.
2263 is_raw = (self.vdi_type == vhdutil.VDI_TYPE_RAW)
2264 self.sr._vhdutil.snapshot(
2265 snap_path, self.path, is_raw, self.MAX_METADATA_VIRT_SIZE
2266 )
2268 # 3. Get snapshot parent.
2269 snap_parent = self.sr._vhdutil.get_parent(snap_uuid)
2271 # 4. Update metadata.
2272 util.SMlog('Set VDI {} metadata of snapshot'.format(snap_uuid))
2273 volume_metadata = {
2274 NAME_LABEL_TAG: util.to_plain_string(self.label),
2275 NAME_DESCRIPTION_TAG: util.to_plain_string(self.description),
2276 IS_A_SNAPSHOT_TAG: bool(snap_of_uuid),
2277 SNAPSHOT_OF_TAG: snap_of_uuid,
2278 SNAPSHOT_TIME_TAG: '',
2279 TYPE_TAG: self.ty,
2280 VDI_TYPE_TAG: vhdutil.VDI_TYPE_VHD,
2281 READ_ONLY_TAG: False,
2282 METADATA_OF_POOL_TAG: ''
2283 }
2284 self._linstor.set_volume_metadata(snap_uuid, volume_metadata)
2286 # 5. Set size.
2287 snap_vdi = LinstorVDI(self.sr, snap_uuid)
2288 if not snap_vdi._exists:
2289 raise xs_errors.XenError('VDISnapshot')
2291 volume_info = self._linstor.get_volume_info(snap_uuid)
2293 snap_vdi.size = self.sr._vhdutil.get_size_virt(snap_uuid)
2294 snap_vdi.utilisation = volume_info.allocated_size
2296 # 6. Update sm config.
2297 snap_vdi.sm_config = {}
2298 snap_vdi.sm_config['vdi_type'] = snap_vdi.vdi_type
2299 if snap_parent:
2300 snap_vdi.sm_config['vhd-parent'] = snap_parent
2301 snap_vdi.parent = snap_parent
2303 snap_vdi.label = self.label
2304 snap_vdi.description = self.description
2306 self._linstor.mark_volume_as_persistent(snap_uuid)
2308 return snap_vdi
2310 # --------------------------------------------------------------------------
2311 # Implement specific SR methods.
2312 # --------------------------------------------------------------------------
2314 def _rename(self, oldpath, newpath):
2315 # TODO: I'm not sure... Used by CBT.
2316 volume_uuid = self._linstor.get_volume_uuid_from_device_path(oldpath)
2317 self._linstor.update_volume_name(volume_uuid, newpath)
2319 def _do_snapshot(
2320 self, sr_uuid, vdi_uuid, snap_type, secondary=None, cbtlog=None
2321 ):
2322 # If cbt enabled, save file consistency state.
2323 if cbtlog is not None:
2324 if blktap2.VDI.tap_status(self.session, vdi_uuid):
2325 consistency_state = False
2326 else:
2327 consistency_state = True
2328 util.SMlog(
2329 'Saving log consistency state of {} for vdi: {}'
2330 .format(consistency_state, vdi_uuid)
2331 )
2332 else:
2333 consistency_state = None
2335 if self.vdi_type != vhdutil.VDI_TYPE_VHD:
2336 raise xs_errors.XenError('Unimplemented')
2338 if not blktap2.VDI.tap_pause(self.session, sr_uuid, vdi_uuid):
2339 raise util.SMException('Failed to pause VDI {}'.format(vdi_uuid))
2340 try:
2341 return self._snapshot(snap_type, cbtlog, consistency_state)
2342 finally:
2343 blktap2.VDI.tap_unpause(self.session, sr_uuid, vdi_uuid, secondary)
2345 def _snapshot(self, snap_type, cbtlog=None, cbt_consistency=None):
2346 util.SMlog(
2347 'LinstorVDI._snapshot for {} (type {})'
2348 .format(self.uuid, snap_type)
2349 )
2351 # 1. Checks...
2352 if self.hidden:
2353 raise xs_errors.XenError('VDIClone', opterr='hidden VDI')
2355 depth = self.sr._vhdutil.get_depth(self.uuid)
2356 if depth == -1:
2357 raise xs_errors.XenError(
2358 'VDIUnavailable',
2359 opterr='failed to get VHD depth'
2360 )
2361 elif depth >= vhdutil.MAX_CHAIN_SIZE:
2362 raise xs_errors.XenError('SnapshotChainTooLong')
2364 # Ensure we have a valid path if we don't have a local diskful.
2365 self.sr._vhdutil.create_chain_paths(self.uuid, readonly=True)
2367 volume_path = self.path
2368 if not util.pathexists(volume_path):
2369 raise xs_errors.XenError(
2370 'EIO',
2371 opterr='IO error checking path {}'.format(volume_path)
2372 )
2374 # 2. Create base and snap uuid (if required) and a journal entry.
2375 base_uuid = util.gen_uuid()
2376 snap_uuid = None
2378 if snap_type == VDI.SNAPSHOT_DOUBLE:
2379 snap_uuid = util.gen_uuid()
2381 clone_info = '{}_{}'.format(base_uuid, snap_uuid)
2383 active_uuid = self.uuid
2384 self.sr._journaler.create(
2385 LinstorJournaler.CLONE, active_uuid, clone_info
2386 )
2388 try:
2389 # 3. Self becomes the new base.
2390 # The device path remains the same.
2391 self._linstor.update_volume_uuid(self.uuid, base_uuid)
2392 self.uuid = base_uuid
2393 self.location = self.uuid
2394 self.read_only = True
2395 self.managed = False
2397 # 4. Create snapshots (new active and snap).
2398 active_vdi = self._create_snapshot(active_uuid)
2400 snap_vdi = None
2401 if snap_type == VDI.SNAPSHOT_DOUBLE:
2402 snap_vdi = self._create_snapshot(snap_uuid, active_uuid)
2404 self.label = 'base copy'
2405 self.description = ''
2407 # 5. Mark the base VDI as hidden so that it does not show up
2408 # in subsequent scans.
2409 self._mark_hidden()
2410 self._linstor.update_volume_metadata(
2411 self.uuid, {READ_ONLY_TAG: True}
2412 )
2414 # 6. We must update the new active VDI with the "paused" and
2415 # "host_" properties. Why? Because the original VDI has been
2416 # paused and we we must unpause it after the snapshot.
2417 # See: `tap_unpause` in `blktap2.py`.
2418 vdi_ref = self.session.xenapi.VDI.get_by_uuid(active_uuid)
2419 sm_config = self.session.xenapi.VDI.get_sm_config(vdi_ref)
2420 for key in [x for x in sm_config.keys() if x == 'paused' or x.startswith('host_')]:
2421 active_vdi.sm_config[key] = sm_config[key]
2423 # 7. Verify parent locator field of both children and
2424 # delete base if unused.
2425 introduce_parent = True
2426 try:
2427 snap_parent = None
2428 if snap_vdi:
2429 snap_parent = snap_vdi.parent
2431 if active_vdi.parent != self.uuid and (
2432 snap_type == VDI.SNAPSHOT_SINGLE or
2433 snap_type == VDI.SNAPSHOT_INTERNAL or
2434 snap_parent != self.uuid
2435 ):
2436 util.SMlog(
2437 'Destroy unused base volume: {} (path={})'
2438 .format(self.uuid, self.path)
2439 )
2440 introduce_parent = False
2441 self._linstor.destroy_volume(self.uuid)
2442 except Exception as e:
2443 util.SMlog('Ignoring exception: {}'.format(e))
2444 pass
2446 # 8. Introduce the new VDI records.
2447 if snap_vdi:
2448 # If the parent is encrypted set the key_hash for the
2449 # new snapshot disk.
2450 vdi_ref = self.sr.srcmd.params['vdi_ref']
2451 sm_config = self.session.xenapi.VDI.get_sm_config(vdi_ref)
2452 # TODO: Maybe remove key_hash support.
2453 if 'key_hash' in sm_config:
2454 snap_vdi.sm_config['key_hash'] = sm_config['key_hash']
2455 # If we have CBT enabled on the VDI,
2456 # set CBT status for the new snapshot disk.
2457 if cbtlog:
2458 snap_vdi.cbt_enabled = True
2460 if snap_vdi:
2461 snap_vdi_ref = snap_vdi._db_introduce()
2462 util.SMlog(
2463 'vdi_clone: introduced VDI: {} ({})'
2464 .format(snap_vdi_ref, snap_vdi.uuid)
2465 )
2466 if introduce_parent:
2467 base_vdi_ref = self._db_introduce()
2468 self.session.xenapi.VDI.set_managed(base_vdi_ref, False)
2469 util.SMlog(
2470 'vdi_clone: introduced VDI: {} ({})'
2471 .format(base_vdi_ref, self.uuid)
2472 )
2473 self._linstor.update_volume_metadata(self.uuid, {
2474 NAME_LABEL_TAG: util.to_plain_string(self.label),
2475 NAME_DESCRIPTION_TAG: util.to_plain_string(
2476 self.description
2477 ),
2478 READ_ONLY_TAG: True,
2479 METADATA_OF_POOL_TAG: ''
2480 })
2482 # 9. Update cbt files if user created snapshot (SNAPSHOT_DOUBLE)
2483 if snap_type == VDI.SNAPSHOT_DOUBLE and cbtlog:
2484 try:
2485 self._cbt_snapshot(snap_uuid, cbt_consistency)
2486 except Exception:
2487 # CBT operation failed.
2488 # TODO: Implement me.
2489 raise
2491 if snap_type != VDI.SNAPSHOT_INTERNAL:
2492 self.sr._update_stats(self.size)
2494 # 10. Return info on the new user-visible leaf VDI.
2495 ret_vdi = snap_vdi
2496 if not ret_vdi:
2497 ret_vdi = self
2498 if not ret_vdi:
2499 ret_vdi = active_vdi
2501 vdi_ref = self.sr.srcmd.params['vdi_ref']
2502 self.session.xenapi.VDI.set_sm_config(
2503 vdi_ref, active_vdi.sm_config
2504 )
2505 except Exception:
2506 util.logException('Failed to snapshot!')
2507 try:
2508 self.sr._handle_interrupted_clone(
2509 active_uuid, clone_info, force_undo=True
2510 )
2511 self.sr._journaler.remove(LinstorJournaler.CLONE, active_uuid)
2512 except Exception as clean_error:
2513 util.SMlog(
2514 'WARNING: Failed to clean up failed snapshot: {}'
2515 .format(clean_error)
2516 )
2517 raise xs_errors.XenError('VDIClone', opterr=str(e))
2519 self.sr._journaler.remove(LinstorJournaler.CLONE, active_uuid)
2521 return ret_vdi.get_params()
2523 @staticmethod
2524 def _start_persistent_http_server(volume_name):
2525 pid_path = None
2526 http_server = None
2528 try:
2529 if volume_name == HA_VOLUME_NAME:
2530 port = '8076'
2531 else:
2532 port = '8077'
2534 try:
2535 # Use a timeout call because XAPI may be unusable on startup
2536 # or if the host has been ejected. So in this case the call can
2537 # block indefinitely.
2538 session = util.timeout_call(5, util.get_localAPI_session)
2539 host_ip = util.get_this_host_address(session)
2540 except:
2541 # Fallback using the XHA file if session not available.
2542 host_ip, _ = get_ips_from_xha_config_file()
2543 if not host_ip:
2544 raise Exception(
2545 'Cannot start persistent HTTP server: no XAPI session, nor XHA config file'
2546 )
2548 arguments = [
2549 'http-disk-server',
2550 '--disk',
2551 '/dev/drbd/by-res/{}/0'.format(volume_name),
2552 '--ip',
2553 host_ip,
2554 '--port',
2555 port
2556 ]
2558 util.SMlog('Starting {} on port {}...'.format(arguments[0], port))
2559 http_server = subprocess.Popen(
2560 [FORK_LOG_DAEMON] + arguments,
2561 stdout=subprocess.PIPE,
2562 stderr=subprocess.STDOUT,
2563 # Ensure we use another group id to kill this process without
2564 # touch the current one.
2565 preexec_fn=os.setsid
2566 )
2568 pid_path = '/run/http-server-{}.pid'.format(volume_name)
2569 with open(pid_path, 'w') as pid_file:
2570 pid_file.write(str(http_server.pid))
2572 reg_server_ready = re.compile("Server ready!$")
2573 def is_ready():
2574 while http_server.poll() is None:
2575 line = http_server.stdout.readline()
2576 if reg_server_ready.search(line):
2577 return True
2578 return False
2579 try:
2580 if not util.timeout_call(10, is_ready):
2581 raise Exception('Failed to wait HTTP server startup, bad output')
2582 except util.TimeoutException:
2583 raise Exception('Failed to wait for HTTP server startup during given delay')
2584 except Exception as e:
2585 if pid_path:
2586 try:
2587 os.remove(pid_path)
2588 except Exception:
2589 pass
2591 if http_server:
2592 # Kill process and children in this case...
2593 try:
2594 os.killpg(os.getpgid(http_server.pid), signal.SIGTERM)
2595 except:
2596 pass
2598 raise xs_errors.XenError(
2599 'VDIUnavailable',
2600 opterr='Failed to start http-server: {}'.format(e)
2601 )
2603 def _start_persistent_nbd_server(self, volume_name):
2604 pid_path = None
2605 nbd_path = None
2606 nbd_server = None
2608 try:
2609 # We use a precomputed device size.
2610 # So if the XAPI is modified, we must update these values!
2611 if volume_name == HA_VOLUME_NAME:
2612 # See: https://github.com/xapi-project/xen-api/blob/703479fa448a8d7141954bb6e8964d8e25c4ac2e/ocaml/xapi/xha_statefile.ml#L32-L37
2613 port = '8076'
2614 device_size = 4 * 1024 * 1024
2615 else:
2616 # See: https://github.com/xapi-project/xen-api/blob/703479fa448a8d7141954bb6e8964d8e25c4ac2e/ocaml/database/redo_log.ml#L41-L44
2617 port = '8077'
2618 device_size = 256 * 1024 * 1024
2620 try:
2621 session = util.timeout_call(5, util.get_localAPI_session)
2622 ips = util.get_host_addresses(session)
2623 except Exception as e:
2624 _, ips = get_ips_from_xha_config_file()
2625 if not ips:
2626 raise Exception(
2627 'Cannot start persistent NBD server: no XAPI session, nor XHA config file ({})'.format(e)
2628 )
2629 ips = ips.values()
2631 arguments = [
2632 'nbd-http-server',
2633 '--socket-path',
2634 '/run/{}.socket'.format(volume_name),
2635 '--nbd-name',
2636 volume_name,
2637 '--urls',
2638 ','.join(['http://' + ip + ':' + port for ip in ips]),
2639 '--device-size',
2640 str(device_size)
2641 ]
2643 util.SMlog('Starting {} using port {}...'.format(arguments[0], port))
2644 nbd_server = subprocess.Popen(
2645 [FORK_LOG_DAEMON] + arguments,
2646 stdout=subprocess.PIPE,
2647 stderr=subprocess.STDOUT,
2648 # Ensure we use another group id to kill this process without
2649 # touch the current one.
2650 preexec_fn=os.setsid
2651 )
2653 pid_path = '/run/nbd-server-{}.pid'.format(volume_name)
2654 with open(pid_path, 'w') as pid_file:
2655 pid_file.write(str(nbd_server.pid))
2657 reg_nbd_path = re.compile("NBD `(/dev/nbd[0-9]+)` is now attached.$")
2658 def get_nbd_path():
2659 while nbd_server.poll() is None:
2660 line = nbd_server.stdout.readline()
2661 match = reg_nbd_path.search(line)
2662 if match:
2663 return match.group(1)
2664 # Use a timeout to never block the smapi if there is a problem.
2665 try:
2666 nbd_path = util.timeout_call(10, get_nbd_path)
2667 if nbd_path is None:
2668 raise Exception('Empty NBD path (NBD server is probably dead)')
2669 except util.TimeoutException:
2670 raise Exception('Unable to read NBD path')
2672 util.SMlog('Create symlink: {} -> {}'.format(self.path, nbd_path))
2673 os.symlink(nbd_path, self.path)
2674 except Exception as e:
2675 if pid_path:
2676 try:
2677 os.remove(pid_path)
2678 except Exception:
2679 pass
2681 if nbd_path:
2682 try:
2683 os.remove(nbd_path)
2684 except Exception:
2685 pass
2687 if nbd_server:
2688 # Kill process and children in this case...
2689 try:
2690 os.killpg(os.getpgid(nbd_server.pid), signal.SIGTERM)
2691 except:
2692 pass
2694 raise xs_errors.XenError(
2695 'VDIUnavailable',
2696 opterr='Failed to start nbd-server: {}'.format(e)
2697 )
2699 @classmethod
2700 def _kill_persistent_server(self, type, volume_name, sig):
2701 try:
2702 path = '/run/{}-server-{}.pid'.format(type, volume_name)
2703 if not os.path.exists(path):
2704 return
2706 pid = None
2707 with open(path, 'r') as pid_file:
2708 try:
2709 pid = int(pid_file.read())
2710 except Exception:
2711 pass
2713 if pid is not None and util.check_pid_exists(pid):
2714 util.SMlog('Kill {} server {} (pid={})'.format(type, path, pid))
2715 try:
2716 os.killpg(os.getpgid(pid), sig)
2717 except Exception as e:
2718 util.SMlog('Failed to kill {} server: {}'.format(type, e))
2720 os.remove(path)
2721 except:
2722 pass
2724 @classmethod
2725 def _kill_persistent_http_server(self, volume_name, sig=signal.SIGTERM):
2726 return self._kill_persistent_server('nbd', volume_name, sig)
2728 @classmethod
2729 def _kill_persistent_nbd_server(self, volume_name, sig=signal.SIGTERM):
2730 return self._kill_persistent_server('http', volume_name, sig)
2732 def _check_http_nbd_volume_name(self):
2733 volume_name = self.path[14:]
2734 if volume_name not in [
2735 HA_VOLUME_NAME, REDO_LOG_VOLUME_NAME
2736 ]:
2737 raise xs_errors.XenError(
2738 'VDIUnavailable',
2739 opterr='Unsupported path: {}'.format(self.path)
2740 )
2741 return volume_name
2743 def _attach_using_http_nbd(self):
2744 volume_name = self._check_http_nbd_volume_name()
2746 # Ensure there is no NBD and HTTP server running.
2747 self._kill_persistent_nbd_server(volume_name)
2748 self._kill_persistent_http_server(volume_name)
2750 # 0. Fetch drbd path.
2751 must_get_device_path = True
2752 if not self.sr.is_master():
2753 # We are on a slave, we must try to find a diskful locally.
2754 try:
2755 volume_info = self._linstor.get_volume_info(self.uuid)
2756 except Exception as e:
2757 raise xs_errors.XenError(
2758 'VDIUnavailable',
2759 opterr='Cannot get volume info of {}: {}'
2760 .format(self.uuid, e)
2761 )
2763 hostname = socket.gethostname()
2764 must_get_device_path = hostname in volume_info.diskful
2766 drbd_path = None
2767 if must_get_device_path or self.sr.is_master():
2768 # If we are master, we must ensure we have a diskless
2769 # or diskful available to init HA.
2770 # It also avoid this error in xensource.log
2771 # (/usr/libexec/xapi/cluster-stack/xhad/ha_set_pool_state):
2772 # init exited with code 8 [stdout = ''; stderr = 'SF: failed to write in State-File \x10 (fd 4208696). (sys 28)\x0A']
2773 # init returned MTC_EXIT_CAN_NOT_ACCESS_STATEFILE (State-File is inaccessible)
2774 available = False
2775 try:
2776 drbd_path = self._linstor.get_device_path(self.uuid)
2777 available = util.pathexists(drbd_path)
2778 except Exception:
2779 pass
2781 if not available:
2782 raise xs_errors.XenError(
2783 'VDIUnavailable',
2784 opterr='Cannot get device path of {}'.format(self.uuid)
2785 )
2787 # 1. Prepare http-nbd folder.
2788 try:
2789 if not os.path.exists('/dev/http-nbd/'):
2790 os.makedirs('/dev/http-nbd/')
2791 elif os.path.islink(self.path):
2792 os.remove(self.path)
2793 except OSError as e:
2794 if e.errno != errno.EEXIST:
2795 raise xs_errors.XenError(
2796 'VDIUnavailable',
2797 opterr='Cannot prepare http-nbd: {}'.format(e)
2798 )
2800 # 2. Start HTTP service if we have a diskful or if we are master.
2801 http_service = None
2802 if drbd_path:
2803 assert(drbd_path in (
2804 '/dev/drbd/by-res/{}/0'.format(HA_VOLUME_NAME),
2805 '/dev/drbd/by-res/{}/0'.format(REDO_LOG_VOLUME_NAME)
2806 ))
2807 self._start_persistent_http_server(volume_name)
2809 # 3. Start NBD server in all cases.
2810 try:
2811 self._start_persistent_nbd_server(volume_name)
2812 except Exception as e:
2813 if drbd_path:
2814 self._kill_persistent_http_server(volume_name)
2815 raise
2817 self.attached = True
2818 return VDI.VDI.attach(self, self.sr.uuid, self.uuid)
2820 def _detach_using_http_nbd(self):
2821 volume_name = self._check_http_nbd_volume_name()
2822 self._kill_persistent_nbd_server(volume_name)
2823 self._kill_persistent_http_server(volume_name)
2825# ------------------------------------------------------------------------------
2828if __name__ == '__main__': 2828 ↛ 2829line 2828 didn't jump to line 2829, because the condition on line 2828 was never true
2829 def run():
2830 SRCommand.run(LinstorSR, DRIVER_INFO)
2832 if not TRACE_PERFS:
2833 run()
2834 else:
2835 util.make_profile('LinstorSR', run)
2836else:
2837 SR.registerSR(LinstorSR)