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
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._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']
370 self._vdi_shared_time = 0
372 self._init_status = self.INIT_STATUS_NOT_SET
374 self._vdis_loaded = False
375 self._all_volume_info_cache = None
376 self._all_volume_metadata_cache = None
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)
383 def load(self, *args, **kwargs):
384 # Activate all LVMs to make drbd-reactor happy.
385 if self.srcmd.cmd in ('sr_attach', 'vdi_attach_from_config'):
386 activate_lvm_group(self._group_name)
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)
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
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 )
430 self._journaler = LinstorJournaler(
431 controller_uri, self._group_name, logger=util.SMlog
432 )
434 if self.srcmd.cmd is None:
435 # Only useful on on-slave plugin (is_open).
436 self._vhdutil = LinstorVhdUtil(self.session, self._linstor)
438 return wrapped_method(self, *args, **kwargs)
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')
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()
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))
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))
470 if hosts:
471 util.SMlog('Failed to join node(s): {}'.format(hosts))
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 )
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 )
505 if load_vdis:
506 self._load_vdis()
508 self._linstor.remove_resourceless_volumes()
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())
521 return wrapped_method(self, *args, **kwargs)
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
542 return wrap
544 def cleanup(self):
545 if self._vdi_shared_time:
546 self._shared_lock_vdi(self.srcmd.params['vdi_uuid'], locked=False)
548 @_locked_load
549 def create(self, uuid, size):
550 util.SMlog('LinstorSR.create for {}'.format(self.uuid))
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 )
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])
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 )
577 if srs:
578 raise xs_errors.XenError(
579 'LinstorSRCreate',
580 opterr='LINSTOR SR must be unique in a pool'
581 )
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 )
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']
596 if len(ips) != len(online_hosts):
597 raise xs_errors.XenError(
598 'LinstorSRCreate',
599 opterr='Multiple hosts with same hostname'
600 )
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)
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))
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
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)
643 if self.vdis or self._linstor._volumes:
644 raise xs_errors.XenError('SRNotEmpty')
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 )
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
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 )
671 try:
672 self._update_drbd_reactor_on_all_hosts(
673 controller_node_name=node_name, enabled=False
674 )
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 )
698 Lock.cleanupAll(self.uuid)
700 @_locked_load
701 def update(self, uuid):
702 util.SMlog('LinstorSR.update for {}'.format(self.uuid))
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 )
711 self._update_stats(0)
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 }
724 @_locked_load
725 def attach(self, uuid):
726 util.SMlog('LinstorSR.attach for {}'.format(self.uuid))
728 if not self._linstor:
729 raise xs_errors.XenError(
730 'SRUnavailable',
731 opterr='no such group: {}'.format(self._group_name)
732 )
734 @_locked_load
735 def detach(self, uuid):
736 util.SMlog('LinstorSR.detach for {}'.format(self.uuid))
737 cleanup.abort(self.uuid)
739 @_locked_load
740 def probe(self):
741 util.SMlog('LinstorSR.probe for {}'.format(self.uuid))
742 # TODO
744 @_locked_load
745 def scan(self, uuid):
746 if self._init_status == self.INIT_STATUS_FAIL:
747 return
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 )
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()
761 for vdi_uuid in list(self.vdis.keys()):
762 if self.vdis[vdi_uuid].deleted:
763 del self.vdis[vdi_uuid]
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 )
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()
785 @_locked_load
786 def vdi(self, uuid):
787 return LinstorVDI(self, uuid)
789 _locked_load = staticmethod(_locked_load)
791 # --------------------------------------------------------------------------
792 # Lock.
793 # --------------------------------------------------------------------------
795 def _shared_lock_vdi(self, vdi_uuid, locked=True):
796 master = util.get_master_ref(self.session)
798 command = 'lockVdi'
799 args = {
800 'groupName': self._group_name,
801 'srUuid': self.uuid,
802 'vdiUuid': vdi_uuid,
803 'locked': str(locked)
804 }
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
823 self._exec_manager_command(master, command, args, 'VDIUnavailable')
825 # --------------------------------------------------------------------------
826 # Network.
827 # --------------------------------------------------------------------------
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']
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
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 )
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 )
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)
868 for slave in util.get_all_slaves(self.session):
869 self._prepare_sr(slave, group_name, enabled)
871 def _update_drbd_reactor(self, host, enabled):
872 self._exec_manager_command(
873 host,
874 'updateDrbdReactor',
875 {'enabled': str(enabled)},
876 'SRUnavailable'
877 )
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'
889 controller_host = None
890 secondary_hosts = []
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))
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 ))
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)
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)
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)
929 # --------------------------------------------------------------------------
930 # Metadata.
931 # --------------------------------------------------------------------------
933 def _synchronize_metadata_and_xapi(self):
934 try:
935 # First synch SR parameters.
936 self.update(self.uuid)
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
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 )
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 )
969 def _synchronize_metadata(self):
970 if not self._is_master:
971 return
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))
983 # --------------------------------------------------------------------------
984 # Stats.
985 # --------------------------------------------------------------------------
987 def _update_stats(self, virt_alloc_delta):
988 valloc = int(self.session.xenapi.SR.get_virtual_allocation(
989 self.sr_ref
990 ))
992 # Update size attributes of the SR parent class.
993 self.virtual_allocation = valloc + virt_alloc_delta
995 self._update_physical_size()
997 # Notify SR parent class.
998 self._db_update()
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
1007 self.physical_utilisation = self._linstor.allocated_volume_size
1009 # --------------------------------------------------------------------------
1010 # VDIs.
1011 # --------------------------------------------------------------------------
1013 def _load_vdis(self):
1014 if self._vdis_loaded:
1015 return
1017 assert self._is_master
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()
1026 # We must mark VDIs as loaded only if the load is a success.
1027 self._vdis_loaded = True
1029 self._undo_all_journal_transactions()
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))
1038 # 2. Get volumes info.
1039 all_volume_info = self._all_volume_info_cache
1040 volumes_metadata = self._all_volume_metadata_cache
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)
1050 introduce = False
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())
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
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
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
1077 if vdi_uuid.startswith('DELETED_'):
1078 continue
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
1088 util.SMlog(
1089 'Trying to introduce VDI {} as it is present in '
1090 'LINSTOR and not in XAPI...'
1091 .format(vdi_uuid)
1092 )
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
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)
1107 if not vdi_type:
1108 util.SMlog(
1109 'Cannot introduce {} '.format(vdi_uuid) +
1110 'without vdi_type'
1111 )
1112 continue
1114 sm_config = {
1115 'vdi_type': vdi_type
1116 }
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
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 )
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 )
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 )
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]
1175 # 4.b. Add the VDI in the list.
1176 vdi = self.vdi(vdi_uuid)
1177 self.vdis[vdi_uuid] = vdi
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
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)
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
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))
1213 # TODO: Check correctly how to use CBT.
1214 # Update cbt_enabled on the right VDI, check LVM/FileSR code.
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
1225 # 7. Update virtual allocation, build geneology and remove useless VDIs
1226 self.virtual_allocation = 0
1228 # 8. Build geneology.
1229 geneology = {}
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
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]
1251 # --------------------------------------------------------------------------
1252 # Journals.
1253 # --------------------------------------------------------------------------
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)
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)
1267 # Otherwise it's a VHD and a parent can exist.
1268 if not self._vhdutil.check(vdi_uuid):
1269 return (None, None)
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)
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()
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)
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)
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 )
1309 vdi = self.vdis.get(vdi_uuid)
1310 if not vdi:
1311 util.SMlog('Cannot deflate missing VDI {}'.format(vdi_uuid))
1312 return
1314 assert not self._all_volume_info_cache
1315 volume_info = self._linstor.get_volume_info(vdi_uuid)
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)
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 )
1329 base_uuid, snap_uuid = clone_info.split('_')
1331 # Use LINSTOR data because new VDIs may not be in the XAPI.
1332 volume_names = self._linstor.get_volumes_with_name()
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 )
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
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
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 )
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
1370 util.SMlog('Leaves valid but => revert')
1371 self._undo_clone(volume_names, vdi_uuid, base_uuid, snap_uuid)
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]
1378 if not util.pathexists(base_path):
1379 util.SMlog('Base not found! Exit...')
1380 util.SMlog('*** INTERRUPTED CLONE OP: rollback fail')
1381 return
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 )
1395 # Remove the child nodes.
1396 if snap_uuid and snap_uuid in volume_names:
1397 util.SMlog('Destroying snap {}...'.format(snap_uuid))
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 )
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 )
1425 # Rename!
1426 self._linstor.update_volume_uuid(base_uuid, vdi_uuid)
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
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')
1443 util.SMlog('*** INTERRUPTED CLONE OP: rollback success')
1445 # --------------------------------------------------------------------------
1446 # Cache.
1447 # --------------------------------------------------------------------------
1449 def _create_linstor_cache(self):
1450 reconnect = False
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
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 )
1470 def _destroy_linstor_cache(self):
1471 self._all_volume_info_cache = None
1472 self._all_volume_metadata_cache = None
1474 # --------------------------------------------------------------------------
1475 # Misc.
1476 # --------------------------------------------------------------------------
1478 def _reconnect(self):
1479 controller_uri = get_controller_uri()
1481 self._journaler = LinstorJournaler(
1482 controller_uri, self._group_name, logger=util.SMlog
1483 )
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)
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')
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
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()
1533 util.SMlog('Kicking GC')
1534 cleanup.gc(self.session, self.uuid, True)
1536# ==============================================================================
1537# LinstorSr VDI
1538# ==============================================================================
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'
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
1552 # --------------------------------------------------------------------------
1553 # VDI methods.
1554 # --------------------------------------------------------------------------
1556 def load(self, vdi_uuid):
1557 self._lock = self.sr.lock
1558 self._exists = True
1559 self._linstor = self.sr._linstor
1561 # Update hidden parent property.
1562 self.hidden = False
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 )
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()
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
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.
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')
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)
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
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')
1637 assert self.uuid
1638 assert self.ty
1639 assert self.vdi_type
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)
1650 # 3. Set sm_config attribute of VDI parent class.
1651 self.sm_config = self.sr.srcmd.params['vdi_sm_config']
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
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)
1668 self._update_device_name(volume_info.name)
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)
1678 if self._key_hash:
1679 self.sr._vhdutil.set_key(self.path, self._key_hash)
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)
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)
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)
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 )
1724 self.utilisation = volume_info.allocated_size
1725 self.sm_config['vdi_type'] = self.vdi_type
1727 self.ref = self._db_introduce()
1728 self.sr._update_stats(self.size)
1730 return VDI.VDI.get_params(self)
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')
1737 if self.deleted:
1738 return super(LinstorVDI, self).delete(
1739 sr_uuid, vdi_uuid, data_only
1740 )
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 )
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()
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 )
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))
1768 return
1770 if self.uuid in self.sr.vdis:
1771 del self.sr.vdis[self.uuid]
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)
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 )
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'
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
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 )
1816 if not hasattr(self, 'xenstore_data'):
1817 self.xenstore_data = {}
1818 self.xenstore_data['storage-type'] = LinstorSR.DRIVER_TYPE
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()
1827 # Ensure we have a path...
1828 self._create_chain_paths(self.uuid)
1830 self.attached = True
1831 return VDI.VDI.attach(self, self.sr.uuid, self.uuid)
1833 def detach(self, sr_uuid, vdi_uuid):
1834 util.SMlog('LinstorVDI.detach for {}'.format(self.uuid))
1835 detach_from_config = self.sr.srcmd.cmd == 'vdi_detach_from_config'
1836 self.attached = False
1838 if detach_from_config and self.path.startswith('/dev/http-nbd/'):
1839 return self._detach_using_http_nbd()
1841 if self.vdi_type == vhdutil.VDI_TYPE_RAW:
1842 return
1844 # The VDI is already deflated if the VHD image size + metadata is
1845 # equal to the LINSTOR volume size.
1846 volume_size = LinstorVhdUtil.compute_volume_size(self.size, self.vdi_type)
1847 already_deflated = self.capacity <= volume_size
1849 if already_deflated:
1850 util.SMlog(
1851 'VDI {} already deflated (old volume size={}, volume size={})'
1852 .format(self.uuid, self.capacity, volume_size)
1853 )
1855 need_deflate = True
1856 if already_deflated:
1857 need_deflate = False
1858 elif self.sr._provisioning == 'thick':
1859 need_deflate = False
1861 vdi_ref = self.sr.srcmd.params['vdi_ref']
1862 if self.session.xenapi.VDI.get_is_a_snapshot(vdi_ref):
1863 need_deflate = True
1865 if need_deflate:
1866 try:
1867 self._prepare_thin(False)
1868 except Exception as e:
1869 raise xs_errors.XenError(
1870 'VDIUnavailable',
1871 opterr='Failed to detach VDI during "prepare thin": {}'
1872 .format(e)
1873 )
1875 # We remove only on slaves because the volume can be used by the GC.
1876 if self.sr._is_master:
1877 return
1879 while vdi_uuid:
1880 try:
1881 path = self._linstor.build_device_path(self._linstor.get_volume_name(vdi_uuid))
1882 parent_vdi_uuid = self.sr._vhdutil.get_vhd_info(vdi_uuid).parentUuid
1883 except Exception:
1884 break
1886 if util.pathexists(path):
1887 try:
1888 self._linstor.remove_volume_if_diskless(vdi_uuid)
1889 except Exception as e:
1890 # Ensure we can always detach properly.
1891 # I don't want to corrupt the XAPI info.
1892 util.SMlog('Failed to clean VDI {} during detach: {}'.format(vdi_uuid, e))
1893 vdi_uuid = parent_vdi_uuid
1895 def resize(self, sr_uuid, vdi_uuid, size):
1896 util.SMlog('LinstorVDI.resize for {}'.format(self.uuid))
1897 if not self.sr._is_master:
1898 raise xs_errors.XenError(
1899 'VDISize',
1900 opterr='resize on slave not allowed'
1901 )
1903 if self.hidden:
1904 raise xs_errors.XenError('VDIUnavailable', opterr='hidden VDI')
1906 # Compute the virtual VHD and DRBD volume size.
1907 size = vhdutil.validate_and_round_vhd_size(int(size))
1908 volume_size = LinstorVhdUtil.compute_volume_size(size, self.vdi_type)
1909 util.SMlog(
1910 'LinstorVDI.resize: type={}, vhd-size={}, volume-size={}'
1911 .format(self.vdi_type, size, volume_size)
1912 )
1914 if size < self.size:
1915 util.SMlog(
1916 'vdi_resize: shrinking not supported: '
1917 '(current size: {}, new size: {})'.format(self.size, size)
1918 )
1919 raise xs_errors.XenError('VDISize', opterr='shrinking not allowed')
1921 if size == self.size:
1922 return VDI.VDI.get_params(self)
1924 if self.vdi_type == vhdutil.VDI_TYPE_RAW:
1925 old_volume_size = self.size
1926 new_volume_size = LinstorVolumeManager.round_up_volume_size(size)
1927 else:
1928 old_volume_size = self.utilisation
1929 if self.sr._provisioning == 'thin':
1930 # VDI is currently deflated, so keep it deflated.
1931 new_volume_size = old_volume_size
1932 else:
1933 new_volume_size = LinstorVhdUtil.compute_volume_size(size, self.vdi_type)
1934 assert new_volume_size >= old_volume_size
1936 space_needed = new_volume_size - old_volume_size
1937 self.sr._ensure_space_available(space_needed)
1939 old_size = self.size
1940 if self.vdi_type == vhdutil.VDI_TYPE_RAW:
1941 self._linstor.resize(self.uuid, new_volume_size)
1942 else:
1943 if new_volume_size != old_volume_size:
1944 self.sr._vhdutil.inflate(
1945 self.sr._journaler, self.uuid, self.path,
1946 new_volume_size, old_volume_size
1947 )
1948 self.sr._vhdutil.set_size_virt_fast(self.path, size)
1950 # Reload size attributes.
1951 self._load_this()
1953 vdi_ref = self.sr.srcmd.params['vdi_ref']
1954 self.session.xenapi.VDI.set_virtual_size(vdi_ref, str(self.size))
1955 self.session.xenapi.VDI.set_physical_utilisation(
1956 vdi_ref, str(self.utilisation)
1957 )
1958 self.sr._update_stats(self.size - old_size)
1959 return VDI.VDI.get_params(self)
1961 def clone(self, sr_uuid, vdi_uuid):
1962 return self._do_snapshot(sr_uuid, vdi_uuid, VDI.SNAPSHOT_DOUBLE)
1964 def compose(self, sr_uuid, vdi1, vdi2):
1965 util.SMlog('VDI.compose for {} -> {}'.format(vdi2, vdi1))
1966 if self.vdi_type != vhdutil.VDI_TYPE_VHD:
1967 raise xs_errors.XenError('Unimplemented')
1969 parent_uuid = vdi1
1970 parent_path = self._linstor.get_device_path(parent_uuid)
1972 # We must pause tapdisk to correctly change the parent. Otherwise we
1973 # have a readonly error.
1974 # See: https://github.com/xapi-project/xen-api/blob/b3169a16d36dae0654881b336801910811a399d9/ocaml/xapi/storage_migrate.ml#L928-L929
1975 # and: https://github.com/xapi-project/xen-api/blob/b3169a16d36dae0654881b336801910811a399d9/ocaml/xapi/storage_migrate.ml#L775
1977 if not blktap2.VDI.tap_pause(self.session, self.sr.uuid, self.uuid):
1978 raise util.SMException('Failed to pause VDI {}'.format(self.uuid))
1979 try:
1980 self.sr._vhdutil.set_parent(self.path, parent_path, False)
1981 self.sr._vhdutil.set_hidden(parent_path)
1982 self.sr.session.xenapi.VDI.set_managed(
1983 self.sr.srcmd.params['args'][0], False
1984 )
1985 finally:
1986 blktap2.VDI.tap_unpause(self.session, self.sr.uuid, self.uuid)
1988 if not blktap2.VDI.tap_refresh(self.session, self.sr.uuid, self.uuid):
1989 raise util.SMException(
1990 'Failed to refresh VDI {}'.format(self.uuid)
1991 )
1993 util.SMlog('Compose done')
1995 def generate_config(self, sr_uuid, vdi_uuid):
1996 """
1997 Generate the XML config required to attach and activate
1998 a VDI for use when XAPI is not running. Attach and
1999 activation is handled by vdi_attach_from_config below.
2000 """
2002 util.SMlog('LinstorVDI.generate_config for {}'.format(self.uuid))
2004 resp = {}
2005 resp['device_config'] = self.sr.dconf
2006 resp['sr_uuid'] = sr_uuid
2007 resp['vdi_uuid'] = self.uuid
2008 resp['sr_sm_config'] = self.sr.sm_config
2009 resp['command'] = 'vdi_attach_from_config'
2011 # By default, we generate a normal config.
2012 # But if the disk is persistent, we must use a HTTP/NBD
2013 # server to ensure we can always write or read data.
2014 # Why? DRBD is unsafe when used with more than 4 hosts:
2015 # We are limited to use 1 diskless and 3 full.
2016 # We can't increase this limitation, so we use a NBD/HTTP device
2017 # instead.
2018 volume_name = self._linstor.get_volume_name(self.uuid)
2019 if not USE_HTTP_NBD_SERVERS or volume_name not in [
2020 HA_VOLUME_NAME, REDO_LOG_VOLUME_NAME
2021 ]:
2022 if not self.path or not util.pathexists(self.path):
2023 available = False
2024 # Try to refresh symlink path...
2025 try:
2026 self.path = self._linstor.get_device_path(vdi_uuid)
2027 available = util.pathexists(self.path)
2028 except Exception:
2029 pass
2030 if not available:
2031 raise xs_errors.XenError('VDIUnavailable')
2033 resp['vdi_path'] = self.path
2034 else:
2035 # Axiom: DRBD device is present on at least one host.
2036 resp['vdi_path'] = '/dev/http-nbd/' + volume_name
2038 config = xmlrpc.client.dumps(tuple([resp]), 'vdi_attach_from_config')
2039 return xmlrpc.client.dumps((config,), "", True)
2041 def attach_from_config(self, sr_uuid, vdi_uuid):
2042 """
2043 Attach and activate a VDI using config generated by
2044 vdi_generate_config above. This is used for cases such as
2045 the HA state-file and the redo-log.
2046 """
2048 util.SMlog('LinstorVDI.attach_from_config for {}'.format(vdi_uuid))
2050 try:
2051 if not util.pathexists(self.sr.path):
2052 self.sr.attach(sr_uuid)
2054 if not DRIVER_CONFIG['ATTACH_FROM_CONFIG_WITH_TAPDISK']:
2055 return self.attach(sr_uuid, vdi_uuid)
2056 except Exception:
2057 util.logException('LinstorVDI.attach_from_config')
2058 raise xs_errors.XenError(
2059 'SRUnavailable',
2060 opterr='Unable to attach from config'
2061 )
2063 def reset_leaf(self, sr_uuid, vdi_uuid):
2064 if self.vdi_type != vhdutil.VDI_TYPE_VHD:
2065 raise xs_errors.XenError('Unimplemented')
2067 if not self.sr._vhdutil.has_parent(self.uuid):
2068 raise util.SMException(
2069 'ERROR: VDI {} has no parent, will not reset contents'
2070 .format(self.uuid)
2071 )
2073 self.sr._vhdutil.kill_data(self.path)
2075 def _load_this(self):
2076 volume_metadata = None
2077 if self.sr._all_volume_metadata_cache:
2078 volume_metadata = self.sr._all_volume_metadata_cache.get(self.uuid)
2079 if volume_metadata is None:
2080 volume_metadata = self._linstor.get_volume_metadata(self.uuid)
2082 volume_info = None
2083 if self.sr._all_volume_info_cache:
2084 volume_info = self.sr._all_volume_info_cache.get(self.uuid)
2085 if volume_info is None:
2086 volume_info = self._linstor.get_volume_info(self.uuid)
2088 # Contains the max physical size used on a disk.
2089 # When LINSTOR LVM driver is used, the size should be similar to
2090 # virtual size (i.e. the LINSTOR max volume size).
2091 # When LINSTOR Thin LVM driver is used, the used physical size should
2092 # be lower than virtual size at creation.
2093 # The physical size increases after each write in a new block.
2094 self.utilisation = volume_info.allocated_size
2095 self.capacity = volume_info.virtual_size
2097 if self.vdi_type == vhdutil.VDI_TYPE_RAW:
2098 self.hidden = int(volume_metadata.get(HIDDEN_TAG) or 0)
2099 self.size = volume_info.virtual_size
2100 self.parent = ''
2101 else:
2102 vhd_info = self.sr._vhdutil.get_vhd_info(self.uuid)
2103 self.hidden = vhd_info.hidden
2104 self.size = vhd_info.sizeVirt
2105 self.parent = vhd_info.parentUuid
2107 if self.hidden:
2108 self.managed = False
2110 self.label = volume_metadata.get(NAME_LABEL_TAG) or ''
2111 self.description = volume_metadata.get(NAME_DESCRIPTION_TAG) or ''
2113 # Update sm_config_override of VDI parent class.
2114 self.sm_config_override = {'vhd-parent': self.parent or None}
2116 def _mark_hidden(self, hidden=True):
2117 if self.hidden == hidden:
2118 return
2120 if self.vdi_type == vhdutil.VDI_TYPE_VHD:
2121 self.sr._vhdutil.set_hidden(self.path, hidden)
2122 else:
2123 self._linstor.update_volume_metadata(self.uuid, {
2124 HIDDEN_TAG: hidden
2125 })
2126 self.hidden = hidden
2128 def update(self, sr_uuid, vdi_uuid):
2129 xenapi = self.session.xenapi
2130 vdi_ref = xenapi.VDI.get_by_uuid(self.uuid)
2132 volume_metadata = {
2133 NAME_LABEL_TAG: util.to_plain_string(
2134 xenapi.VDI.get_name_label(vdi_ref)
2135 ),
2136 NAME_DESCRIPTION_TAG: util.to_plain_string(
2137 xenapi.VDI.get_name_description(vdi_ref)
2138 )
2139 }
2141 try:
2142 self._linstor.update_volume_metadata(self.uuid, volume_metadata)
2143 except LinstorVolumeManagerError as e:
2144 if e.code == LinstorVolumeManagerError.ERR_VOLUME_NOT_EXISTS:
2145 raise xs_errors.XenError(
2146 'VDIUnavailable',
2147 opterr='LINSTOR volume {} not found'.format(self.uuid)
2148 )
2149 raise xs_errors.XenError('VDIUnavailable', opterr=str(e))
2151 # --------------------------------------------------------------------------
2152 # Thin provisioning.
2153 # --------------------------------------------------------------------------
2155 def _prepare_thin(self, attach):
2156 if self.sr._is_master:
2157 if attach:
2158 attach_thin(
2159 self.session, self.sr._journaler, self._linstor,
2160 self.sr.uuid, self.uuid
2161 )
2162 else:
2163 detach_thin(
2164 self.session, self._linstor, self.sr.uuid, self.uuid
2165 )
2166 else:
2167 fn = 'attach' if attach else 'detach'
2169 master = util.get_master_ref(self.session)
2171 args = {
2172 'groupName': self.sr._group_name,
2173 'srUuid': self.sr.uuid,
2174 'vdiUuid': self.uuid
2175 }
2177 try:
2178 self.sr._exec_manager_command(master, fn, args, 'VDIUnavailable')
2179 except Exception:
2180 if fn != 'detach':
2181 raise
2183 # Reload size attrs after inflate or deflate!
2184 self._load_this()
2185 self.sr._update_physical_size()
2187 vdi_ref = self.sr.srcmd.params['vdi_ref']
2188 self.session.xenapi.VDI.set_physical_utilisation(
2189 vdi_ref, str(self.utilisation)
2190 )
2192 self.session.xenapi.SR.set_physical_utilisation(
2193 self.sr.sr_ref, str(self.sr.physical_utilisation)
2194 )
2196 # --------------------------------------------------------------------------
2197 # Generic helpers.
2198 # --------------------------------------------------------------------------
2200 def _determine_type_and_path(self):
2201 """
2202 Determine whether this is a RAW or a VHD VDI.
2203 """
2205 # 1. Check vdi_ref and vdi_type in config.
2206 try:
2207 vdi_ref = self.session.xenapi.VDI.get_by_uuid(self.uuid)
2208 if vdi_ref:
2209 sm_config = self.session.xenapi.VDI.get_sm_config(vdi_ref)
2210 vdi_type = sm_config.get('vdi_type')
2211 if vdi_type:
2212 # Update parent fields.
2213 self.vdi_type = vdi_type
2214 self.sm_config_override = sm_config
2215 self._update_device_name(
2216 self._linstor.get_volume_name(self.uuid)
2217 )
2218 return
2219 except Exception:
2220 pass
2222 # 2. Otherwise use the LINSTOR volume manager directly.
2223 # It's probably a new VDI created via snapshot.
2224 volume_metadata = self._linstor.get_volume_metadata(self.uuid)
2225 self.vdi_type = volume_metadata.get(VDI_TYPE_TAG)
2226 if not self.vdi_type:
2227 raise xs_errors.XenError(
2228 'VDIUnavailable',
2229 opterr='failed to get vdi_type in metadata'
2230 )
2231 self._update_device_name(self._linstor.get_volume_name(self.uuid))
2233 def _update_device_name(self, device_name):
2234 self._device_name = device_name
2236 # Mark path of VDI parent class.
2237 if device_name:
2238 self.path = self._linstor.build_device_path(self._device_name)
2239 else:
2240 self.path = None
2242 def _create_snapshot(self, snap_uuid, snap_of_uuid=None):
2243 """
2244 Snapshot self and return the snapshot VDI object.
2245 """
2247 # 1. Create a new LINSTOR volume with the same size than self.
2248 snap_path = self._linstor.shallow_clone_volume(
2249 self.uuid, snap_uuid, persistent=False
2250 )
2252 # 2. Write the snapshot content.
2253 is_raw = (self.vdi_type == vhdutil.VDI_TYPE_RAW)
2254 self.sr._vhdutil.snapshot(
2255 snap_path, self.path, is_raw, self.MAX_METADATA_VIRT_SIZE
2256 )
2258 # 3. Get snapshot parent.
2259 snap_parent = self.sr._vhdutil.get_parent(snap_uuid)
2261 # 4. Update metadata.
2262 util.SMlog('Set VDI {} metadata of snapshot'.format(snap_uuid))
2263 volume_metadata = {
2264 NAME_LABEL_TAG: util.to_plain_string(self.label),
2265 NAME_DESCRIPTION_TAG: util.to_plain_string(self.description),
2266 IS_A_SNAPSHOT_TAG: bool(snap_of_uuid),
2267 SNAPSHOT_OF_TAG: snap_of_uuid,
2268 SNAPSHOT_TIME_TAG: '',
2269 TYPE_TAG: self.ty,
2270 VDI_TYPE_TAG: vhdutil.VDI_TYPE_VHD,
2271 READ_ONLY_TAG: False,
2272 METADATA_OF_POOL_TAG: ''
2273 }
2274 self._linstor.set_volume_metadata(snap_uuid, volume_metadata)
2276 # 5. Set size.
2277 snap_vdi = LinstorVDI(self.sr, snap_uuid)
2278 if not snap_vdi._exists:
2279 raise xs_errors.XenError('VDISnapshot')
2281 volume_info = self._linstor.get_volume_info(snap_uuid)
2283 snap_vdi.size = self.sr._vhdutil.get_size_virt(snap_uuid)
2284 snap_vdi.utilisation = volume_info.allocated_size
2286 # 6. Update sm config.
2287 snap_vdi.sm_config = {}
2288 snap_vdi.sm_config['vdi_type'] = snap_vdi.vdi_type
2289 if snap_parent:
2290 snap_vdi.sm_config['vhd-parent'] = snap_parent
2291 snap_vdi.parent = snap_parent
2293 snap_vdi.label = self.label
2294 snap_vdi.description = self.description
2296 self._linstor.mark_volume_as_persistent(snap_uuid)
2298 return snap_vdi
2300 # --------------------------------------------------------------------------
2301 # Implement specific SR methods.
2302 # --------------------------------------------------------------------------
2304 def _rename(self, oldpath, newpath):
2305 # TODO: I'm not sure... Used by CBT.
2306 volume_uuid = self._linstor.get_volume_uuid_from_device_path(oldpath)
2307 self._linstor.update_volume_name(volume_uuid, newpath)
2309 def _do_snapshot(
2310 self, sr_uuid, vdi_uuid, snap_type, secondary=None, cbtlog=None
2311 ):
2312 # If cbt enabled, save file consistency state.
2313 if cbtlog is not None:
2314 if blktap2.VDI.tap_status(self.session, vdi_uuid):
2315 consistency_state = False
2316 else:
2317 consistency_state = True
2318 util.SMlog(
2319 'Saving log consistency state of {} for vdi: {}'
2320 .format(consistency_state, vdi_uuid)
2321 )
2322 else:
2323 consistency_state = None
2325 if self.vdi_type != vhdutil.VDI_TYPE_VHD:
2326 raise xs_errors.XenError('Unimplemented')
2328 if not blktap2.VDI.tap_pause(self.session, sr_uuid, vdi_uuid):
2329 raise util.SMException('Failed to pause VDI {}'.format(vdi_uuid))
2330 try:
2331 return self._snapshot(snap_type, cbtlog, consistency_state)
2332 finally:
2333 blktap2.VDI.tap_unpause(self.session, sr_uuid, vdi_uuid, secondary)
2335 def _snapshot(self, snap_type, cbtlog=None, cbt_consistency=None):
2336 util.SMlog(
2337 'LinstorVDI._snapshot for {} (type {})'
2338 .format(self.uuid, snap_type)
2339 )
2341 # 1. Checks...
2342 if self.hidden:
2343 raise xs_errors.XenError('VDIClone', opterr='hidden VDI')
2345 depth = self.sr._vhdutil.get_depth(self.uuid)
2346 if depth == -1:
2347 raise xs_errors.XenError(
2348 'VDIUnavailable',
2349 opterr='failed to get VHD depth'
2350 )
2351 elif depth >= vhdutil.MAX_CHAIN_SIZE:
2352 raise xs_errors.XenError('SnapshotChainTooLong')
2354 # Ensure we have a valid path if we don't have a local diskful.
2355 self._create_chain_paths(self.uuid)
2357 volume_path = self.path
2358 if not util.pathexists(volume_path):
2359 raise xs_errors.XenError(
2360 'EIO',
2361 opterr='IO error checking path {}'.format(volume_path)
2362 )
2364 # 2. Create base and snap uuid (if required) and a journal entry.
2365 base_uuid = util.gen_uuid()
2366 snap_uuid = None
2368 if snap_type == VDI.SNAPSHOT_DOUBLE:
2369 snap_uuid = util.gen_uuid()
2371 clone_info = '{}_{}'.format(base_uuid, snap_uuid)
2373 active_uuid = self.uuid
2374 self.sr._journaler.create(
2375 LinstorJournaler.CLONE, active_uuid, clone_info
2376 )
2378 try:
2379 # 3. Self becomes the new base.
2380 # The device path remains the same.
2381 self._linstor.update_volume_uuid(self.uuid, base_uuid)
2382 self.uuid = base_uuid
2383 self.location = self.uuid
2384 self.read_only = True
2385 self.managed = False
2387 # 4. Create snapshots (new active and snap).
2388 active_vdi = self._create_snapshot(active_uuid)
2390 snap_vdi = None
2391 if snap_type == VDI.SNAPSHOT_DOUBLE:
2392 snap_vdi = self._create_snapshot(snap_uuid, active_uuid)
2394 self.label = 'base copy'
2395 self.description = ''
2397 # 5. Mark the base VDI as hidden so that it does not show up
2398 # in subsequent scans.
2399 self._mark_hidden()
2400 self._linstor.update_volume_metadata(
2401 self.uuid, {READ_ONLY_TAG: True}
2402 )
2404 # 6. We must update the new active VDI with the "paused" and
2405 # "host_" properties. Why? Because the original VDI has been
2406 # paused and we we must unpause it after the snapshot.
2407 # See: `tap_unpause` in `blktap2.py`.
2408 vdi_ref = self.session.xenapi.VDI.get_by_uuid(active_uuid)
2409 sm_config = self.session.xenapi.VDI.get_sm_config(vdi_ref)
2410 for key in [x for x in sm_config.keys() if x == 'paused' or x.startswith('host_')]:
2411 active_vdi.sm_config[key] = sm_config[key]
2413 # 7. Verify parent locator field of both children and
2414 # delete base if unused.
2415 introduce_parent = True
2416 try:
2417 snap_parent = None
2418 if snap_vdi:
2419 snap_parent = snap_vdi.parent
2421 if active_vdi.parent != self.uuid and (
2422 snap_type == VDI.SNAPSHOT_SINGLE or
2423 snap_type == VDI.SNAPSHOT_INTERNAL or
2424 snap_parent != self.uuid
2425 ):
2426 util.SMlog(
2427 'Destroy unused base volume: {} (path={})'
2428 .format(self.uuid, self.path)
2429 )
2430 introduce_parent = False
2431 self._linstor.destroy_volume(self.uuid)
2432 except Exception as e:
2433 util.SMlog('Ignoring exception: {}'.format(e))
2434 pass
2436 # 8. Introduce the new VDI records.
2437 if snap_vdi:
2438 # If the parent is encrypted set the key_hash for the
2439 # new snapshot disk.
2440 vdi_ref = self.sr.srcmd.params['vdi_ref']
2441 sm_config = self.session.xenapi.VDI.get_sm_config(vdi_ref)
2442 # TODO: Maybe remove key_hash support.
2443 if 'key_hash' in sm_config:
2444 snap_vdi.sm_config['key_hash'] = sm_config['key_hash']
2445 # If we have CBT enabled on the VDI,
2446 # set CBT status for the new snapshot disk.
2447 if cbtlog:
2448 snap_vdi.cbt_enabled = True
2450 if snap_vdi:
2451 snap_vdi_ref = snap_vdi._db_introduce()
2452 util.SMlog(
2453 'vdi_clone: introduced VDI: {} ({})'
2454 .format(snap_vdi_ref, snap_vdi.uuid)
2455 )
2456 if introduce_parent:
2457 base_vdi_ref = self._db_introduce()
2458 self.session.xenapi.VDI.set_managed(base_vdi_ref, False)
2459 util.SMlog(
2460 'vdi_clone: introduced VDI: {} ({})'
2461 .format(base_vdi_ref, self.uuid)
2462 )
2463 self._linstor.update_volume_metadata(self.uuid, {
2464 NAME_LABEL_TAG: util.to_plain_string(self.label),
2465 NAME_DESCRIPTION_TAG: util.to_plain_string(
2466 self.description
2467 ),
2468 READ_ONLY_TAG: True,
2469 METADATA_OF_POOL_TAG: ''
2470 })
2472 # 9. Update cbt files if user created snapshot (SNAPSHOT_DOUBLE)
2473 if snap_type == VDI.SNAPSHOT_DOUBLE and cbtlog:
2474 try:
2475 self._cbt_snapshot(snap_uuid, cbt_consistency)
2476 except Exception:
2477 # CBT operation failed.
2478 # TODO: Implement me.
2479 raise
2481 if snap_type != VDI.SNAPSHOT_INTERNAL:
2482 self.sr._update_stats(self.size)
2484 # 10. Return info on the new user-visible leaf VDI.
2485 ret_vdi = snap_vdi
2486 if not ret_vdi:
2487 ret_vdi = self
2488 if not ret_vdi:
2489 ret_vdi = active_vdi
2491 vdi_ref = self.sr.srcmd.params['vdi_ref']
2492 self.session.xenapi.VDI.set_sm_config(
2493 vdi_ref, active_vdi.sm_config
2494 )
2495 except Exception:
2496 util.logException('Failed to snapshot!')
2497 try:
2498 self.sr._handle_interrupted_clone(
2499 active_uuid, clone_info, force_undo=True
2500 )
2501 self.sr._journaler.remove(LinstorJournaler.CLONE, active_uuid)
2502 except Exception as e:
2503 util.SMlog(
2504 'WARNING: Failed to clean up failed snapshot: {}'
2505 .format(e)
2506 )
2507 raise xs_errors.XenError('VDIClone', opterr=str(e))
2509 self.sr._journaler.remove(LinstorJournaler.CLONE, active_uuid)
2511 return ret_vdi.get_params()
2513 @staticmethod
2514 def _start_persistent_http_server(volume_name):
2515 pid_path = None
2516 http_server = None
2518 try:
2519 if volume_name == HA_VOLUME_NAME:
2520 port = '8076'
2521 else:
2522 port = '8077'
2524 try:
2525 # Use a timeout call because XAPI may be unusable on startup
2526 # or if the host has been ejected. So in this case the call can
2527 # block indefinitely.
2528 session = util.timeout_call(5, util.get_localAPI_session)
2529 host_ip = util.get_this_host_address(session)
2530 except:
2531 # Fallback using the XHA file if session not available.
2532 host_ip, _ = get_ips_from_xha_config_file()
2533 if not host_ip:
2534 raise Exception(
2535 'Cannot start persistent HTTP server: no XAPI session, nor XHA config file'
2536 )
2538 arguments = [
2539 'http-disk-server',
2540 '--disk',
2541 '/dev/drbd/by-res/{}/0'.format(volume_name),
2542 '--ip',
2543 host_ip,
2544 '--port',
2545 port
2546 ]
2548 util.SMlog('Starting {} on port {}...'.format(arguments[0], port))
2549 http_server = subprocess.Popen(
2550 [FORK_LOG_DAEMON] + arguments,
2551 stdout=subprocess.PIPE,
2552 stderr=subprocess.STDOUT,
2553 # Ensure we use another group id to kill this process without
2554 # touch the current one.
2555 preexec_fn=os.setsid
2556 )
2558 pid_path = '/run/http-server-{}.pid'.format(volume_name)
2559 with open(pid_path, 'w') as pid_file:
2560 pid_file.write(str(http_server.pid))
2562 reg_server_ready = re.compile("Server ready!$")
2563 def is_ready():
2564 while http_server.poll() is None:
2565 line = http_server.stdout.readline()
2566 if reg_server_ready.search(line):
2567 return True
2568 return False
2569 try:
2570 if not util.timeout_call(10, is_ready):
2571 raise Exception('Failed to wait HTTP server startup, bad output')
2572 except util.TimeoutException:
2573 raise Exception('Failed to wait for HTTP server startup during given delay')
2574 except Exception as e:
2575 if pid_path:
2576 try:
2577 os.remove(pid_path)
2578 except Exception:
2579 pass
2581 if http_server:
2582 # Kill process and children in this case...
2583 try:
2584 os.killpg(os.getpgid(http_server.pid), signal.SIGTERM)
2585 except:
2586 pass
2588 raise xs_errors.XenError(
2589 'VDIUnavailable',
2590 opterr='Failed to start http-server: {}'.format(e)
2591 )
2593 def _start_persistent_nbd_server(self, volume_name):
2594 pid_path = None
2595 nbd_path = None
2596 nbd_server = None
2598 try:
2599 # We use a precomputed device size.
2600 # So if the XAPI is modified, we must update these values!
2601 if volume_name == HA_VOLUME_NAME:
2602 # See: https://github.com/xapi-project/xen-api/blob/703479fa448a8d7141954bb6e8964d8e25c4ac2e/ocaml/xapi/xha_statefile.ml#L32-L37
2603 port = '8076'
2604 device_size = 4 * 1024 * 1024
2605 else:
2606 # See: https://github.com/xapi-project/xen-api/blob/703479fa448a8d7141954bb6e8964d8e25c4ac2e/ocaml/database/redo_log.ml#L41-L44
2607 port = '8077'
2608 device_size = 256 * 1024 * 1024
2610 try:
2611 session = util.timeout_call(5, util.get_localAPI_session)
2612 ips = util.get_host_addresses(session)
2613 except Exception as e:
2614 _, ips = get_ips_from_xha_config_file()
2615 if not ips:
2616 raise Exception(
2617 'Cannot start persistent NBD server: no XAPI session, nor XHA config file ({})'.format(e)
2618 )
2619 ips = ips.values()
2621 arguments = [
2622 'nbd-http-server',
2623 '--socket-path',
2624 '/run/{}.socket'.format(volume_name),
2625 '--nbd-name',
2626 volume_name,
2627 '--urls',
2628 ','.join(['http://' + ip + ':' + port for ip in ips]),
2629 '--device-size',
2630 str(device_size)
2631 ]
2633 util.SMlog('Starting {} using port {}...'.format(arguments[0], port))
2634 nbd_server = subprocess.Popen(
2635 [FORK_LOG_DAEMON] + arguments,
2636 stdout=subprocess.PIPE,
2637 stderr=subprocess.STDOUT,
2638 # Ensure we use another group id to kill this process without
2639 # touch the current one.
2640 preexec_fn=os.setsid
2641 )
2643 pid_path = '/run/nbd-server-{}.pid'.format(volume_name)
2644 with open(pid_path, 'w') as pid_file:
2645 pid_file.write(str(nbd_server.pid))
2647 reg_nbd_path = re.compile("NBD `(/dev/nbd[0-9]+)` is now attached.$")
2648 def get_nbd_path():
2649 while nbd_server.poll() is None:
2650 line = nbd_server.stdout.readline()
2651 match = reg_nbd_path.search(line)
2652 if match:
2653 return match.group(1)
2654 # Use a timeout to never block the smapi if there is a problem.
2655 try:
2656 nbd_path = util.timeout_call(10, get_nbd_path)
2657 if nbd_path is None:
2658 raise Exception('Empty NBD path (NBD server is probably dead)')
2659 except util.TimeoutException:
2660 raise Exception('Unable to read NBD path')
2662 util.SMlog('Create symlink: {} -> {}'.format(self.path, nbd_path))
2663 os.symlink(nbd_path, self.path)
2664 except Exception as e:
2665 if pid_path:
2666 try:
2667 os.remove(pid_path)
2668 except Exception:
2669 pass
2671 if nbd_path:
2672 try:
2673 os.remove(nbd_path)
2674 except Exception:
2675 pass
2677 if nbd_server:
2678 # Kill process and children in this case...
2679 try:
2680 os.killpg(os.getpgid(nbd_server.pid), signal.SIGTERM)
2681 except:
2682 pass
2684 raise xs_errors.XenError(
2685 'VDIUnavailable',
2686 opterr='Failed to start nbd-server: {}'.format(e)
2687 )
2689 @classmethod
2690 def _kill_persistent_server(self, type, volume_name, sig):
2691 try:
2692 path = '/run/{}-server-{}.pid'.format(type, volume_name)
2693 if not os.path.exists(path):
2694 return
2696 pid = None
2697 with open(path, 'r') as pid_file:
2698 try:
2699 pid = int(pid_file.read())
2700 except Exception:
2701 pass
2703 if pid is not None and util.check_pid_exists(pid):
2704 util.SMlog('Kill {} server {} (pid={})'.format(type, path, pid))
2705 try:
2706 os.killpg(os.getpgid(pid), sig)
2707 except Exception as e:
2708 util.SMlog('Failed to kill {} server: {}'.format(type, e))
2710 os.remove(path)
2711 except:
2712 pass
2714 @classmethod
2715 def _kill_persistent_http_server(self, volume_name, sig=signal.SIGTERM):
2716 return self._kill_persistent_server('nbd', volume_name, sig)
2718 @classmethod
2719 def _kill_persistent_nbd_server(self, volume_name, sig=signal.SIGTERM):
2720 return self._kill_persistent_server('http', volume_name, sig)
2722 def _check_http_nbd_volume_name(self):
2723 volume_name = self.path[14:]
2724 if volume_name not in [
2725 HA_VOLUME_NAME, REDO_LOG_VOLUME_NAME
2726 ]:
2727 raise xs_errors.XenError(
2728 'VDIUnavailable',
2729 opterr='Unsupported path: {}'.format(self.path)
2730 )
2731 return volume_name
2733 def _attach_using_http_nbd(self):
2734 volume_name = self._check_http_nbd_volume_name()
2736 # Ensure there is no NBD and HTTP server running.
2737 self._kill_persistent_nbd_server(volume_name)
2738 self._kill_persistent_http_server(volume_name)
2740 # 0. Fetch drbd path.
2741 must_get_device_path = True
2742 if not self.sr._is_master:
2743 # We are on a slave, we must try to find a diskful locally.
2744 try:
2745 volume_info = self._linstor.get_volume_info(self.uuid)
2746 except Exception as e:
2747 raise xs_errors.XenError(
2748 'VDIUnavailable',
2749 opterr='Cannot get volume info of {}: {}'
2750 .format(self.uuid, e)
2751 )
2753 hostname = socket.gethostname()
2754 must_get_device_path = hostname in volume_info.diskful
2756 drbd_path = None
2757 if must_get_device_path or self.sr._is_master:
2758 # If we are master, we must ensure we have a diskless
2759 # or diskful available to init HA.
2760 # It also avoid this error in xensource.log
2761 # (/usr/libexec/xapi/cluster-stack/xhad/ha_set_pool_state):
2762 # init exited with code 8 [stdout = ''; stderr = 'SF: failed to write in State-File \x10 (fd 4208696). (sys 28)\x0A']
2763 # init returned MTC_EXIT_CAN_NOT_ACCESS_STATEFILE (State-File is inaccessible)
2764 available = False
2765 try:
2766 drbd_path = self._linstor.get_device_path(self.uuid)
2767 available = util.pathexists(drbd_path)
2768 except Exception:
2769 pass
2771 if not available:
2772 raise xs_errors.XenError(
2773 'VDIUnavailable',
2774 opterr='Cannot get device path of {}'.format(self.uuid)
2775 )
2777 # 1. Prepare http-nbd folder.
2778 try:
2779 if not os.path.exists('/dev/http-nbd/'):
2780 os.makedirs('/dev/http-nbd/')
2781 elif os.path.islink(self.path):
2782 os.remove(self.path)
2783 except OSError as e:
2784 if e.errno != errno.EEXIST:
2785 raise xs_errors.XenError(
2786 'VDIUnavailable',
2787 opterr='Cannot prepare http-nbd: {}'.format(e)
2788 )
2790 # 2. Start HTTP service if we have a diskful or if we are master.
2791 http_service = None
2792 if drbd_path:
2793 assert(drbd_path in (
2794 '/dev/drbd/by-res/{}/0'.format(HA_VOLUME_NAME),
2795 '/dev/drbd/by-res/{}/0'.format(REDO_LOG_VOLUME_NAME)
2796 ))
2797 self._start_persistent_http_server(volume_name)
2799 # 3. Start NBD server in all cases.
2800 try:
2801 self._start_persistent_nbd_server(volume_name)
2802 except Exception as e:
2803 if drbd_path:
2804 self._kill_persistent_http_server(volume_name)
2805 raise
2807 self.attached = True
2808 return VDI.VDI.attach(self, self.sr.uuid, self.uuid)
2810 def _detach_using_http_nbd(self):
2811 volume_name = self._check_http_nbd_volume_name()
2812 self._kill_persistent_nbd_server(volume_name)
2813 self._kill_persistent_http_server(volume_name)
2815 def _create_chain_paths(self, vdi_uuid):
2816 # OPTIMIZE: Add a limit_to_first_allocated_block param to limit vhdutil calls.
2817 # Useful for the snapshot code algorithm.
2819 while vdi_uuid:
2820 path = self._linstor.get_device_path(vdi_uuid)
2821 if not util.pathexists(path):
2822 raise xs_errors.XenError(
2823 'VDIUnavailable', opterr='Could not find: {}'.format(path)
2824 )
2826 # Diskless path can be created on the fly, ensure we can open it.
2827 def check_volume_usable():
2828 while True:
2829 try:
2830 with open(path, 'r+'):
2831 pass
2832 except IOError as e:
2833 if e.errno == errno.ENODATA:
2834 time.sleep(2)
2835 continue
2836 if e.errno == errno.EROFS:
2837 util.SMlog('Volume not attachable because RO. Openers: {}'.format(
2838 self.sr._linstor.get_volume_openers(vdi_uuid)
2839 ))
2840 raise
2841 break
2842 util.retry(check_volume_usable, 15, 2)
2844 vdi_uuid = self.sr._vhdutil.get_vhd_info(vdi_uuid).parentUuid
2846# ------------------------------------------------------------------------------
2849if __name__ == '__main__': 2849 ↛ 2850line 2849 didn't jump to line 2850, because the condition on line 2849 was never true
2850 def run():
2851 SRCommand.run(LinstorSR, DRIVER_INFO)
2853 if not TRACE_PERFS:
2854 run()
2855 else:
2856 util.make_profile('LinstorSR', run)
2857else:
2858 SR.registerSR(LinstorSR)