Coverage for drivers/LinstorSR.py : 0%

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
27 LINSTOR_AVAILABLE = True
28except ImportError:
29 LINSTOR_AVAILABLE = False
31from lock import Lock
32import blktap2
33import cleanup
34import distutils
35import errno
36import functools
37import lvutil
38import os
39import re
40import scsiutil
41import signal
42import socket
43import SR
44import SRCommand
45import subprocess
46import time
47import traceback
48import util
49import VDI
50import vhdutil
51import xml.etree.ElementTree as xml_parser
52import xmlrpc.client
53import xs_errors
55from srmetadata import \
56 NAME_LABEL_TAG, NAME_DESCRIPTION_TAG, IS_A_SNAPSHOT_TAG, SNAPSHOT_OF_TAG, \
57 TYPE_TAG, VDI_TYPE_TAG, READ_ONLY_TAG, SNAPSHOT_TIME_TAG, \
58 METADATA_OF_POOL_TAG
60HIDDEN_TAG = 'hidden'
62XHA_CONFIG_PATH = '/etc/xensource/xhad.conf'
64FORK_LOG_DAEMON = '/opt/xensource/libexec/fork-log-daemon'
66# This flag can be disabled to debug the DRBD layer.
67# When this config var is False, the HA can only be used under
68# specific conditions:
69# - Only one heartbeat diskless VDI is present in the pool.
70# - The other hearbeat volumes must be diskful and limited to a maximum of 3.
71USE_HTTP_NBD_SERVERS = True
73# Useful flag to trace calls using cProfile.
74TRACE_PERFS = False
76# Enable/Disable VHD key hash support.
77USE_KEY_HASH = False
79# ==============================================================================
81# TODO: Supports 'VDI_INTRODUCE', 'VDI_RESET_ON_BOOT/2', 'SR_TRIM',
82# 'VDI_CONFIG_CBT', 'SR_PROBE'
84CAPABILITIES = [
85 'ATOMIC_PAUSE',
86 'SR_UPDATE',
87 'VDI_CREATE',
88 'VDI_DELETE',
89 'VDI_UPDATE',
90 'VDI_ATTACH',
91 'VDI_DETACH',
92 'VDI_ACTIVATE',
93 'VDI_DEACTIVATE',
94 'VDI_CLONE',
95 'VDI_MIRROR',
96 'VDI_RESIZE',
97 'VDI_SNAPSHOT',
98 'VDI_GENERATE_CONFIG'
99]
101CONFIGURATION = [
102 ['group-name', 'LVM group name'],
103 ['redundancy', 'replication count'],
104 ['provisioning', '"thin" or "thick" are accepted (optional, defaults to thin)'],
105 ['monitor-db-quorum', 'disable controller when only one host is online (optional, defaults to true)']
106]
108DRIVER_INFO = {
109 'name': 'LINSTOR resources on XCP-ng',
110 'description': 'SR plugin which uses Linstor to manage VDIs',
111 'vendor': 'Vates',
112 'copyright': '(C) 2020 Vates',
113 'driver_version': '1.0',
114 'required_api_version': '1.0',
115 'capabilities': CAPABILITIES,
116 'configuration': CONFIGURATION
117}
119DRIVER_CONFIG = {'ATTACH_FROM_CONFIG_WITH_TAPDISK': False}
121OPS_EXCLUSIVE = [
122 'sr_create', 'sr_delete', 'sr_attach', 'sr_detach', 'sr_scan',
123 'sr_update', 'sr_probe', 'vdi_init', 'vdi_create', 'vdi_delete',
124 'vdi_attach', 'vdi_detach', 'vdi_clone', 'vdi_snapshot',
125]
127# ==============================================================================
128# Misc helpers used by LinstorSR and linstor-thin plugin.
129# ==============================================================================
132def compute_volume_size(virtual_size, image_type):
133 if image_type == vhdutil.VDI_TYPE_VHD:
134 # All LINSTOR VDIs have the metadata area preallocated for
135 # the maximum possible virtual size (for fast online VDI.resize).
136 meta_overhead = vhdutil.calcOverheadEmpty(LinstorVDI.MAX_SIZE)
137 bitmap_overhead = vhdutil.calcOverheadBitmap(virtual_size)
138 virtual_size += meta_overhead + bitmap_overhead
139 elif image_type != vhdutil.VDI_TYPE_RAW:
140 raise Exception('Invalid image type: {}'.format(image_type))
142 return LinstorVolumeManager.round_up_volume_size(virtual_size)
145def try_lock(lock):
146 for i in range(20):
147 if lock.acquireNoblock():
148 return
149 time.sleep(1)
150 raise util.SRBusyException()
153def attach_thin(session, journaler, linstor, sr_uuid, vdi_uuid):
154 volume_metadata = linstor.get_volume_metadata(vdi_uuid)
155 image_type = volume_metadata.get(VDI_TYPE_TAG)
156 if image_type == vhdutil.VDI_TYPE_RAW:
157 return
159 lock = Lock(vhdutil.LOCK_TYPE_SR, sr_uuid)
160 try:
161 try_lock(lock)
163 device_path = linstor.get_device_path(vdi_uuid)
165 # If the virtual VHD size is lower than the LINSTOR volume size,
166 # there is nothing to do.
167 vhd_size = compute_volume_size(
168 # TODO: Replace pylint comment with this feature when possible:
169 # https://github.com/PyCQA/pylint/pull/2926
170 LinstorVhdUtil(session, linstor).get_size_virt(vdi_uuid), # pylint: disable = E1120
171 image_type
172 )
174 volume_info = linstor.get_volume_info(vdi_uuid)
175 volume_size = volume_info.virtual_size
177 if vhd_size > volume_size:
178 inflate(
179 journaler, linstor, vdi_uuid, device_path,
180 vhd_size, volume_size
181 )
182 finally:
183 lock.release()
186def detach_thin(session, linstor, sr_uuid, vdi_uuid):
187 volume_metadata = linstor.get_volume_metadata(vdi_uuid)
188 image_type = volume_metadata.get(VDI_TYPE_TAG)
189 if image_type == vhdutil.VDI_TYPE_RAW:
190 return
192 lock = Lock(vhdutil.LOCK_TYPE_SR, sr_uuid)
193 try:
194 try_lock(lock)
196 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
197 vbds = session.xenapi.VBD.get_all_records_where(
198 'field "VDI" = "{}"'.format(vdi_ref)
199 )
201 num_plugged = 0
202 for vbd_rec in vbds.values():
203 if vbd_rec['currently_attached']:
204 num_plugged += 1
205 if num_plugged > 1:
206 raise xs_errors.XenError(
207 'VDIUnavailable',
208 opterr='Cannot deflate VDI {}, already used by '
209 'at least 2 VBDs'.format(vdi_uuid)
210 )
212 device_path = linstor.get_device_path(vdi_uuid)
213 new_volume_size = LinstorVolumeManager.round_up_volume_size(
214 # TODO: Replace pylint comment with this feature when possible:
215 # https://github.com/PyCQA/pylint/pull/2926
216 LinstorVhdUtil(session, linstor).get_size_phys(vdi_uuid) # pylint: disable = E1120
217 )
219 volume_info = linstor.get_volume_info(vdi_uuid)
220 old_volume_size = volume_info.virtual_size
221 deflate(
222 linstor, vdi_uuid, device_path, new_volume_size, old_volume_size
223 )
224 finally:
225 lock.release()
228def inflate(journaler, linstor, vdi_uuid, vdi_path, new_size, old_size):
229 # Only inflate if the LINSTOR volume capacity is not enough.
230 new_size = LinstorVolumeManager.round_up_volume_size(new_size)
231 if new_size <= old_size:
232 return
234 util.SMlog(
235 'Inflate {} (size={}, previous={})'
236 .format(vdi_uuid, new_size, old_size)
237 )
239 journaler.create(
240 LinstorJournaler.INFLATE, vdi_uuid, old_size
241 )
242 linstor.resize_volume(vdi_uuid, new_size)
244 result_size = linstor.get_volume_size(vdi_uuid)
245 if result_size < new_size:
246 util.SMlog(
247 'WARNING: Cannot inflate volume to {}B, result size: {}B'
248 .format(new_size, result_size)
249 )
251 if not util.zeroOut(
252 vdi_path, result_size - vhdutil.VHD_FOOTER_SIZE,
253 vhdutil.VHD_FOOTER_SIZE
254 ):
255 raise xs_errors.XenError(
256 'EIO',
257 opterr='Failed to zero out VHD footer {}'.format(vdi_path)
258 )
260 LinstorVhdUtil(None, linstor).set_size_phys(vdi_path, result_size, False)
261 journaler.remove(LinstorJournaler.INFLATE, vdi_uuid)
264def deflate(linstor, vdi_uuid, vdi_path, new_size, old_size):
265 new_size = LinstorVolumeManager.round_up_volume_size(new_size)
266 if new_size >= old_size:
267 return
269 util.SMlog(
270 'Deflate {} (new size={}, previous={})'
271 .format(vdi_uuid, new_size, old_size)
272 )
274 LinstorVhdUtil(None, linstor).set_size_phys(vdi_path, new_size)
275 # TODO: Change the LINSTOR volume size using linstor.resize_volume.
278IPS_XHA_CACHE = None
281def get_ips_from_xha_config_file():
282 if IPS_XHA_CACHE:
283 return IPS_XHA_CACHE
285 ips = dict()
286 host_id = None
287 try:
288 # Ensure there is no dirty read problem.
289 # For example if the HA is reloaded.
290 tree = util.retry(
291 lambda: xml_parser.parse(XHA_CONFIG_PATH),
292 maxretry=10,
293 period=1
294 )
295 except:
296 return (None, ips)
298 def parse_host_nodes(ips, node):
299 current_id = None
300 current_ip = None
302 for sub_node in node:
303 if sub_node.tag == 'IPaddress':
304 current_ip = sub_node.text
305 elif sub_node.tag == 'HostID':
306 current_id = sub_node.text
307 else:
308 continue
310 if current_id and current_ip:
311 ips[current_id] = current_ip
312 return
313 util.SMlog('Ill-formed XHA file, missing IPaddress or/and HostID')
315 def parse_common_config(ips, node):
316 for sub_node in node:
317 if sub_node.tag == 'host':
318 parse_host_nodes(ips, sub_node)
320 def parse_local_config(ips, node):
321 for sub_node in node:
322 if sub_node.tag == 'localhost':
323 for host_node in sub_node:
324 if host_node.tag == 'HostID':
325 return host_node.text
327 for node in tree.getroot():
328 if node.tag == 'common-config':
329 parse_common_config(ips, node)
330 elif node.tag == 'local-config':
331 host_id = parse_local_config(ips, node)
332 else:
333 continue
335 if ips and host_id:
336 break
338 return (host_id and ips.get(host_id), ips)
341def activate_lvm_group(group_name):
342 path = group_name.split('/')
343 assert path and len(path) <= 2
344 try:
345 lvutil.setActiveVG(path[0], True)
346 except Exception as e:
347 util.SMlog('Cannot active VG `{}`: {}'.format(path[0], e))
349# ==============================================================================
351# Usage example:
352# xe sr-create type=linstor name-label=linstor-sr
353# host-uuid=d2deba7a-c5ad-4de1-9a20-5c8df3343e93
354# device-config:group-name=vg_loop device-config:redundancy=2
357class LinstorSR(SR.SR):
358 DRIVER_TYPE = 'linstor'
360 PROVISIONING_TYPES = ['thin', 'thick']
361 PROVISIONING_DEFAULT = 'thin'
363 MANAGER_PLUGIN = 'linstor-manager'
365 INIT_STATUS_NOT_SET = 0
366 INIT_STATUS_IN_PROGRESS = 1
367 INIT_STATUS_OK = 2
368 INIT_STATUS_FAIL = 3
370 # --------------------------------------------------------------------------
371 # SR methods.
372 # --------------------------------------------------------------------------
374 @staticmethod
375 def handles(type):
376 return type == LinstorSR.DRIVER_TYPE
378 def load(self, sr_uuid):
379 if not LINSTOR_AVAILABLE:
380 raise util.SMException(
381 'Can\'t load LinstorSR: LINSTOR libraries are missing'
382 )
384 # Check parameters.
385 if 'group-name' not in self.dconf or not self.dconf['group-name']:
386 raise xs_errors.XenError('LinstorConfigGroupNameMissing')
387 if 'redundancy' not in self.dconf or not self.dconf['redundancy']:
388 raise xs_errors.XenError('LinstorConfigRedundancyMissing')
390 self.driver_config = DRIVER_CONFIG
392 # Check provisioning config.
393 provisioning = self.dconf.get('provisioning')
394 if provisioning:
395 if provisioning in self.PROVISIONING_TYPES:
396 self._provisioning = provisioning
397 else:
398 raise xs_errors.XenError(
399 'InvalidArg',
400 opterr='Provisioning parameter must be one of {}'.format(
401 self.PROVISIONING_TYPES
402 )
403 )
404 else:
405 self._provisioning = self.PROVISIONING_DEFAULT
407 monitor_db_quorum = self.dconf.get('monitor-db-quorum')
408 self._monitor_db_quorum = (monitor_db_quorum is None) or \
409 distutils.util.strtobool(monitor_db_quorum)
411 # Note: We don't have access to the session field if the
412 # 'vdi_attach_from_config' command is executed.
413 self._has_session = self.sr_ref and self.session is not None
414 if self._has_session:
415 self.sm_config = self.session.xenapi.SR.get_sm_config(self.sr_ref)
416 else:
417 self.sm_config = self.srcmd.params.get('sr_sm_config') or {}
419 provisioning = self.sm_config.get('provisioning')
420 if provisioning in self.PROVISIONING_TYPES:
421 self._provisioning = provisioning
423 # Define properties for SR parent class.
424 self.ops_exclusive = OPS_EXCLUSIVE
425 self.path = LinstorVolumeManager.DEV_ROOT_PATH
426 self.lock = Lock(vhdutil.LOCK_TYPE_SR, self.uuid)
427 self.sr_vditype = SR.DEFAULT_TAP
429 if self.cmd == 'sr_create':
430 self._redundancy = int(self.dconf['redundancy']) or 1
431 self._linstor = None # Ensure that LINSTOR attribute exists.
432 self._journaler = None
434 self._is_master = False
435 if 'SRmaster' in self.dconf and self.dconf['SRmaster'] == 'true':
436 self._is_master = True
437 self._group_name = self.dconf['group-name']
439 self._vdi_shared_time = 0
441 self._init_status = self.INIT_STATUS_NOT_SET
443 self._vdis_loaded = False
444 self._all_volume_info_cache = None
445 self._all_volume_metadata_cache = None
447 def _locked_load(method):
448 def wrapped_method(self, *args, **kwargs):
449 self._init_status = self.INIT_STATUS_OK
450 return method(self, *args, **kwargs)
452 def load(self, *args, **kwargs):
453 # Activate all LVMs to make drbd-reactor happy.
454 if self.srcmd.cmd == 'sr_attach':
455 activate_lvm_group(self._group_name)
457 if not self._has_session:
458 if self.srcmd.cmd in (
459 'vdi_attach_from_config',
460 'vdi_detach_from_config',
461 # When on-slave (is_open) is executed we have an
462 # empty command.
463 None
464 ):
465 def create_linstor(uri, attempt_count=30):
466 self._linstor = LinstorVolumeManager(
467 uri,
468 self._group_name,
469 logger=util.SMlog,
470 attempt_count=attempt_count
471 )
473 controller_uri = get_controller_uri()
474 if controller_uri:
475 create_linstor(controller_uri)
476 else:
477 def connect():
478 # We must have a valid LINSTOR instance here without using
479 # the XAPI. Fallback with the HA config file.
480 for ip in get_ips_from_xha_config_file()[1].values():
481 controller_uri = 'linstor://' + ip
482 try:
483 util.SMlog('Connecting from config to LINSTOR controller using: {}'.format(ip))
484 create_linstor(controller_uri, attempt_count=0)
485 return controller_uri
486 except:
487 pass
489 controller_uri = util.retry(connect, maxretry=30, period=1)
490 if not controller_uri:
491 raise xs_errors.XenError(
492 'SRUnavailable',
493 opterr='No valid controller URI to attach/detach from config'
494 )
496 self._journaler = LinstorJournaler(
497 controller_uri, self._group_name, logger=util.SMlog
498 )
500 if self.srcmd.cmd is None:
501 # Only useful on on-slave plugin (is_open).
502 self._vhdutil = LinstorVhdUtil(self.session, self._linstor)
504 return wrapped_method(self, *args, **kwargs)
506 if not self._is_master:
507 if self.cmd in [
508 'sr_create', 'sr_delete', 'sr_update', 'sr_probe',
509 'sr_scan', 'vdi_create', 'vdi_delete', 'vdi_resize',
510 'vdi_snapshot', 'vdi_clone'
511 ]:
512 util.SMlog('{} blocked for non-master'.format(self.cmd))
513 raise xs_errors.XenError('LinstorMaster')
515 # Because the LINSTOR KV objects cache all values, we must lock
516 # the VDI before the LinstorJournaler/LinstorVolumeManager
517 # instantiation and before any action on the master to avoid a
518 # bad read. The lock is also necessary to avoid strange
519 # behaviors if the GC is executed during an action on a slave.
520 if self.cmd.startswith('vdi_'):
521 self._shared_lock_vdi(self.srcmd.params['vdi_uuid'])
522 self._vdi_shared_time = time.time()
524 if self.srcmd.cmd != 'sr_create' and self.srcmd.cmd != 'sr_detach':
525 try:
526 self._reconnect()
527 except Exception as e:
528 raise xs_errors.XenError('SRUnavailable', opterr=str(e))
530 if self._linstor:
531 try:
532 hosts = self._linstor.disconnected_hosts
533 except Exception as e:
534 raise xs_errors.XenError('SRUnavailable', opterr=str(e))
536 if hosts:
537 util.SMlog('Failed to join node(s): {}'.format(hosts))
539 # Ensure we use a non-locked volume when vhdutil is called.
540 if (
541 self._is_master and self.cmd.startswith('vdi_') and
542 self.cmd != 'vdi_create'
543 ):
544 self._linstor.ensure_volume_is_not_locked(
545 self.srcmd.params['vdi_uuid']
546 )
548 try:
549 # If the command is a SR scan command on the master,
550 # we must load all VDIs and clean journal transactions.
551 # We must load the VDIs in the snapshot case too only if
552 # there is at least one entry in the journal.
553 #
554 # If the command is a SR command we want at least to remove
555 # resourceless volumes.
556 if self._is_master and self.cmd not in [
557 'vdi_attach', 'vdi_detach',
558 'vdi_activate', 'vdi_deactivate',
559 'vdi_epoch_begin', 'vdi_epoch_end',
560 'vdi_update', 'vdi_destroy'
561 ]:
562 load_vdis = (
563 self.cmd == 'sr_scan' or
564 self.cmd == 'sr_attach'
565 ) or len(
566 self._journaler.get_all(LinstorJournaler.INFLATE)
567 ) or len(
568 self._journaler.get_all(LinstorJournaler.CLONE)
569 )
571 if load_vdis:
572 self._load_vdis()
574 self._linstor.remove_resourceless_volumes()
576 self._synchronize_metadata()
577 except Exception as e:
578 if self.cmd == 'sr_scan' or self.cmd == 'sr_attach':
579 # Always raise, we don't want to remove VDIs
580 # from the XAPI database otherwise.
581 raise e
582 util.SMlog(
583 'Ignoring exception in LinstorSR.load: {}'.format(e)
584 )
585 util.SMlog(traceback.format_exc())
587 return wrapped_method(self, *args, **kwargs)
589 @functools.wraps(wrapped_method)
590 def wrap(self, *args, **kwargs):
591 if self._init_status in \
592 (self.INIT_STATUS_OK, self.INIT_STATUS_IN_PROGRESS):
593 return wrapped_method(self, *args, **kwargs)
594 if self._init_status == self.INIT_STATUS_FAIL:
595 util.SMlog(
596 'Can\'t call method {} because initialization failed'
597 .format(method)
598 )
599 else:
600 try:
601 self._init_status = self.INIT_STATUS_IN_PROGRESS
602 return load(self, *args, **kwargs)
603 except Exception:
604 if self._init_status != self.INIT_STATUS_OK:
605 self._init_status = self.INIT_STATUS_FAIL
606 raise
608 return wrap
610 def cleanup(self):
611 if self._vdi_shared_time:
612 self._shared_lock_vdi(self.srcmd.params['vdi_uuid'], locked=False)
614 @_locked_load
615 def create(self, uuid, size):
616 util.SMlog('LinstorSR.create for {}'.format(self.uuid))
618 host_adresses = util.get_host_addresses(self.session)
619 if self._redundancy > len(host_adresses):
620 raise xs_errors.XenError(
621 'LinstorSRCreate',
622 opterr='Redundancy greater than host count'
623 )
625 xenapi = self.session.xenapi
626 srs = xenapi.SR.get_all_records_where(
627 'field "type" = "{}"'.format(self.DRIVER_TYPE)
628 )
629 srs = dict([e for e in srs.items() if e[1]['uuid'] != self.uuid])
631 for sr in srs.values():
632 for pbd in sr['PBDs']:
633 device_config = xenapi.PBD.get_device_config(pbd)
634 group_name = device_config.get('group-name')
635 if group_name and group_name == self._group_name:
636 raise xs_errors.XenError(
637 'LinstorSRCreate',
638 opterr='group name must be unique'
639 )
641 if srs:
642 raise xs_errors.XenError(
643 'LinstorSRCreate',
644 opterr='LINSTOR SR must be unique in a pool'
645 )
647 online_hosts = util.get_online_hosts(self.session)
648 if len(online_hosts) < len(host_adresses):
649 raise xs_errors.XenError(
650 'LinstorSRCreate',
651 opterr='Not enough online hosts'
652 )
654 ips = {}
655 for host_ref in online_hosts:
656 record = self.session.xenapi.host.get_record(host_ref)
657 hostname = record['hostname']
658 ips[hostname] = record['address']
660 # Ensure ports are opened and LINSTOR satellites
661 # are activated. In the same time the drbd-reactor instances
662 # must be stopped.
663 self._prepare_sr_on_all_hosts(self._group_name, enabled=True)
665 # Create SR.
666 # Throw if the SR already exists.
667 try:
668 self._linstor = LinstorVolumeManager.create_sr(
669 self._group_name,
670 ips,
671 self._redundancy,
672 thin_provisioning=self._provisioning == 'thin',
673 auto_quorum=self._monitor_db_quorum,
674 logger=util.SMlog
675 )
676 self._vhdutil = LinstorVhdUtil(self.session, self._linstor)
677 except Exception as e:
678 util.SMlog('Failed to create LINSTOR SR: {}'.format(e))
679 raise xs_errors.XenError('LinstorSRCreate', opterr=str(e))
681 try:
682 util.SMlog(
683 "Finishing SR creation, enable drbd-reactor on all hosts..."
684 )
685 self._update_drbd_reactor_on_all_hosts(enabled=True)
686 except Exception as e:
687 try:
688 self._linstor.destroy()
689 except Exception as e2:
690 util.SMlog(
691 'Failed to destroy LINSTOR SR after creation fail: {}'
692 .format(e2)
693 )
694 raise e
696 @_locked_load
697 def delete(self, uuid):
698 util.SMlog('LinstorSR.delete for {}'.format(self.uuid))
699 cleanup.gc_force(self.session, self.uuid)
701 if self.vdis or self._linstor._volumes:
702 raise xs_errors.XenError('SRNotEmpty')
704 node_name = get_controller_node_name()
705 if not node_name:
706 raise xs_errors.XenError(
707 'LinstorSRDelete',
708 opterr='Cannot get controller node name'
709 )
711 host = None
712 if node_name == 'localhost':
713 host = util.get_this_host_ref(self.session)
714 else:
715 for slave in util.get_all_slaves(self.session):
716 r_name = self.session.xenapi.host.get_record(slave)['hostname']
717 if r_name == node_name:
718 host = slave
719 break
721 if not host:
722 raise xs_errors.XenError(
723 'LinstorSRDelete',
724 opterr='Failed to find host with hostname: {}'.format(
725 node_name
726 )
727 )
729 try:
730 self._update_drbd_reactor_on_all_hosts(
731 controller_node_name=node_name, enabled=False
732 )
734 args = {
735 'groupName': self._group_name,
736 }
737 self._exec_manager_command(
738 host, 'destroy', args, 'LinstorSRDelete'
739 )
740 except Exception as e:
741 try:
742 self._update_drbd_reactor_on_all_hosts(
743 controller_node_name=node_name, enabled=True
744 )
745 except Exception as e2:
746 util.SMlog(
747 'Failed to restart drbd-reactor after destroy fail: {}'
748 .format(e2)
749 )
750 util.SMlog('Failed to delete LINSTOR SR: {}'.format(e))
751 raise xs_errors.XenError(
752 'LinstorSRDelete',
753 opterr=str(e)
754 )
756 Lock.cleanupAll(self.uuid)
758 @_locked_load
759 def update(self, uuid):
760 util.SMlog('LinstorSR.update for {}'.format(self.uuid))
762 # Well, how can we update a SR if it doesn't exist? :thinking:
763 if not self._linstor:
764 raise xs_errors.XenError(
765 'SRUnavailable',
766 opterr='no such volume group: {}'.format(self._group_name)
767 )
769 self._update_stats(0)
771 # Update the SR name and description only in LINSTOR metadata.
772 xenapi = self.session.xenapi
773 self._linstor.metadata = {
774 NAME_LABEL_TAG: util.to_plain_string(
775 xenapi.SR.get_name_label(self.sr_ref)
776 ),
777 NAME_DESCRIPTION_TAG: util.to_plain_string(
778 xenapi.SR.get_name_description(self.sr_ref)
779 )
780 }
782 @_locked_load
783 def attach(self, uuid):
784 util.SMlog('LinstorSR.attach for {}'.format(self.uuid))
786 if not self._linstor:
787 raise xs_errors.XenError(
788 'SRUnavailable',
789 opterr='no such group: {}'.format(self._group_name)
790 )
792 @_locked_load
793 def detach(self, uuid):
794 util.SMlog('LinstorSR.detach for {}'.format(self.uuid))
795 cleanup.abort(self.uuid)
797 @_locked_load
798 def probe(self):
799 util.SMlog('LinstorSR.probe for {}'.format(self.uuid))
800 # TODO
802 @_locked_load
803 def scan(self, uuid):
804 if self._init_status == self.INIT_STATUS_FAIL:
805 return
807 util.SMlog('LinstorSR.scan for {}'.format(self.uuid))
808 if not self._linstor:
809 raise xs_errors.XenError(
810 'SRUnavailable',
811 opterr='no such volume group: {}'.format(self._group_name)
812 )
814 # Note: `scan` can be called outside this module, so ensure the VDIs
815 # are loaded.
816 self._load_vdis()
817 self._update_physical_size()
819 for vdi_uuid in list(self.vdis.keys()):
820 if self.vdis[vdi_uuid].deleted:
821 del self.vdis[vdi_uuid]
823 # Update the database before the restart of the GC to avoid
824 # bad sync in the process if new VDIs have been introduced.
825 ret = super(LinstorSR, self).scan(self.uuid)
826 self._kick_gc()
827 return ret
829 @_locked_load
830 def vdi(self, uuid):
831 return LinstorVDI(self, uuid)
833 _locked_load = staticmethod(_locked_load)
835 # --------------------------------------------------------------------------
836 # Lock.
837 # --------------------------------------------------------------------------
839 def _shared_lock_vdi(self, vdi_uuid, locked=True):
840 master = util.get_master_ref(self.session)
842 command = 'lockVdi'
843 args = {
844 'groupName': self._group_name,
845 'srUuid': self.uuid,
846 'vdiUuid': vdi_uuid,
847 'locked': str(locked)
848 }
850 # Note: We must avoid to unlock the volume if the timeout is reached
851 # because during volume unlock, the SR lock is not used. Otherwise
852 # we could destroy a valid lock acquired from another host...
853 #
854 # This code is not very clean, the ideal solution would be to acquire
855 # the SR lock during volume unlock (like lock) but it's not easy
856 # to implement without impacting performance.
857 if not locked:
858 elapsed_time = time.time() - self._vdi_shared_time
859 timeout = LinstorVolumeManager.LOCKED_EXPIRATION_DELAY * 0.7
860 if elapsed_time >= timeout:
861 util.SMlog(
862 'Avoid unlock call of {} because timeout has been reached'
863 .format(vdi_uuid)
864 )
865 return
867 self._exec_manager_command(master, command, args, 'VDIUnavailable')
869 # --------------------------------------------------------------------------
870 # Network.
871 # --------------------------------------------------------------------------
873 def _exec_manager_command(self, host_ref, command, args, error):
874 host_rec = self.session.xenapi.host.get_record(host_ref)
875 host_uuid = host_rec['uuid']
877 try:
878 ret = self.session.xenapi.host.call_plugin(
879 host_ref, self.MANAGER_PLUGIN, command, args
880 )
881 except Exception as e:
882 util.SMlog(
883 'call-plugin on {} ({}:{} with {}) raised'.format(
884 host_uuid, self.MANAGER_PLUGIN, command, args
885 )
886 )
887 raise e
889 util.SMlog(
890 'call-plugin on {} ({}:{} with {}) returned: {}'.format(
891 host_uuid, self.MANAGER_PLUGIN, command, args, ret
892 )
893 )
894 if ret == 'False':
895 raise xs_errors.XenError(
896 error,
897 opterr='Plugin {} failed'.format(self.MANAGER_PLUGIN)
898 )
900 def _prepare_sr(self, host, group_name, enabled):
901 self._exec_manager_command(
902 host,
903 'prepareSr' if enabled else 'releaseSr',
904 {'groupName': group_name},
905 'SRUnavailable'
906 )
908 def _prepare_sr_on_all_hosts(self, group_name, enabled):
909 master = util.get_master_ref(self.session)
910 self._prepare_sr(master, group_name, enabled)
912 for slave in util.get_all_slaves(self.session):
913 self._prepare_sr(slave, group_name, enabled)
915 def _update_drbd_reactor(self, host, enabled):
916 self._exec_manager_command(
917 host,
918 'updateDrbdReactor',
919 {'enabled': str(enabled)},
920 'SRUnavailable'
921 )
923 def _update_drbd_reactor_on_all_hosts(
924 self, enabled, controller_node_name=None
925 ):
926 if controller_node_name == 'localhost':
927 controller_node_name = self.session.xenapi.host.get_record(
928 util.get_this_host_ref(self.session)
929 )['hostname']
930 assert controller_node_name
931 assert controller_node_name != 'localhost'
933 controller_host = None
934 secondary_hosts = []
936 hosts = self.session.xenapi.host.get_all_records()
937 for host_ref, host_rec in hosts.items():
938 hostname = host_rec['hostname']
939 if controller_node_name == hostname:
940 controller_host = host_ref
941 else:
942 secondary_hosts.append((host_ref, hostname))
944 action_name = 'Starting' if enabled else 'Stopping'
945 if controller_node_name and not controller_host:
946 util.SMlog('Failed to find controller host: `{}`'.format(
947 controller_node_name
948 ))
950 if enabled and controller_host:
951 util.SMlog('{} drbd-reactor on controller host `{}`...'.format(
952 action_name, controller_node_name
953 ))
954 # If enabled is true, we try to start the controller on the desired
955 # node name first.
956 self._update_drbd_reactor(controller_host, enabled)
958 for host_ref, hostname in secondary_hosts:
959 util.SMlog('{} drbd-reactor on host {}...'.format(
960 action_name, hostname
961 ))
962 self._update_drbd_reactor(host_ref, enabled)
964 if not enabled and controller_host:
965 util.SMlog('{} drbd-reactor on controller host `{}`...'.format(
966 action_name, controller_node_name
967 ))
968 # If enabled is false, we disable the drbd-reactor service of
969 # the controller host last. Why? Otherwise the linstor-controller
970 # of other nodes can be started, and we don't want that.
971 self._update_drbd_reactor(controller_host, enabled)
973 # --------------------------------------------------------------------------
974 # Metadata.
975 # --------------------------------------------------------------------------
977 def _synchronize_metadata_and_xapi(self):
978 try:
979 # First synch SR parameters.
980 self.update(self.uuid)
982 # Now update the VDI information in the metadata if required.
983 xenapi = self.session.xenapi
984 volumes_metadata = self._linstor.get_volumes_with_metadata()
985 for vdi_uuid, volume_metadata in volumes_metadata.items():
986 try:
987 vdi_ref = xenapi.VDI.get_by_uuid(vdi_uuid)
988 except Exception:
989 # May be the VDI is not in XAPI yet dont bother.
990 continue
992 label = util.to_plain_string(
993 xenapi.VDI.get_name_label(vdi_ref)
994 )
995 description = util.to_plain_string(
996 xenapi.VDI.get_name_description(vdi_ref)
997 )
999 if (
1000 volume_metadata.get(NAME_LABEL_TAG) != label or
1001 volume_metadata.get(NAME_DESCRIPTION_TAG) != description
1002 ):
1003 self._linstor.update_volume_metadata(vdi_uuid, {
1004 NAME_LABEL_TAG: label,
1005 NAME_DESCRIPTION_TAG: description
1006 })
1007 except Exception as e:
1008 raise xs_errors.XenError(
1009 'MetadataError',
1010 opterr='Error synching SR Metadata and XAPI: {}'.format(e)
1011 )
1013 def _synchronize_metadata(self):
1014 if not self._is_master:
1015 return
1017 util.SMlog('Synchronize metadata...')
1018 if self.cmd == 'sr_attach':
1019 try:
1020 util.SMlog(
1021 'Synchronize SR metadata and the state on the storage.'
1022 )
1023 self._synchronize_metadata_and_xapi()
1024 except Exception as e:
1025 util.SMlog('Failed to synchronize metadata: {}'.format(e))
1027 # --------------------------------------------------------------------------
1028 # Stats.
1029 # --------------------------------------------------------------------------
1031 def _update_stats(self, virt_alloc_delta):
1032 valloc = int(self.session.xenapi.SR.get_virtual_allocation(
1033 self.sr_ref
1034 ))
1036 # Update size attributes of the SR parent class.
1037 self.virtual_allocation = valloc + virt_alloc_delta
1039 self._update_physical_size()
1041 # Notify SR parent class.
1042 self._db_update()
1044 def _update_physical_size(self):
1045 # We use the size of the smallest disk, this is an approximation that
1046 # ensures the displayed physical size is reachable by the user.
1047 (min_physical_size, pool_count) = self._linstor.get_min_physical_size()
1048 self.physical_size = min_physical_size * pool_count // \
1049 self._linstor.redundancy
1051 self.physical_utilisation = self._linstor.allocated_volume_size
1053 # --------------------------------------------------------------------------
1054 # VDIs.
1055 # --------------------------------------------------------------------------
1057 def _load_vdis(self):
1058 if self._vdis_loaded:
1059 return
1061 assert self._is_master
1063 # We use a cache to avoid repeated JSON parsing.
1064 # The performance gain is not big but we can still
1065 # enjoy it with a few lines.
1066 self._create_linstor_cache()
1067 self._load_vdis_ex()
1068 self._destroy_linstor_cache()
1070 # We must mark VDIs as loaded only if the load is a success.
1071 self._vdis_loaded = True
1073 self._undo_all_journal_transactions()
1075 def _load_vdis_ex(self):
1076 # 1. Get existing VDIs in XAPI.
1077 xenapi = self.session.xenapi
1078 xapi_vdi_uuids = set()
1079 for vdi in xenapi.SR.get_VDIs(self.sr_ref):
1080 xapi_vdi_uuids.add(xenapi.VDI.get_uuid(vdi))
1082 # 2. Get volumes info.
1083 all_volume_info = self._all_volume_info_cache
1084 volumes_metadata = self._all_volume_metadata_cache
1086 # 3. Get CBT vdis.
1087 # See: https://support.citrix.com/article/CTX230619
1088 cbt_vdis = set()
1089 for volume_metadata in volumes_metadata.values():
1090 cbt_uuid = volume_metadata.get(CBTLOG_TAG)
1091 if cbt_uuid:
1092 cbt_vdis.add(cbt_uuid)
1094 introduce = False
1096 # Try to introduce VDIs only during scan/attach.
1097 if self.cmd == 'sr_scan' or self.cmd == 'sr_attach':
1098 has_clone_entries = list(self._journaler.get_all(
1099 LinstorJournaler.CLONE
1100 ).items())
1102 if has_clone_entries:
1103 util.SMlog(
1104 'Cannot introduce VDIs during scan because it exists '
1105 'CLONE entries in journaler on SR {}'.format(self.uuid)
1106 )
1107 else:
1108 introduce = True
1110 # 4. Now check all volume info.
1111 vdi_to_snaps = {}
1112 for vdi_uuid, volume_info in all_volume_info.items():
1113 if vdi_uuid.startswith(cleanup.SR.TMP_RENAME_PREFIX):
1114 continue
1116 # 4.a. Check if the VDI in LINSTOR is in XAPI VDIs.
1117 if vdi_uuid not in xapi_vdi_uuids:
1118 if not introduce:
1119 continue
1121 if vdi_uuid.startswith('DELETED_'):
1122 continue
1124 volume_metadata = volumes_metadata.get(vdi_uuid)
1125 if not volume_metadata:
1126 util.SMlog(
1127 'Skipping volume {} because no metadata could be found'
1128 .format(vdi_uuid)
1129 )
1130 continue
1132 util.SMlog(
1133 'Trying to introduce VDI {} as it is present in '
1134 'LINSTOR and not in XAPI...'
1135 .format(vdi_uuid)
1136 )
1138 try:
1139 self._linstor.get_device_path(vdi_uuid)
1140 except Exception as e:
1141 util.SMlog(
1142 'Cannot introduce {}, unable to get path: {}'
1143 .format(vdi_uuid, e)
1144 )
1145 continue
1147 name_label = volume_metadata.get(NAME_LABEL_TAG) or ''
1148 type = volume_metadata.get(TYPE_TAG) or 'user'
1149 vdi_type = volume_metadata.get(VDI_TYPE_TAG)
1151 if not vdi_type:
1152 util.SMlog(
1153 'Cannot introduce {} '.format(vdi_uuid) +
1154 'without vdi_type'
1155 )
1156 continue
1158 sm_config = {
1159 'vdi_type': vdi_type
1160 }
1162 if vdi_type == vhdutil.VDI_TYPE_RAW:
1163 managed = not volume_metadata.get(HIDDEN_TAG)
1164 elif vdi_type == vhdutil.VDI_TYPE_VHD:
1165 vhd_info = self._vhdutil.get_vhd_info(vdi_uuid)
1166 managed = not vhd_info.hidden
1167 if vhd_info.parentUuid:
1168 sm_config['vhd-parent'] = vhd_info.parentUuid
1169 else:
1170 util.SMlog(
1171 'Cannot introduce {} with invalid VDI type {}'
1172 .format(vdi_uuid, vdi_type)
1173 )
1174 continue
1176 util.SMlog(
1177 'Introducing VDI {} '.format(vdi_uuid) +
1178 ' (name={}, virtual_size={}, allocated_size={})'.format(
1179 name_label,
1180 volume_info.virtual_size,
1181 volume_info.allocated_size
1182 )
1183 )
1185 vdi_ref = xenapi.VDI.db_introduce(
1186 vdi_uuid,
1187 name_label,
1188 volume_metadata.get(NAME_DESCRIPTION_TAG) or '',
1189 self.sr_ref,
1190 type,
1191 False, # sharable
1192 bool(volume_metadata.get(READ_ONLY_TAG)),
1193 {}, # other_config
1194 vdi_uuid, # location
1195 {}, # xenstore_data
1196 sm_config,
1197 managed,
1198 str(volume_info.virtual_size),
1199 str(volume_info.allocated_size)
1200 )
1202 is_a_snapshot = volume_metadata.get(IS_A_SNAPSHOT_TAG)
1203 xenapi.VDI.set_is_a_snapshot(vdi_ref, bool(is_a_snapshot))
1204 if is_a_snapshot:
1205 xenapi.VDI.set_snapshot_time(
1206 vdi_ref,
1207 xmlrpc.client.DateTime(
1208 volume_metadata[SNAPSHOT_TIME_TAG] or
1209 '19700101T00:00:00Z'
1210 )
1211 )
1213 snap_uuid = volume_metadata[SNAPSHOT_OF_TAG]
1214 if snap_uuid in vdi_to_snaps:
1215 vdi_to_snaps[snap_uuid].append(vdi_uuid)
1216 else:
1217 vdi_to_snaps[snap_uuid] = [vdi_uuid]
1219 # 4.b. Add the VDI in the list.
1220 vdi = self.vdi(vdi_uuid)
1221 self.vdis[vdi_uuid] = vdi
1223 if USE_KEY_HASH and vdi.vdi_type == vhdutil.VDI_TYPE_VHD:
1224 # TODO: Replace pylint comment with this feature when possible:
1225 # https://github.com/PyCQA/pylint/pull/2926
1226 vdi.sm_config_override['key_hash'] = \
1227 self._vhdutil.get_key_hash(vdi_uuid) # pylint: disable = E1120
1229 # 4.c. Update CBT status of disks either just added
1230 # or already in XAPI.
1231 cbt_uuid = volume_metadata.get(CBTLOG_TAG)
1232 if cbt_uuid in cbt_vdis:
1233 vdi_ref = xenapi.VDI.get_by_uuid(vdi_uuid)
1234 xenapi.VDI.set_cbt_enabled(vdi_ref, True)
1235 # For existing VDIs, update local state too.
1236 # Scan in base class SR updates existing VDIs
1237 # again based on local states.
1238 self.vdis[vdi_uuid].cbt_enabled = True
1239 cbt_vdis.remove(cbt_uuid)
1241 # 5. Now set the snapshot statuses correctly in XAPI.
1242 for src_uuid in vdi_to_snaps:
1243 try:
1244 src_ref = xenapi.VDI.get_by_uuid(src_uuid)
1245 except Exception:
1246 # The source VDI no longer exists, continue.
1247 continue
1249 for snap_uuid in vdi_to_snaps[src_uuid]:
1250 try:
1251 # This might fail in cases where its already set.
1252 snap_ref = xenapi.VDI.get_by_uuid(snap_uuid)
1253 xenapi.VDI.set_snapshot_of(snap_ref, src_ref)
1254 except Exception as e:
1255 util.SMlog('Setting snapshot failed: {}'.format(e))
1257 # TODO: Check correctly how to use CBT.
1258 # Update cbt_enabled on the right VDI, check LVM/FileSR code.
1260 # 6. If we have items remaining in this list,
1261 # they are cbt_metadata VDI that XAPI doesn't know about.
1262 # Add them to self.vdis and they'll get added to the DB.
1263 for cbt_uuid in cbt_vdis:
1264 new_vdi = self.vdi(cbt_uuid)
1265 new_vdi.ty = 'cbt_metadata'
1266 new_vdi.cbt_enabled = True
1267 self.vdis[cbt_uuid] = new_vdi
1269 # 7. Update virtual allocation, build geneology and remove useless VDIs
1270 self.virtual_allocation = 0
1272 # 8. Build geneology.
1273 geneology = {}
1275 for vdi_uuid, vdi in self.vdis.items():
1276 if vdi.parent:
1277 if vdi.parent in self.vdis:
1278 self.vdis[vdi.parent].read_only = True
1279 if vdi.parent in geneology:
1280 geneology[vdi.parent].append(vdi_uuid)
1281 else:
1282 geneology[vdi.parent] = [vdi_uuid]
1283 if not vdi.hidden:
1284 self.virtual_allocation += vdi.size
1286 # 9. Remove all hidden leaf nodes to avoid introducing records that
1287 # will be GC'ed.
1288 for vdi_uuid in list(self.vdis.keys()):
1289 if vdi_uuid not in geneology and self.vdis[vdi_uuid].hidden:
1290 util.SMlog(
1291 'Scan found hidden leaf ({}), ignoring'.format(vdi_uuid)
1292 )
1293 del self.vdis[vdi_uuid]
1295 # --------------------------------------------------------------------------
1296 # Journals.
1297 # --------------------------------------------------------------------------
1299 def _get_vdi_path_and_parent(self, vdi_uuid, volume_name):
1300 try:
1301 device_path = self._linstor.build_device_path(volume_name)
1302 if not util.pathexists(device_path):
1303 return (None, None)
1305 # If it's a RAW VDI, there is no parent.
1306 volume_metadata = self._linstor.get_volume_metadata(vdi_uuid)
1307 vdi_type = volume_metadata[VDI_TYPE_TAG]
1308 if vdi_type == vhdutil.VDI_TYPE_RAW:
1309 return (device_path, None)
1311 # Otherwise it's a VHD and a parent can exist.
1312 if not self._vhdutil.check(vdi_uuid):
1313 return (None, None)
1315 vhd_info = self._vhdutil.get_vhd_info(vdi_uuid)
1316 if vhd_info:
1317 return (device_path, vhd_info.parentUuid)
1318 except Exception as e:
1319 util.SMlog(
1320 'Failed to get VDI path and parent, ignoring: {}'
1321 .format(e)
1322 )
1323 return (None, None)
1325 def _undo_all_journal_transactions(self):
1326 util.SMlog('Undoing all journal transactions...')
1327 self.lock.acquire()
1328 try:
1329 self._handle_interrupted_inflate_ops()
1330 self._handle_interrupted_clone_ops()
1331 pass
1332 finally:
1333 self.lock.release()
1335 def _handle_interrupted_inflate_ops(self):
1336 transactions = self._journaler.get_all(LinstorJournaler.INFLATE)
1337 for vdi_uuid, old_size in transactions.items():
1338 self._handle_interrupted_inflate(vdi_uuid, old_size)
1339 self._journaler.remove(LinstorJournaler.INFLATE, vdi_uuid)
1341 def _handle_interrupted_clone_ops(self):
1342 transactions = self._journaler.get_all(LinstorJournaler.CLONE)
1343 for vdi_uuid, old_size in transactions.items():
1344 self._handle_interrupted_clone(vdi_uuid, old_size)
1345 self._journaler.remove(LinstorJournaler.CLONE, vdi_uuid)
1347 def _handle_interrupted_inflate(self, vdi_uuid, old_size):
1348 util.SMlog(
1349 '*** INTERRUPTED INFLATE OP: for {} ({})'
1350 .format(vdi_uuid, old_size)
1351 )
1353 vdi = self.vdis.get(vdi_uuid)
1354 if not vdi:
1355 util.SMlog('Cannot deflate missing VDI {}'.format(vdi_uuid))
1356 return
1358 assert not self._all_volume_info_cache
1359 volume_info = self._linstor.get_volume_info(vdi_uuid)
1361 current_size = volume_info.virtual_size
1362 assert current_size > 0
1364 util.zeroOut(
1365 vdi.path,
1366 current_size - vhdutil.VHD_FOOTER_SIZE,
1367 vhdutil.VHD_FOOTER_SIZE
1368 )
1369 deflate(self._linstor, vdi_uuid, vdi.path, old_size, current_size)
1371 def _handle_interrupted_clone(
1372 self, vdi_uuid, clone_info, force_undo=False
1373 ):
1374 util.SMlog(
1375 '*** INTERRUPTED CLONE OP: for {} ({})'
1376 .format(vdi_uuid, clone_info)
1377 )
1379 base_uuid, snap_uuid = clone_info.split('_')
1381 # Use LINSTOR data because new VDIs may not be in the XAPI.
1382 volume_names = self._linstor.get_volumes_with_name()
1384 # Check if we don't have a base VDI. (If clone failed at startup.)
1385 if base_uuid not in volume_names:
1386 if vdi_uuid in volume_names:
1387 util.SMlog('*** INTERRUPTED CLONE OP: nothing to do')
1388 return
1389 raise util.SMException(
1390 'Base copy {} not present, but no original {} found'
1391 .format(base_uuid, vdi_uuid)
1392 )
1394 if force_undo:
1395 util.SMlog('Explicit revert')
1396 self._undo_clone(
1397 volume_names, vdi_uuid, base_uuid, snap_uuid
1398 )
1399 return
1401 # If VDI or snap uuid is missing...
1402 if vdi_uuid not in volume_names or \
1403 (snap_uuid and snap_uuid not in volume_names):
1404 util.SMlog('One or both leaves missing => revert')
1405 self._undo_clone(volume_names, vdi_uuid, base_uuid, snap_uuid)
1406 return
1408 vdi_path, vdi_parent_uuid = self._get_vdi_path_and_parent(
1409 vdi_uuid, volume_names[vdi_uuid]
1410 )
1411 snap_path, snap_parent_uuid = self._get_vdi_path_and_parent(
1412 snap_uuid, volume_names[snap_uuid]
1413 )
1415 if not vdi_path or (snap_uuid and not snap_path):
1416 util.SMlog('One or both leaves invalid (and path(s)) => revert')
1417 self._undo_clone(volume_names, vdi_uuid, base_uuid, snap_uuid)
1418 return
1420 util.SMlog('Leaves valid but => revert')
1421 self._undo_clone(volume_names, vdi_uuid, base_uuid, snap_uuid)
1423 def _undo_clone(self, volume_names, vdi_uuid, base_uuid, snap_uuid):
1424 base_path = self._linstor.build_device_path(volume_names[base_uuid])
1425 base_metadata = self._linstor.get_volume_metadata(base_uuid)
1426 base_type = base_metadata[VDI_TYPE_TAG]
1428 if not util.pathexists(base_path):
1429 util.SMlog('Base not found! Exit...')
1430 util.SMlog('*** INTERRUPTED CLONE OP: rollback fail')
1431 return
1433 # Un-hide the parent.
1434 self._linstor.update_volume_metadata(base_uuid, {READ_ONLY_TAG: False})
1435 if base_type == vhdutil.VDI_TYPE_VHD:
1436 vhd_info = self._vhdutil.get_vhd_info(base_uuid, False)
1437 if vhd_info.hidden:
1438 self._vhdutil.set_hidden(base_path, False)
1439 elif base_type == vhdutil.VDI_TYPE_RAW and \
1440 base_metadata.get(HIDDEN_TAG):
1441 self._linstor.update_volume_metadata(
1442 base_uuid, {HIDDEN_TAG: False}
1443 )
1445 # Remove the child nodes.
1446 if snap_uuid and snap_uuid in volume_names:
1447 util.SMlog('Destroying snap {}...'.format(snap_uuid))
1449 try:
1450 self._linstor.destroy_volume(snap_uuid)
1451 except Exception as e:
1452 util.SMlog(
1453 'Cannot destroy snap {} during undo clone: {}'
1454 .format(snap_uuid, e)
1455 )
1457 if vdi_uuid in volume_names:
1458 try:
1459 util.SMlog('Destroying {}...'.format(vdi_uuid))
1460 self._linstor.destroy_volume(vdi_uuid)
1461 except Exception as e:
1462 util.SMlog(
1463 'Cannot destroy VDI {} during undo clone: {}'
1464 .format(vdi_uuid, e)
1465 )
1466 # We can get an exception like this:
1467 # "Shutdown of the DRBD resource 'XXX failed", so the
1468 # volume info remains... The problem is we can't rename
1469 # properly the base VDI below this line, so we must change the
1470 # UUID of this bad VDI before.
1471 self._linstor.update_volume_uuid(
1472 vdi_uuid, 'DELETED_' + vdi_uuid, force=True
1473 )
1475 # Rename!
1476 self._linstor.update_volume_uuid(base_uuid, vdi_uuid)
1478 # Inflate to the right size.
1479 if base_type == vhdutil.VDI_TYPE_VHD:
1480 vdi = self.vdi(vdi_uuid)
1481 volume_size = compute_volume_size(vdi.size, vdi.vdi_type)
1482 inflate(
1483 self._journaler, self._linstor, vdi_uuid, vdi.path,
1484 volume_size, vdi.capacity
1485 )
1486 self.vdis[vdi_uuid] = vdi
1488 # At this stage, tapdisk and SM vdi will be in paused state. Remove
1489 # flag to facilitate vm deactivate.
1490 vdi_ref = self.session.xenapi.VDI.get_by_uuid(vdi_uuid)
1491 self.session.xenapi.VDI.remove_from_sm_config(vdi_ref, 'paused')
1493 util.SMlog('*** INTERRUPTED CLONE OP: rollback success')
1495 # --------------------------------------------------------------------------
1496 # Cache.
1497 # --------------------------------------------------------------------------
1499 def _create_linstor_cache(self):
1500 reconnect = False
1502 def create_cache():
1503 nonlocal reconnect
1504 try:
1505 if reconnect:
1506 self._reconnect()
1507 return self._linstor.get_volumes_with_info()
1508 except Exception as e:
1509 reconnect = True
1510 raise e
1512 self._all_volume_metadata_cache = \
1513 self._linstor.get_volumes_with_metadata()
1514 self._all_volume_info_cache = util.retry(
1515 create_cache,
1516 maxretry=10,
1517 period=3
1518 )
1520 def _destroy_linstor_cache(self):
1521 self._all_volume_info_cache = None
1522 self._all_volume_metadata_cache = None
1524 # --------------------------------------------------------------------------
1525 # Misc.
1526 # --------------------------------------------------------------------------
1528 def _reconnect(self):
1529 controller_uri = get_controller_uri()
1531 self._journaler = LinstorJournaler(
1532 controller_uri, self._group_name, logger=util.SMlog
1533 )
1535 # Try to open SR if exists.
1536 # We can repair only if we are on the master AND if
1537 # we are trying to execute an exclusive operation.
1538 # Otherwise we could try to delete a VDI being created or
1539 # during a snapshot. An exclusive op is the guarantee that
1540 # the SR is locked.
1541 self._linstor = LinstorVolumeManager(
1542 controller_uri,
1543 self._group_name,
1544 repair=(
1545 self._is_master and
1546 self.srcmd.cmd in self.ops_exclusive
1547 ),
1548 logger=util.SMlog
1549 )
1550 self._vhdutil = LinstorVhdUtil(self.session, self._linstor)
1552 def _ensure_space_available(self, amount_needed):
1553 space_available = self._linstor.max_volume_size_allowed
1554 if (space_available < amount_needed):
1555 util.SMlog(
1556 'Not enough space! Free space: {}, need: {}'.format(
1557 space_available, amount_needed
1558 )
1559 )
1560 raise xs_errors.XenError('SRNoSpace')
1562 def _kick_gc(self):
1563 # Don't bother if an instance already running. This is just an
1564 # optimization to reduce the overhead of forking a new process if we
1565 # don't have to, but the process will check the lock anyways.
1566 lock = Lock(cleanup.LOCK_TYPE_RUNNING, self.uuid)
1567 if not lock.acquireNoblock():
1568 if not cleanup.should_preempt(self.session, self.uuid):
1569 util.SMlog('A GC instance already running, not kicking')
1570 return
1572 util.SMlog('Aborting currently-running coalesce of garbage VDI')
1573 try:
1574 if not cleanup.abort(self.uuid, soft=True):
1575 util.SMlog('The GC has already been scheduled to re-start')
1576 except util.CommandException as e:
1577 if e.code != errno.ETIMEDOUT:
1578 raise
1579 util.SMlog('Failed to abort the GC')
1580 else:
1581 lock.release()
1583 util.SMlog('Kicking GC')
1584 cleanup.gc(self.session, self.uuid, True)
1586# ==============================================================================
1587# LinstorSr VDI
1588# ==============================================================================
1591class LinstorVDI(VDI.VDI):
1592 # Warning: Not the same values than vhdutil.VDI_TYPE_*.
1593 # These values represents the types given on the command line.
1594 TYPE_RAW = 'raw'
1595 TYPE_VHD = 'vhd'
1597 MAX_SIZE = 2 * 1024 * 1024 * 1024 * 1024 # Max VHD size.
1599 # Metadata size given to the "S" param of vhd-util create.
1600 # "-S size (MB) for metadata preallocation".
1601 # Increase the performance when resize is called.
1602 MAX_METADATA_VIRT_SIZE = 2 * 1024 * 1024
1604 # --------------------------------------------------------------------------
1605 # VDI methods.
1606 # --------------------------------------------------------------------------
1608 def load(self, vdi_uuid):
1609 self._lock = self.sr.lock
1610 self._exists = True
1611 self._linstor = self.sr._linstor
1613 # Update hidden parent property.
1614 self.hidden = False
1616 def raise_bad_load(e):
1617 util.SMlog(
1618 'Got exception in LinstorVDI.load: {}'.format(e)
1619 )
1620 util.SMlog(traceback.format_exc())
1621 raise xs_errors.XenError(
1622 'VDIUnavailable',
1623 opterr='Could not load {} because: {}'.format(self.uuid, e)
1624 )
1626 # Try to load VDI.
1627 try:
1628 if (
1629 self.sr.srcmd.cmd == 'vdi_attach_from_config' or
1630 self.sr.srcmd.cmd == 'vdi_detach_from_config'
1631 ):
1632 self.vdi_type = vhdutil.VDI_TYPE_RAW
1633 self.path = self.sr.srcmd.params['vdi_path']
1634 else:
1635 self._determine_type_and_path()
1636 self._load_this()
1638 util.SMlog('VDI {} loaded! (path={}, hidden={})'.format(
1639 self.uuid, self.path, self.hidden
1640 ))
1641 except LinstorVolumeManagerError as e:
1642 # 1. It may be a VDI deletion.
1643 if e.code == LinstorVolumeManagerError.ERR_VOLUME_NOT_EXISTS:
1644 if self.sr.srcmd.cmd == 'vdi_delete':
1645 self.deleted = True
1646 return
1648 # 2. Or maybe a creation.
1649 if self.sr.srcmd.cmd == 'vdi_create':
1650 # Set type attribute of VDI parent class.
1651 # We use VHD by default.
1652 self.vdi_type = vhdutil.VDI_TYPE_VHD
1653 self._key_hash = None # Only used in create.
1655 self._exists = False
1656 vdi_sm_config = self.sr.srcmd.params.get('vdi_sm_config')
1657 if vdi_sm_config is not None:
1658 type = vdi_sm_config.get('type')
1659 if type is not None:
1660 if type == self.TYPE_RAW:
1661 self.vdi_type = vhdutil.VDI_TYPE_RAW
1662 elif type == self.TYPE_VHD:
1663 self.vdi_type = vhdutil.VDI_TYPE_VHD
1664 else:
1665 raise xs_errors.XenError(
1666 'VDICreate',
1667 opterr='Invalid VDI type {}'.format(type)
1668 )
1669 if self.vdi_type == vhdutil.VDI_TYPE_VHD:
1670 self._key_hash = vdi_sm_config.get('key_hash')
1672 # For the moment we don't have a path.
1673 self._update_device_name(None)
1674 return
1675 raise_bad_load(e)
1676 except Exception as e:
1677 raise_bad_load(e)
1679 def create(self, sr_uuid, vdi_uuid, size):
1680 # Usage example:
1681 # xe vdi-create sr-uuid=39a5826b-5a90-73eb-dd09-51e3a116f937
1682 # name-label="linstor-vdi-1" virtual-size=4096MiB sm-config:type=vhd
1684 # 1. Check if we are on the master and if the VDI doesn't exist.
1685 util.SMlog('LinstorVDI.create for {}'.format(self.uuid))
1686 if self._exists:
1687 raise xs_errors.XenError('VDIExists')
1689 assert self.uuid
1690 assert self.ty
1691 assert self.vdi_type
1693 # 2. Compute size and check space available.
1694 size = vhdutil.validate_and_round_vhd_size(int(size))
1695 volume_size = compute_volume_size(size, self.vdi_type)
1696 util.SMlog(
1697 'LinstorVDI.create: type={}, vhd-size={}, volume-size={}'
1698 .format(self.vdi_type, size, volume_size)
1699 )
1700 self.sr._ensure_space_available(volume_size)
1702 # 3. Set sm_config attribute of VDI parent class.
1703 self.sm_config = self.sr.srcmd.params['vdi_sm_config']
1705 # 4. Create!
1706 failed = False
1707 try:
1708 volume_name = None
1709 if self.ty == 'ha_statefile':
1710 volume_name = 'xcp-persistent-ha-statefile'
1711 elif self.ty == 'redo_log':
1712 volume_name = 'xcp-persistent-redo-log'
1714 self._linstor.create_volume(
1715 self.uuid, volume_size, persistent=False,
1716 volume_name=volume_name
1717 )
1718 volume_info = self._linstor.get_volume_info(self.uuid)
1720 self._update_device_name(volume_info.name)
1722 if self.vdi_type == vhdutil.VDI_TYPE_RAW:
1723 self.size = volume_info.virtual_size
1724 else:
1725 self.sr._vhdutil.create(
1726 self.path, size, False, self.MAX_METADATA_VIRT_SIZE
1727 )
1728 self.size = self.sr._vhdutil.get_size_virt(self.uuid)
1730 if self._key_hash:
1731 self.sr._vhdutil.set_key(self.path, self._key_hash)
1733 # Because vhdutil commands modify the volume data,
1734 # we must retrieve a new time the utilization size.
1735 volume_info = self._linstor.get_volume_info(self.uuid)
1737 volume_metadata = {
1738 NAME_LABEL_TAG: util.to_plain_string(self.label),
1739 NAME_DESCRIPTION_TAG: util.to_plain_string(self.description),
1740 IS_A_SNAPSHOT_TAG: False,
1741 SNAPSHOT_OF_TAG: '',
1742 SNAPSHOT_TIME_TAG: '',
1743 TYPE_TAG: self.ty,
1744 VDI_TYPE_TAG: self.vdi_type,
1745 READ_ONLY_TAG: bool(self.read_only),
1746 METADATA_OF_POOL_TAG: ''
1747 }
1748 self._linstor.set_volume_metadata(self.uuid, volume_metadata)
1750 # Set the open timeout to 1min to reduce CPU usage
1751 # in http-disk-server when a secondary server tries to open
1752 # an already opened volume.
1753 if self.ty == 'ha_statefile' or self.ty == 'redo_log':
1754 self._linstor.set_auto_promote_timeout(self.uuid, 600)
1756 self._linstor.mark_volume_as_persistent(self.uuid)
1757 except util.CommandException as e:
1758 failed = True
1759 raise xs_errors.XenError(
1760 'VDICreate', opterr='error {}'.format(e.code)
1761 )
1762 except Exception as e:
1763 failed = True
1764 raise xs_errors.XenError('VDICreate', opterr='error {}'.format(e))
1765 finally:
1766 if failed:
1767 util.SMlog('Unable to create VDI {}'.format(self.uuid))
1768 try:
1769 self._linstor.destroy_volume(self.uuid)
1770 except Exception as e:
1771 util.SMlog(
1772 'Ignoring exception after fail in LinstorVDI.create: '
1773 '{}'.format(e)
1774 )
1776 self.utilisation = volume_info.allocated_size
1777 self.sm_config['vdi_type'] = self.vdi_type
1779 self.ref = self._db_introduce()
1780 self.sr._update_stats(self.size)
1782 return VDI.VDI.get_params(self)
1784 def delete(self, sr_uuid, vdi_uuid, data_only=False):
1785 util.SMlog('LinstorVDI.delete for {}'.format(self.uuid))
1786 if self.attached:
1787 raise xs_errors.XenError('VDIInUse')
1789 if self.deleted:
1790 return super(LinstorVDI, self).delete(
1791 sr_uuid, vdi_uuid, data_only
1792 )
1794 vdi_ref = self.sr.srcmd.params['vdi_ref']
1795 if not self.session.xenapi.VDI.get_managed(vdi_ref):
1796 raise xs_errors.XenError(
1797 'VDIDelete',
1798 opterr='Deleting non-leaf node not permitted'
1799 )
1801 try:
1802 # Remove from XAPI and delete from LINSTOR.
1803 self._linstor.destroy_volume(self.uuid)
1804 if not data_only:
1805 self._db_forget()
1807 self.sr.lock.cleanupAll(vdi_uuid)
1808 except Exception as e:
1809 util.SMlog(
1810 'Failed to remove the volume (maybe is leaf coalescing) '
1811 'for {} err: {}'.format(self.uuid, e)
1812 )
1813 raise xs_errors.XenError('VDIDelete', opterr=str(e))
1815 if self.uuid in self.sr.vdis:
1816 del self.sr.vdis[self.uuid]
1818 # TODO: Check size after delete.
1819 self.sr._update_stats(-self.size)
1820 self.sr._kick_gc()
1821 return super(LinstorVDI, self).delete(sr_uuid, vdi_uuid, data_only)
1823 def attach(self, sr_uuid, vdi_uuid):
1824 util.SMlog('LinstorVDI.attach for {}'.format(self.uuid))
1825 attach_from_config = self.sr.srcmd.cmd == 'vdi_attach_from_config'
1826 if (
1827 not attach_from_config or
1828 self.sr.srcmd.params['vdi_uuid'] != self.uuid
1829 ) and self.sr._journaler.has_entries(self.uuid):
1830 raise xs_errors.XenError(
1831 'VDIUnavailable',
1832 opterr='Interrupted operation detected on this VDI, '
1833 'scan SR first to trigger auto-repair'
1834 )
1836 if not attach_from_config or self.sr._is_master:
1837 writable = 'args' not in self.sr.srcmd.params or \
1838 self.sr.srcmd.params['args'][0] == 'true'
1840 # We need to inflate the volume if we don't have enough place
1841 # to mount the VHD image. I.e. the volume capacity must be greater
1842 # than the VHD size + bitmap size.
1843 need_inflate = True
1844 if (
1845 self.vdi_type == vhdutil.VDI_TYPE_RAW or
1846 not writable or
1847 self.capacity >= compute_volume_size(self.size, self.vdi_type)
1848 ):
1849 need_inflate = False
1851 if need_inflate:
1852 try:
1853 self._prepare_thin(True)
1854 except Exception as e:
1855 raise xs_errors.XenError(
1856 'VDIUnavailable',
1857 opterr='Failed to attach VDI during "prepare thin": {}'
1858 .format(e)
1859 )
1861 if not hasattr(self, 'xenstore_data'):
1862 self.xenstore_data = {}
1863 self.xenstore_data['storage-type'] = LinstorSR.DRIVER_TYPE
1865 if (
1866 USE_HTTP_NBD_SERVERS and
1867 attach_from_config and
1868 self.path.startswith('/dev/http-nbd/')
1869 ):
1870 return self._attach_using_http_nbd()
1872 # Ensure we have a path...
1873 while vdi_uuid:
1874 path = self._linstor.get_device_path(vdi_uuid)
1875 if not util.pathexists(path):
1876 raise xs_errors.XenError(
1877 'VDIUnavailable', opterr='Could not find: {}'.format(path)
1878 )
1879 vdi_uuid = self.sr._vhdutil.get_vhd_info(vdi_uuid).parentUuid
1881 self.attached = True
1882 return VDI.VDI.attach(self, self.sr.uuid, self.uuid)
1884 def detach(self, sr_uuid, vdi_uuid):
1885 util.SMlog('LinstorVDI.detach for {}'.format(self.uuid))
1886 detach_from_config = self.sr.srcmd.cmd == 'vdi_detach_from_config'
1887 self.attached = False
1889 if detach_from_config and self.path.startswith('/dev/http-nbd/'):
1890 return self._detach_using_http_nbd()
1892 if self.vdi_type == vhdutil.VDI_TYPE_RAW:
1893 return
1895 # The VDI is already deflated if the VHD image size + metadata is
1896 # equal to the LINSTOR volume size.
1897 volume_size = compute_volume_size(self.size, self.vdi_type)
1898 already_deflated = self.capacity <= volume_size
1900 if already_deflated:
1901 util.SMlog(
1902 'VDI {} already deflated (old volume size={}, volume size={})'
1903 .format(self.uuid, self.capacity, volume_size)
1904 )
1906 need_deflate = True
1907 if already_deflated:
1908 need_deflate = False
1909 elif self.sr._provisioning == 'thick':
1910 need_deflate = False
1912 vdi_ref = self.sr.srcmd.params['vdi_ref']
1913 if self.session.xenapi.VDI.get_is_a_snapshot(vdi_ref):
1914 need_deflate = True
1916 if need_deflate:
1917 try:
1918 self._prepare_thin(False)
1919 except Exception as e:
1920 raise xs_errors.XenError(
1921 'VDIUnavailable',
1922 opterr='Failed to detach VDI during "prepare thin": {}'
1923 .format(e)
1924 )
1926 def resize(self, sr_uuid, vdi_uuid, size):
1927 util.SMlog('LinstorVDI.resize for {}'.format(self.uuid))
1928 if not self.sr._is_master:
1929 raise xs_errors.XenError(
1930 'VDISize',
1931 opterr='resize on slave not allowed'
1932 )
1934 if self.hidden:
1935 raise xs_errors.XenError('VDIUnavailable', opterr='hidden VDI')
1937 # Compute the virtual VHD and DRBD volume size.
1938 size = vhdutil.validate_and_round_vhd_size(int(size))
1939 volume_size = compute_volume_size(size, self.vdi_type)
1940 util.SMlog(
1941 'LinstorVDI.resize: type={}, vhd-size={}, volume-size={}'
1942 .format(self.vdi_type, size, volume_size)
1943 )
1945 if size < self.size:
1946 util.SMlog(
1947 'vdi_resize: shrinking not supported: '
1948 '(current size: {}, new size: {})'.format(self.size, size)
1949 )
1950 raise xs_errors.XenError('VDISize', opterr='shrinking not allowed')
1952 if size == self.size:
1953 return VDI.VDI.get_params(self)
1955 if self.vdi_type == vhdutil.VDI_TYPE_RAW:
1956 old_volume_size = self.size
1957 else:
1958 old_volume_size = self.utilisation
1959 if self.sr._provisioning == 'thin':
1960 # VDI is currently deflated, so keep it deflated.
1961 new_volume_size = old_volume_size
1962 assert new_volume_size >= old_volume_size
1964 space_needed = new_volume_size - old_volume_size
1965 self.sr._ensure_space_available(space_needed)
1967 old_size = self.size
1968 if self.vdi_type == vhdutil.VDI_TYPE_RAW:
1969 self._linstor.resize(self.uuid, new_volume_size)
1970 else:
1971 if new_volume_size != old_volume_size:
1972 inflate(
1973 self.sr._journaler, self._linstor, self.uuid, self.path,
1974 new_volume_size, old_volume_size
1975 )
1976 self.sr._vhdutil.set_size_virt_fast(self.path, size)
1978 # Reload size attributes.
1979 self._load_this()
1981 vdi_ref = self.sr.srcmd.params['vdi_ref']
1982 self.session.xenapi.VDI.set_virtual_size(vdi_ref, str(self.size))
1983 self.session.xenapi.VDI.set_physical_utilisation(
1984 vdi_ref, str(self.utilisation)
1985 )
1986 self.sr._update_stats(self.size - old_size)
1987 return VDI.VDI.get_params(self)
1989 def clone(self, sr_uuid, vdi_uuid):
1990 return self._do_snapshot(sr_uuid, vdi_uuid, VDI.SNAPSHOT_DOUBLE)
1992 def compose(self, sr_uuid, vdi1, vdi2):
1993 util.SMlog('VDI.compose for {} -> {}'.format(vdi2, vdi1))
1994 if self.vdi_type != vhdutil.VDI_TYPE_VHD:
1995 raise xs_errors.XenError('Unimplemented')
1997 parent_uuid = vdi1
1998 parent_path = self._linstor.get_device_path(parent_uuid)
2000 # We must pause tapdisk to correctly change the parent. Otherwise we
2001 # have a readonly error.
2002 # See: https://github.com/xapi-project/xen-api/blob/b3169a16d36dae0654881b336801910811a399d9/ocaml/xapi/storage_migrate.ml#L928-L929
2003 # and: https://github.com/xapi-project/xen-api/blob/b3169a16d36dae0654881b336801910811a399d9/ocaml/xapi/storage_migrate.ml#L775
2005 if not blktap2.VDI.tap_pause(self.session, self.sr.uuid, self.uuid):
2006 raise util.SMException('Failed to pause VDI {}'.format(self.uuid))
2007 try:
2008 self.sr._vhdutil.set_parent(self.path, parent_path, False)
2009 self.sr._vhdutil.set_hidden(parent_path)
2010 self.sr.session.xenapi.VDI.set_managed(
2011 self.sr.srcmd.params['args'][0], False
2012 )
2013 finally:
2014 blktap2.VDI.tap_unpause(self.session, self.sr.uuid, self.uuid)
2016 if not blktap2.VDI.tap_refresh(self.session, self.sr.uuid, self.uuid):
2017 raise util.SMException(
2018 'Failed to refresh VDI {}'.format(self.uuid)
2019 )
2021 util.SMlog('Compose done')
2023 def generate_config(self, sr_uuid, vdi_uuid):
2024 """
2025 Generate the XML config required to attach and activate
2026 a VDI for use when XAPI is not running. Attach and
2027 activation is handled by vdi_attach_from_config below.
2028 """
2030 util.SMlog('LinstorVDI.generate_config for {}'.format(self.uuid))
2032 resp = {}
2033 resp['device_config'] = self.sr.dconf
2034 resp['sr_uuid'] = sr_uuid
2035 resp['vdi_uuid'] = self.uuid
2036 resp['sr_sm_config'] = self.sr.sm_config
2037 resp['command'] = 'vdi_attach_from_config'
2039 # By default, we generate a normal config.
2040 # But if the disk is persistent, we must use a HTTP/NBD
2041 # server to ensure we can always write or read data.
2042 # Why? DRBD is unsafe when used with more than 4 hosts:
2043 # We are limited to use 1 diskless and 3 full.
2044 # We can't increase this limitation, so we use a NBD/HTTP device
2045 # instead.
2046 volume_name = self._linstor.get_volume_name(self.uuid)
2047 if not USE_HTTP_NBD_SERVERS or volume_name not in [
2048 'xcp-persistent-ha-statefile', 'xcp-persistent-redo-log'
2049 ]:
2050 if not self.path or not util.pathexists(self.path):
2051 available = False
2052 # Try to refresh symlink path...
2053 try:
2054 self.path = self._linstor.get_device_path(vdi_uuid)
2055 available = util.pathexists(self.path)
2056 except Exception:
2057 pass
2058 if not available:
2059 raise xs_errors.XenError('VDIUnavailable')
2061 resp['vdi_path'] = self.path
2062 else:
2063 # Axiom: DRBD device is present on at least one host.
2064 resp['vdi_path'] = '/dev/http-nbd/' + volume_name
2066 config = xmlrpc.client.dumps(tuple([resp]), 'vdi_attach_from_config')
2067 return xmlrpc.client.dumps((config,), "", True)
2069 def attach_from_config(self, sr_uuid, vdi_uuid):
2070 """
2071 Attach and activate a VDI using config generated by
2072 vdi_generate_config above. This is used for cases such as
2073 the HA state-file and the redo-log.
2074 """
2076 util.SMlog('LinstorVDI.attach_from_config for {}'.format(vdi_uuid))
2078 try:
2079 if not util.pathexists(self.sr.path):
2080 self.sr.attach(sr_uuid)
2082 if not DRIVER_CONFIG['ATTACH_FROM_CONFIG_WITH_TAPDISK']:
2083 return self.attach(sr_uuid, vdi_uuid)
2084 except Exception:
2085 util.logException('LinstorVDI.attach_from_config')
2086 raise xs_errors.XenError(
2087 'SRUnavailable',
2088 opterr='Unable to attach from config'
2089 )
2091 def reset_leaf(self, sr_uuid, vdi_uuid):
2092 if self.vdi_type != vhdutil.VDI_TYPE_VHD:
2093 raise xs_errors.XenError('Unimplemented')
2095 if not self.sr._vhdutil.has_parent(self.uuid):
2096 raise util.SMException(
2097 'ERROR: VDI {} has no parent, will not reset contents'
2098 .format(self.uuid)
2099 )
2101 self.sr._vhdutil.kill_data(self.path)
2103 def _load_this(self):
2104 volume_metadata = None
2105 if self.sr._all_volume_metadata_cache:
2106 volume_metadata = self.sr._all_volume_metadata_cache.get(self.uuid)
2107 if volume_metadata is None:
2108 volume_metadata = self._linstor.get_volume_metadata(self.uuid)
2110 volume_info = None
2111 if self.sr._all_volume_info_cache:
2112 volume_info = self.sr._all_volume_info_cache.get(self.uuid)
2113 if volume_info is None:
2114 volume_info = self._linstor.get_volume_info(self.uuid)
2116 # Contains the max physical size used on a disk.
2117 # When LINSTOR LVM driver is used, the size should be similar to
2118 # virtual size (i.e. the LINSTOR max volume size).
2119 # When LINSTOR Thin LVM driver is used, the used physical size should
2120 # be lower than virtual size at creation.
2121 # The physical size increases after each write in a new block.
2122 self.utilisation = volume_info.allocated_size
2123 self.capacity = volume_info.virtual_size
2125 if self.vdi_type == vhdutil.VDI_TYPE_RAW:
2126 self.hidden = int(volume_metadata.get(HIDDEN_TAG) or 0)
2127 self.size = volume_info.virtual_size
2128 self.parent = ''
2129 else:
2130 vhd_info = self.sr._vhdutil.get_vhd_info(self.uuid)
2131 self.hidden = vhd_info.hidden
2132 self.size = vhd_info.sizeVirt
2133 self.parent = vhd_info.parentUuid
2135 if self.hidden:
2136 self.managed = False
2138 self.label = volume_metadata.get(NAME_LABEL_TAG) or ''
2139 self.description = volume_metadata.get(NAME_DESCRIPTION_TAG) or ''
2141 # Update sm_config_override of VDI parent class.
2142 self.sm_config_override = {'vhd-parent': self.parent or None}
2144 def _mark_hidden(self, hidden=True):
2145 if self.hidden == hidden:
2146 return
2148 if self.vdi_type == vhdutil.VDI_TYPE_VHD:
2149 self.sr._vhdutil.set_hidden(self.path, hidden)
2150 else:
2151 self._linstor.update_volume_metadata(self.uuid, {
2152 HIDDEN_TAG: hidden
2153 })
2154 self.hidden = hidden
2156 def update(self, sr_uuid, vdi_uuid):
2157 xenapi = self.session.xenapi
2158 vdi_ref = xenapi.VDI.get_by_uuid(self.uuid)
2160 volume_metadata = {
2161 NAME_LABEL_TAG: util.to_plain_string(
2162 xenapi.VDI.get_name_label(vdi_ref)
2163 ),
2164 NAME_DESCRIPTION_TAG: util.to_plain_string(
2165 xenapi.VDI.get_name_description(vdi_ref)
2166 )
2167 }
2169 try:
2170 self._linstor.update_volume_metadata(self.uuid, volume_metadata)
2171 except LinstorVolumeManagerError as e:
2172 if e.code == LinstorVolumeManagerError.ERR_VOLUME_NOT_EXISTS:
2173 raise xs_errors.XenError(
2174 'VDIUnavailable',
2175 opterr='LINSTOR volume {} not found'.format(self.uuid)
2176 )
2177 raise xs_errors.XenError('VDIUnavailable', opterr=str(e))
2179 # --------------------------------------------------------------------------
2180 # Thin provisioning.
2181 # --------------------------------------------------------------------------
2183 def _prepare_thin(self, attach):
2184 if self.sr._is_master:
2185 if attach:
2186 attach_thin(
2187 self.session, self.sr._journaler, self._linstor,
2188 self.sr.uuid, self.uuid
2189 )
2190 else:
2191 detach_thin(
2192 self.session, self._linstor, self.sr.uuid, self.uuid
2193 )
2194 else:
2195 fn = 'attach' if attach else 'detach'
2197 master = util.get_master_ref(self.session)
2199 args = {
2200 'groupName': self.sr._group_name,
2201 'srUuid': self.sr.uuid,
2202 'vdiUuid': self.uuid
2203 }
2204 self.sr._exec_manager_command(master, fn, args, 'VDIUnavailable')
2206 # Reload size attrs after inflate or deflate!
2207 self._load_this()
2208 self.sr._update_physical_size()
2210 vdi_ref = self.sr.srcmd.params['vdi_ref']
2211 self.session.xenapi.VDI.set_physical_utilisation(
2212 vdi_ref, str(self.utilisation)
2213 )
2215 self.session.xenapi.SR.set_physical_utilisation(
2216 self.sr.sr_ref, str(self.sr.physical_utilisation)
2217 )
2219 # --------------------------------------------------------------------------
2220 # Generic helpers.
2221 # --------------------------------------------------------------------------
2223 def _determine_type_and_path(self):
2224 """
2225 Determine whether this is a RAW or a VHD VDI.
2226 """
2228 # 1. Check vdi_ref and vdi_type in config.
2229 try:
2230 vdi_ref = self.session.xenapi.VDI.get_by_uuid(self.uuid)
2231 if vdi_ref:
2232 sm_config = self.session.xenapi.VDI.get_sm_config(vdi_ref)
2233 vdi_type = sm_config.get('vdi_type')
2234 if vdi_type:
2235 # Update parent fields.
2236 self.vdi_type = vdi_type
2237 self.sm_config_override = sm_config
2238 self._update_device_name(
2239 self._linstor.get_volume_name(self.uuid)
2240 )
2241 return
2242 except Exception:
2243 pass
2245 # 2. Otherwise use the LINSTOR volume manager directly.
2246 # It's probably a new VDI created via snapshot.
2247 volume_metadata = self._linstor.get_volume_metadata(self.uuid)
2248 self.vdi_type = volume_metadata.get(VDI_TYPE_TAG)
2249 if not self.vdi_type:
2250 raise xs_errors.XenError(
2251 'VDIUnavailable',
2252 opterr='failed to get vdi_type in metadata'
2253 )
2254 self._update_device_name(self._linstor.get_volume_name(self.uuid))
2256 def _update_device_name(self, device_name):
2257 self._device_name = device_name
2259 # Mark path of VDI parent class.
2260 if device_name:
2261 self.path = self._linstor.build_device_path(self._device_name)
2262 else:
2263 self.path = None
2265 def _create_snapshot(self, snap_uuid, snap_of_uuid=None):
2266 """
2267 Snapshot self and return the snapshot VDI object.
2268 """
2270 # 1. Create a new LINSTOR volume with the same size than self.
2271 snap_path = self._linstor.shallow_clone_volume(
2272 self.uuid, snap_uuid, persistent=False
2273 )
2275 # 2. Write the snapshot content.
2276 is_raw = (self.vdi_type == vhdutil.VDI_TYPE_RAW)
2277 self.sr._vhdutil.snapshot(
2278 snap_path, self.path, is_raw, self.MAX_METADATA_VIRT_SIZE
2279 )
2281 # 3. Get snapshot parent.
2282 snap_parent = self.sr._vhdutil.get_parent(snap_uuid)
2284 # 4. Update metadata.
2285 util.SMlog('Set VDI {} metadata of snapshot'.format(snap_uuid))
2286 volume_metadata = {
2287 NAME_LABEL_TAG: util.to_plain_string(self.label),
2288 NAME_DESCRIPTION_TAG: util.to_plain_string(self.description),
2289 IS_A_SNAPSHOT_TAG: bool(snap_of_uuid),
2290 SNAPSHOT_OF_TAG: snap_of_uuid,
2291 SNAPSHOT_TIME_TAG: '',
2292 TYPE_TAG: self.ty,
2293 VDI_TYPE_TAG: vhdutil.VDI_TYPE_VHD,
2294 READ_ONLY_TAG: False,
2295 METADATA_OF_POOL_TAG: ''
2296 }
2297 self._linstor.set_volume_metadata(snap_uuid, volume_metadata)
2299 # 5. Set size.
2300 snap_vdi = LinstorVDI(self.sr, snap_uuid)
2301 if not snap_vdi._exists:
2302 raise xs_errors.XenError('VDISnapshot')
2304 volume_info = self._linstor.get_volume_info(snap_uuid)
2306 snap_vdi.size = self.sr._vhdutil.get_size_virt(snap_uuid)
2307 snap_vdi.utilisation = volume_info.allocated_size
2309 # 6. Update sm config.
2310 snap_vdi.sm_config = {}
2311 snap_vdi.sm_config['vdi_type'] = snap_vdi.vdi_type
2312 if snap_parent:
2313 snap_vdi.sm_config['vhd-parent'] = snap_parent
2314 snap_vdi.parent = snap_parent
2316 snap_vdi.label = self.label
2317 snap_vdi.description = self.description
2319 self._linstor.mark_volume_as_persistent(snap_uuid)
2321 return snap_vdi
2323 # --------------------------------------------------------------------------
2324 # Implement specific SR methods.
2325 # --------------------------------------------------------------------------
2327 def _rename(self, oldpath, newpath):
2328 # TODO: I'm not sure... Used by CBT.
2329 volume_uuid = self._linstor.get_volume_uuid_from_device_path(oldpath)
2330 self._linstor.update_volume_name(volume_uuid, newpath)
2332 def _do_snapshot(
2333 self, sr_uuid, vdi_uuid, snap_type, secondary=None, cbtlog=None
2334 ):
2335 # If cbt enabled, save file consistency state.
2336 if cbtlog is not None:
2337 if blktap2.VDI.tap_status(self.session, vdi_uuid):
2338 consistency_state = False
2339 else:
2340 consistency_state = True
2341 util.SMlog(
2342 'Saving log consistency state of {} for vdi: {}'
2343 .format(consistency_state, vdi_uuid)
2344 )
2345 else:
2346 consistency_state = None
2348 if self.vdi_type != vhdutil.VDI_TYPE_VHD:
2349 raise xs_errors.XenError('Unimplemented')
2351 if not blktap2.VDI.tap_pause(self.session, sr_uuid, vdi_uuid):
2352 raise util.SMException('Failed to pause VDI {}'.format(vdi_uuid))
2353 try:
2354 return self._snapshot(snap_type, cbtlog, consistency_state)
2355 finally:
2356 blktap2.VDI.tap_unpause(self.session, sr_uuid, vdi_uuid, secondary)
2358 def _snapshot(self, snap_type, cbtlog=None, cbt_consistency=None):
2359 util.SMlog(
2360 'LinstorVDI._snapshot for {} (type {})'
2361 .format(self.uuid, snap_type)
2362 )
2364 # 1. Checks...
2365 if self.hidden:
2366 raise xs_errors.XenError('VDIClone', opterr='hidden VDI')
2368 depth = self.sr._vhdutil.get_depth(self.uuid)
2369 if depth == -1:
2370 raise xs_errors.XenError(
2371 'VDIUnavailable',
2372 opterr='failed to get VHD depth'
2373 )
2374 elif depth >= vhdutil.MAX_CHAIN_SIZE:
2375 raise xs_errors.XenError('SnapshotChainTooLong')
2377 # Ensure we have a valid path if we don't have a local diskful.
2378 self.sr._linstor.get_device_path(self.uuid)
2380 volume_path = self.path
2381 if not util.pathexists(volume_path):
2382 raise xs_errors.XenError(
2383 'EIO',
2384 opterr='IO error checking path {}'.format(volume_path)
2385 )
2387 # 2. Create base and snap uuid (if required) and a journal entry.
2388 base_uuid = util.gen_uuid()
2389 snap_uuid = None
2391 if snap_type == VDI.SNAPSHOT_DOUBLE:
2392 snap_uuid = util.gen_uuid()
2394 clone_info = '{}_{}'.format(base_uuid, snap_uuid)
2396 active_uuid = self.uuid
2397 self.sr._journaler.create(
2398 LinstorJournaler.CLONE, active_uuid, clone_info
2399 )
2401 try:
2402 # 3. Self becomes the new base.
2403 # The device path remains the same.
2404 self._linstor.update_volume_uuid(self.uuid, base_uuid)
2405 self.uuid = base_uuid
2406 self.location = self.uuid
2407 self.read_only = True
2408 self.managed = False
2410 # 4. Create snapshots (new active and snap).
2411 active_vdi = self._create_snapshot(active_uuid)
2413 snap_vdi = None
2414 if snap_type == VDI.SNAPSHOT_DOUBLE:
2415 snap_vdi = self._create_snapshot(snap_uuid, active_uuid)
2417 self.label = 'base copy'
2418 self.description = ''
2420 # 5. Mark the base VDI as hidden so that it does not show up
2421 # in subsequent scans.
2422 self._mark_hidden()
2423 self._linstor.update_volume_metadata(
2424 self.uuid, {READ_ONLY_TAG: True}
2425 )
2427 # 6. We must update the new active VDI with the "paused" and
2428 # "host_" properties. Why? Because the original VDI has been
2429 # paused and we we must unpause it after the snapshot.
2430 # See: `tap_unpause` in `blktap2.py`.
2431 vdi_ref = self.session.xenapi.VDI.get_by_uuid(active_uuid)
2432 sm_config = self.session.xenapi.VDI.get_sm_config(vdi_ref)
2433 for key in [x for x in sm_config.keys() if x == 'paused' or x.startswith('host_')]:
2434 active_vdi.sm_config[key] = sm_config[key]
2436 # 7. Verify parent locator field of both children and
2437 # delete base if unused.
2438 introduce_parent = True
2439 try:
2440 snap_parent = None
2441 if snap_vdi:
2442 snap_parent = snap_vdi.parent
2444 if active_vdi.parent != self.uuid and (
2445 snap_type == VDI.SNAPSHOT_SINGLE or
2446 snap_type == VDI.SNAPSHOT_INTERNAL or
2447 snap_parent != self.uuid
2448 ):
2449 util.SMlog(
2450 'Destroy unused base volume: {} (path={})'
2451 .format(self.uuid, self.path)
2452 )
2453 introduce_parent = False
2454 self._linstor.destroy_volume(self.uuid)
2455 except Exception as e:
2456 util.SMlog('Ignoring exception: {}'.format(e))
2457 pass
2459 # 8. Introduce the new VDI records.
2460 if snap_vdi:
2461 # If the parent is encrypted set the key_hash for the
2462 # new snapshot disk.
2463 vdi_ref = self.sr.srcmd.params['vdi_ref']
2464 sm_config = self.session.xenapi.VDI.get_sm_config(vdi_ref)
2465 # TODO: Maybe remove key_hash support.
2466 if 'key_hash' in sm_config:
2467 snap_vdi.sm_config['key_hash'] = sm_config['key_hash']
2468 # If we have CBT enabled on the VDI,
2469 # set CBT status for the new snapshot disk.
2470 if cbtlog:
2471 snap_vdi.cbt_enabled = True
2473 if snap_vdi:
2474 snap_vdi_ref = snap_vdi._db_introduce()
2475 util.SMlog(
2476 'vdi_clone: introduced VDI: {} ({})'
2477 .format(snap_vdi_ref, snap_vdi.uuid)
2478 )
2479 if introduce_parent:
2480 base_vdi_ref = self._db_introduce()
2481 self.session.xenapi.VDI.set_managed(base_vdi_ref, False)
2482 util.SMlog(
2483 'vdi_clone: introduced VDI: {} ({})'
2484 .format(base_vdi_ref, self.uuid)
2485 )
2486 self._linstor.update_volume_metadata(self.uuid, {
2487 NAME_LABEL_TAG: util.to_plain_string(self.label),
2488 NAME_DESCRIPTION_TAG: util.to_plain_string(
2489 self.description
2490 ),
2491 READ_ONLY_TAG: True,
2492 METADATA_OF_POOL_TAG: ''
2493 })
2495 # 9. Update cbt files if user created snapshot (SNAPSHOT_DOUBLE)
2496 if snap_type == VDI.SNAPSHOT_DOUBLE and cbtlog:
2497 try:
2498 self._cbt_snapshot(snap_uuid, cbt_consistency)
2499 except Exception:
2500 # CBT operation failed.
2501 # TODO: Implement me.
2502 raise
2504 if snap_type != VDI.SNAPSHOT_INTERNAL:
2505 self.sr._update_stats(self.size)
2507 # 10. Return info on the new user-visible leaf VDI.
2508 ret_vdi = snap_vdi
2509 if not ret_vdi:
2510 ret_vdi = self
2511 if not ret_vdi:
2512 ret_vdi = active_vdi
2514 vdi_ref = self.sr.srcmd.params['vdi_ref']
2515 self.session.xenapi.VDI.set_sm_config(
2516 vdi_ref, active_vdi.sm_config
2517 )
2518 except Exception as e:
2519 util.logException('Failed to snapshot!')
2520 try:
2521 self.sr._handle_interrupted_clone(
2522 active_uuid, clone_info, force_undo=True
2523 )
2524 self.sr._journaler.remove(LinstorJournaler.CLONE, active_uuid)
2525 except Exception as e:
2526 util.SMlog(
2527 'WARNING: Failed to clean up failed snapshot: {}'
2528 .format(e)
2529 )
2530 raise xs_errors.XenError('VDIClone', opterr=str(e))
2532 self.sr._journaler.remove(LinstorJournaler.CLONE, active_uuid)
2534 return ret_vdi.get_params()
2536 @staticmethod
2537 def _start_persistent_http_server(volume_name):
2538 pid_path = None
2539 http_server = None
2541 try:
2542 if volume_name == 'xcp-persistent-ha-statefile':
2543 port = '8076'
2544 else:
2545 port = '8077'
2547 try:
2548 # Use a timeout call because XAPI may be unusable on startup
2549 # or if the host has been ejected. So in this case the call can
2550 # block indefinitely.
2551 session = util.timeout_call(5, util.get_localAPI_session)
2552 host_ip = util.get_this_host_address(session)
2553 except:
2554 # Fallback using the XHA file if session not available.
2555 host_ip, _ = get_ips_from_xha_config_file()
2556 if not host_ip:
2557 raise Exception(
2558 'Cannot start persistent HTTP server: no XAPI session, nor XHA config file'
2559 )
2561 arguments = [
2562 'http-disk-server',
2563 '--disk',
2564 '/dev/drbd/by-res/{}/0'.format(volume_name),
2565 '--ip',
2566 host_ip,
2567 '--port',
2568 port
2569 ]
2571 util.SMlog('Starting {} on port {}...'.format(arguments[0], port))
2572 http_server = subprocess.Popen(
2573 [FORK_LOG_DAEMON] + arguments,
2574 stdout=subprocess.PIPE,
2575 stderr=subprocess.STDOUT,
2576 # Ensure we use another group id to kill this process without
2577 # touch the current one.
2578 preexec_fn=os.setsid
2579 )
2581 pid_path = '/run/http-server-{}.pid'.format(volume_name)
2582 with open(pid_path, 'w') as pid_file:
2583 pid_file.write(str(http_server.pid))
2585 reg_server_ready = re.compile("Server ready!$")
2586 def is_ready():
2587 while http_server.poll() is None:
2588 line = http_server.stdout.readline()
2589 if reg_server_ready.search(line):
2590 return True
2591 return False
2592 try:
2593 if not util.timeout_call(10, is_ready):
2594 raise Exception('Failed to wait HTTP server startup, bad output')
2595 except util.TimeoutException:
2596 raise Exception('Failed to wait for HTTP server startup during given delay')
2597 except Exception as e:
2598 if pid_path:
2599 try:
2600 os.remove(pid_path)
2601 except Exception:
2602 pass
2604 if http_server:
2605 # Kill process and children in this case...
2606 try:
2607 os.killpg(os.getpgid(http_server.pid), signal.SIGTERM)
2608 except:
2609 pass
2611 raise xs_errors.XenError(
2612 'VDIUnavailable',
2613 opterr='Failed to start http-server: {}'.format(e)
2614 )
2616 def _start_persistent_nbd_server(self, volume_name):
2617 pid_path = None
2618 nbd_path = None
2619 nbd_server = None
2621 try:
2622 # We use a precomputed device size.
2623 # So if the XAPI is modified, we must update these values!
2624 if volume_name == 'xcp-persistent-ha-statefile':
2625 # See: https://github.com/xapi-project/xen-api/blob/703479fa448a8d7141954bb6e8964d8e25c4ac2e/ocaml/xapi/xha_statefile.ml#L32-L37
2626 port = '8076'
2627 device_size = 4 * 1024 * 1024
2628 else:
2629 # See: https://github.com/xapi-project/xen-api/blob/703479fa448a8d7141954bb6e8964d8e25c4ac2e/ocaml/database/redo_log.ml#L41-L44
2630 port = '8077'
2631 device_size = 256 * 1024 * 1024
2633 try:
2634 session = util.timeout_call(5, util.get_localAPI_session)
2635 ips = util.get_host_addresses(session)
2636 except Exception as e:
2637 _, ips = get_ips_from_xha_config_file()
2638 if not ips:
2639 raise Exception(
2640 'Cannot start persistent NBD server: no XAPI session, nor XHA config file ({})'.format(e)
2641 )
2642 ips = ips.values()
2644 arguments = [
2645 'nbd-http-server',
2646 '--socket-path',
2647 '/run/{}.socket'.format(volume_name),
2648 '--nbd-name',
2649 volume_name,
2650 '--urls',
2651 ','.join(['http://' + ip + ':' + port for ip in ips]),
2652 '--device-size',
2653 str(device_size)
2654 ]
2656 util.SMlog('Starting {} using port {}...'.format(arguments[0], port))
2657 nbd_server = subprocess.Popen(
2658 [FORK_LOG_DAEMON] + arguments,
2659 stdout=subprocess.PIPE,
2660 stderr=subprocess.STDOUT,
2661 # Ensure we use another group id to kill this process without
2662 # touch the current one.
2663 preexec_fn=os.setsid
2664 )
2666 pid_path = '/run/nbd-server-{}.pid'.format(volume_name)
2667 with open(pid_path, 'w') as pid_file:
2668 pid_file.write(str(nbd_server.pid))
2670 reg_nbd_path = re.compile("NBD `(/dev/nbd[0-9]+)` is now attached.$")
2671 def get_nbd_path():
2672 while nbd_server.poll() is None:
2673 line = nbd_server.stdout.readline()
2674 match = reg_nbd_path.search(line)
2675 if match:
2676 return match.group(1)
2677 # Use a timeout to never block the smapi if there is a problem.
2678 try:
2679 nbd_path = util.timeout_call(10, get_nbd_path)
2680 if nbd_path is None:
2681 raise Exception('Empty NBD path (NBD server is probably dead)')
2682 except util.TimeoutException:
2683 raise Exception('Unable to read NBD path')
2685 util.SMlog('Create symlink: {} -> {}'.format(self.path, nbd_path))
2686 os.symlink(nbd_path, self.path)
2687 except Exception as e:
2688 if pid_path:
2689 try:
2690 os.remove(pid_path)
2691 except Exception:
2692 pass
2694 if nbd_path:
2695 try:
2696 os.remove(nbd_path)
2697 except Exception:
2698 pass
2700 if nbd_server:
2701 # Kill process and children in this case...
2702 try:
2703 os.killpg(os.getpgid(nbd_server.pid), signal.SIGTERM)
2704 except:
2705 pass
2707 raise xs_errors.XenError(
2708 'VDIUnavailable',
2709 opterr='Failed to start nbd-server: {}'.format(e)
2710 )
2712 @classmethod
2713 def _kill_persistent_server(self, type, volume_name, sig):
2714 try:
2715 path = '/run/{}-server-{}.pid'.format(type, volume_name)
2716 if not os.path.exists(path):
2717 return
2719 pid = None
2720 with open(path, 'r') as pid_file:
2721 try:
2722 pid = int(pid_file.read())
2723 except Exception:
2724 pass
2726 if pid is not None and util.check_pid_exists(pid):
2727 util.SMlog('Kill {} server {} (pid={})'.format(type, path, pid))
2728 try:
2729 os.killpg(os.getpgid(pid), sig)
2730 except Exception as e:
2731 util.SMlog('Failed to kill {} server: {}'.format(type, e))
2733 os.remove(path)
2734 except:
2735 pass
2737 @classmethod
2738 def _kill_persistent_http_server(self, volume_name, sig=signal.SIGTERM):
2739 return self._kill_persistent_server('nbd', volume_name, sig)
2741 @classmethod
2742 def _kill_persistent_nbd_server(self, volume_name, sig=signal.SIGTERM):
2743 return self._kill_persistent_server('http', volume_name, sig)
2745 def _check_http_nbd_volume_name(self):
2746 volume_name = self.path[14:]
2747 if volume_name not in [
2748 'xcp-persistent-ha-statefile', 'xcp-persistent-redo-log'
2749 ]:
2750 raise xs_errors.XenError(
2751 'VDIUnavailable',
2752 opterr='Unsupported path: {}'.format(self.path)
2753 )
2754 return volume_name
2756 def _attach_using_http_nbd(self):
2757 volume_name = self._check_http_nbd_volume_name()
2759 # Ensure there is no NBD and HTTP server running.
2760 self._kill_persistent_nbd_server(volume_name)
2761 self._kill_persistent_http_server(volume_name)
2763 # 0. Fetch drbd path.
2764 must_get_device_path = True
2765 if not self.sr._is_master:
2766 # We are on a slave, we must try to find a diskful locally.
2767 try:
2768 volume_info = self._linstor.get_volume_info(self.uuid)
2769 except Exception as e:
2770 raise xs_errors.XenError(
2771 'VDIUnavailable',
2772 opterr='Cannot get volume info of {}: {}'
2773 .format(self.uuid, e)
2774 )
2776 hostname = socket.gethostname()
2777 must_get_device_path = hostname in volume_info.diskful
2779 drbd_path = None
2780 if must_get_device_path or self.sr._is_master:
2781 # If we are master, we must ensure we have a diskless
2782 # or diskful available to init HA.
2783 # It also avoid this error in xensource.log
2784 # (/usr/libexec/xapi/cluster-stack/xhad/ha_set_pool_state):
2785 # init exited with code 8 [stdout = ''; stderr = 'SF: failed to write in State-File \x10 (fd 4208696). (sys 28)\x0A']
2786 # init returned MTC_EXIT_CAN_NOT_ACCESS_STATEFILE (State-File is inaccessible)
2787 available = False
2788 try:
2789 drbd_path = self._linstor.get_device_path(self.uuid)
2790 available = util.pathexists(drbd_path)
2791 except Exception:
2792 pass
2794 if not available:
2795 raise xs_errors.XenError(
2796 'VDIUnavailable',
2797 opterr='Cannot get device path of {}'.format(self.uuid)
2798 )
2800 # 1. Prepare http-nbd folder.
2801 try:
2802 if not os.path.exists('/dev/http-nbd/'):
2803 os.makedirs('/dev/http-nbd/')
2804 elif os.path.islink(self.path):
2805 os.remove(self.path)
2806 except OSError as e:
2807 if e.errno != errno.EEXIST:
2808 raise xs_errors.XenError(
2809 'VDIUnavailable',
2810 opterr='Cannot prepare http-nbd: {}'.format(e)
2811 )
2813 # 2. Start HTTP service if we have a diskful or if we are master.
2814 http_service = None
2815 if drbd_path:
2816 assert(drbd_path in (
2817 '/dev/drbd/by-res/xcp-persistent-ha-statefile/0',
2818 '/dev/drbd/by-res/xcp-persistent-redo-log/0'
2819 ))
2820 self._start_persistent_http_server(volume_name)
2822 # 3. Start NBD server in all cases.
2823 try:
2824 self._start_persistent_nbd_server(volume_name)
2825 except Exception as e:
2826 if drbd_path:
2827 self._kill_persistent_http_server(volume_name)
2828 raise
2830 self.attached = True
2831 return VDI.VDI.attach(self, self.sr.uuid, self.uuid)
2833 def _detach_using_http_nbd(self):
2834 volume_name = self._check_http_nbd_volume_name()
2835 self._kill_persistent_nbd_server(volume_name)
2836 self._kill_persistent_http_server(volume_name)
2838# ------------------------------------------------------------------------------
2841if __name__ == '__main__':
2842 def run():
2843 SRCommand.run(LinstorSR, DRIVER_INFO)
2845 if not TRACE_PERFS:
2846 run()
2847 else:
2848 util.make_profile('LinstorSR', run)
2849else:
2850 SR.registerSR(LinstorSR)