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
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 == 'sr_attach':
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'
573 )
575 if srs:
576 raise xs_errors.XenError(
577 'LinstorSRCreate',
578 opterr='LINSTOR SR must be unique in a pool'
579 )
581 online_hosts = util.get_online_hosts(self.session)
582 if len(online_hosts) < len(host_adresses):
583 raise xs_errors.XenError(
584 'LinstorSRCreate',
585 opterr='Not enough online hosts'
586 )
588 ips = {}
589 for host_ref in online_hosts:
590 record = self.session.xenapi.host.get_record(host_ref)
591 hostname = record['hostname']
592 ips[hostname] = record['address']
594 if len(ips) != len(online_hosts):
595 raise xs_errors.XenError(
596 'LinstorSRCreate',
597 opterr='Multiple hosts with same hostname'
598 )
600 # Ensure ports are opened and LINSTOR satellites
601 # are activated. In the same time the drbd-reactor instances
602 # must be stopped.
603 self._prepare_sr_on_all_hosts(self._group_name, enabled=True)
605 # Create SR.
606 # Throw if the SR already exists.
607 try:
608 self._linstor = LinstorVolumeManager.create_sr(
609 self._group_name,
610 ips,
611 self._redundancy,
612 thin_provisioning=self._provisioning == 'thin',
613 auto_quorum=self._monitor_db_quorum,
614 logger=util.SMlog
615 )
616 self._vhdutil = LinstorVhdUtil(self.session, self._linstor)
617 except Exception as e:
618 util.SMlog('Failed to create LINSTOR SR: {}'.format(e))
619 raise xs_errors.XenError('LinstorSRCreate', opterr=str(e))
621 try:
622 util.SMlog(
623 "Finishing SR creation, enable drbd-reactor on all hosts..."
624 )
625 self._update_drbd_reactor_on_all_hosts(enabled=True)
626 except Exception as e:
627 try:
628 self._linstor.destroy()
629 except Exception as e2:
630 util.SMlog(
631 'Failed to destroy LINSTOR SR after creation fail: {}'
632 .format(e2)
633 )
634 raise e
636 @_locked_load
637 def delete(self, uuid):
638 util.SMlog('LinstorSR.delete for {}'.format(self.uuid))
639 cleanup.gc_force(self.session, self.uuid)
641 if self.vdis or self._linstor._volumes:
642 raise xs_errors.XenError('SRNotEmpty')
644 node_name = get_controller_node_name()
645 if not node_name:
646 raise xs_errors.XenError(
647 'LinstorSRDelete',
648 opterr='Cannot get controller node name'
649 )
651 host = None
652 if node_name == 'localhost':
653 host = util.get_this_host_ref(self.session)
654 else:
655 for slave in util.get_all_slaves(self.session):
656 r_name = self.session.xenapi.host.get_record(slave)['hostname']
657 if r_name == node_name:
658 host = slave
659 break
661 if not host:
662 raise xs_errors.XenError(
663 'LinstorSRDelete',
664 opterr='Failed to find host with hostname: {}'.format(
665 node_name
666 )
667 )
669 try:
670 self._update_drbd_reactor_on_all_hosts(
671 controller_node_name=node_name, enabled=False
672 )
674 args = {
675 'groupName': self._group_name,
676 }
677 self._exec_manager_command(
678 host, 'destroy', args, 'LinstorSRDelete'
679 )
680 except Exception as e:
681 try:
682 self._update_drbd_reactor_on_all_hosts(
683 controller_node_name=node_name, enabled=True
684 )
685 except Exception as e2:
686 util.SMlog(
687 'Failed to restart drbd-reactor after destroy fail: {}'
688 .format(e2)
689 )
690 util.SMlog('Failed to delete LINSTOR SR: {}'.format(e))
691 raise xs_errors.XenError(
692 'LinstorSRDelete',
693 opterr=str(e)
694 )
696 Lock.cleanupAll(self.uuid)
698 @_locked_load
699 def update(self, uuid):
700 util.SMlog('LinstorSR.update for {}'.format(self.uuid))
702 # Well, how can we update a SR if it doesn't exist? :thinking:
703 if not self._linstor:
704 raise xs_errors.XenError(
705 'SRUnavailable',
706 opterr='no such volume group: {}'.format(self._group_name)
707 )
709 self._update_stats(0)
711 # Update the SR name and description only in LINSTOR metadata.
712 xenapi = self.session.xenapi
713 self._linstor.metadata = {
714 NAME_LABEL_TAG: util.to_plain_string(
715 xenapi.SR.get_name_label(self.sr_ref)
716 ),
717 NAME_DESCRIPTION_TAG: util.to_plain_string(
718 xenapi.SR.get_name_description(self.sr_ref)
719 )
720 }
722 @_locked_load
723 def attach(self, uuid):
724 util.SMlog('LinstorSR.attach for {}'.format(self.uuid))
726 if not self._linstor:
727 raise xs_errors.XenError(
728 'SRUnavailable',
729 opterr='no such group: {}'.format(self._group_name)
730 )
732 @_locked_load
733 def detach(self, uuid):
734 util.SMlog('LinstorSR.detach for {}'.format(self.uuid))
735 cleanup.abort(self.uuid)
737 @_locked_load
738 def probe(self):
739 util.SMlog('LinstorSR.probe for {}'.format(self.uuid))
740 # TODO
742 @_locked_load
743 def scan(self, uuid):
744 if self._init_status == self.INIT_STATUS_FAIL:
745 return
747 util.SMlog('LinstorSR.scan for {}'.format(self.uuid))
748 if not self._linstor:
749 raise xs_errors.XenError(
750 'SRUnavailable',
751 opterr='no such volume group: {}'.format(self._group_name)
752 )
754 # Note: `scan` can be called outside this module, so ensure the VDIs
755 # are loaded.
756 self._load_vdis()
757 self._update_physical_size()
759 for vdi_uuid in list(self.vdis.keys()):
760 if self.vdis[vdi_uuid].deleted:
761 del self.vdis[vdi_uuid]
763 # Security to prevent VDIs from being forgotten if the controller
764 # is started without a shared and mounted /var/lib/linstor path.
765 try:
766 self._linstor.get_database_path()
767 except Exception:
768 # Failed to get database path, ensure we don't have
769 # VDIs in the XAPI database...
770 if self.session.xenapi.SR.get_VDIs(
771 self.session.xenapi.SR.get_by_uuid(self.uuid)
772 ):
773 raise xs_errors.XenError(
774 'SRUnavailable',
775 opterr='Database is not mounted'
776 )
778 # Update the database before the restart of the GC to avoid
779 # bad sync in the process if new VDIs have been introduced.
780 super(LinstorSR, self).scan(self.uuid)
781 self._kick_gc()
783 @_locked_load
784 def vdi(self, uuid):
785 return LinstorVDI(self, uuid)
787 _locked_load = staticmethod(_locked_load)
789 # --------------------------------------------------------------------------
790 # Lock.
791 # --------------------------------------------------------------------------
793 def _shared_lock_vdi(self, vdi_uuid, locked=True):
794 master = util.get_master_ref(self.session)
796 command = 'lockVdi'
797 args = {
798 'groupName': self._group_name,
799 'srUuid': self.uuid,
800 'vdiUuid': vdi_uuid,
801 'locked': str(locked)
802 }
804 # Note: We must avoid to unlock the volume if the timeout is reached
805 # because during volume unlock, the SR lock is not used. Otherwise
806 # we could destroy a valid lock acquired from another host...
807 #
808 # This code is not very clean, the ideal solution would be to acquire
809 # the SR lock during volume unlock (like lock) but it's not easy
810 # to implement without impacting performance.
811 if not locked:
812 elapsed_time = time.time() - self._vdi_shared_time
813 timeout = LinstorVolumeManager.LOCKED_EXPIRATION_DELAY * 0.7
814 if elapsed_time >= timeout:
815 util.SMlog(
816 'Avoid unlock call of {} because timeout has been reached'
817 .format(vdi_uuid)
818 )
819 return
821 self._exec_manager_command(master, command, args, 'VDIUnavailable')
823 # --------------------------------------------------------------------------
824 # Network.
825 # --------------------------------------------------------------------------
827 def _exec_manager_command(self, host_ref, command, args, error):
828 host_rec = self.session.xenapi.host.get_record(host_ref)
829 host_uuid = host_rec['uuid']
831 try:
832 ret = self.session.xenapi.host.call_plugin(
833 host_ref, self.MANAGER_PLUGIN, command, args
834 )
835 except Exception as e:
836 util.SMlog(
837 'call-plugin on {} ({}:{} with {}) raised'.format(
838 host_uuid, self.MANAGER_PLUGIN, command, args
839 )
840 )
841 raise e
843 util.SMlog(
844 'call-plugin on {} ({}:{} with {}) returned: {}'.format(
845 host_uuid, self.MANAGER_PLUGIN, command, args, ret
846 )
847 )
848 if ret == 'False':
849 raise xs_errors.XenError(
850 error,
851 opterr='Plugin {} failed'.format(self.MANAGER_PLUGIN)
852 )
854 def _prepare_sr(self, host, group_name, enabled):
855 self._exec_manager_command(
856 host,
857 'prepareSr' if enabled else 'releaseSr',
858 {'groupName': group_name},
859 'SRUnavailable'
860 )
862 def _prepare_sr_on_all_hosts(self, group_name, enabled):
863 master = util.get_master_ref(self.session)
864 self._prepare_sr(master, group_name, enabled)
866 for slave in util.get_all_slaves(self.session):
867 self._prepare_sr(slave, group_name, enabled)
869 def _update_drbd_reactor(self, host, enabled):
870 self._exec_manager_command(
871 host,
872 'updateDrbdReactor',
873 {'enabled': str(enabled)},
874 'SRUnavailable'
875 )
877 def _update_drbd_reactor_on_all_hosts(
878 self, enabled, controller_node_name=None
879 ):
880 if controller_node_name == 'localhost':
881 controller_node_name = self.session.xenapi.host.get_record(
882 util.get_this_host_ref(self.session)
883 )['hostname']
884 assert controller_node_name
885 assert controller_node_name != 'localhost'
887 controller_host = None
888 secondary_hosts = []
890 hosts = self.session.xenapi.host.get_all_records()
891 for host_ref, host_rec in hosts.items():
892 hostname = host_rec['hostname']
893 if controller_node_name == hostname:
894 controller_host = host_ref
895 else:
896 secondary_hosts.append((host_ref, hostname))
898 action_name = 'Starting' if enabled else 'Stopping'
899 if controller_node_name and not controller_host:
900 util.SMlog('Failed to find controller host: `{}`'.format(
901 controller_node_name
902 ))
904 if enabled and controller_host:
905 util.SMlog('{} drbd-reactor on controller host `{}`...'.format(
906 action_name, controller_node_name
907 ))
908 # If enabled is true, we try to start the controller on the desired
909 # node name first.
910 self._update_drbd_reactor(controller_host, enabled)
912 for host_ref, hostname in secondary_hosts:
913 util.SMlog('{} drbd-reactor on host {}...'.format(
914 action_name, hostname
915 ))
916 self._update_drbd_reactor(host_ref, enabled)
918 if not enabled and controller_host:
919 util.SMlog('{} drbd-reactor on controller host `{}`...'.format(
920 action_name, controller_node_name
921 ))
922 # If enabled is false, we disable the drbd-reactor service of
923 # the controller host last. Why? Otherwise the linstor-controller
924 # of other nodes can be started, and we don't want that.
925 self._update_drbd_reactor(controller_host, enabled)
927 # --------------------------------------------------------------------------
928 # Metadata.
929 # --------------------------------------------------------------------------
931 def _synchronize_metadata_and_xapi(self):
932 try:
933 # First synch SR parameters.
934 self.update(self.uuid)
936 # Now update the VDI information in the metadata if required.
937 xenapi = self.session.xenapi
938 volumes_metadata = self._linstor.get_volumes_with_metadata()
939 for vdi_uuid, volume_metadata in volumes_metadata.items():
940 try:
941 vdi_ref = xenapi.VDI.get_by_uuid(vdi_uuid)
942 except Exception:
943 # May be the VDI is not in XAPI yet dont bother.
944 continue
946 label = util.to_plain_string(
947 xenapi.VDI.get_name_label(vdi_ref)
948 )
949 description = util.to_plain_string(
950 xenapi.VDI.get_name_description(vdi_ref)
951 )
953 if (
954 volume_metadata.get(NAME_LABEL_TAG) != label or
955 volume_metadata.get(NAME_DESCRIPTION_TAG) != description
956 ):
957 self._linstor.update_volume_metadata(vdi_uuid, {
958 NAME_LABEL_TAG: label,
959 NAME_DESCRIPTION_TAG: description
960 })
961 except Exception as e:
962 raise xs_errors.XenError(
963 'MetadataError',
964 opterr='Error synching SR Metadata and XAPI: {}'.format(e)
965 )
967 def _synchronize_metadata(self):
968 if not self._is_master:
969 return
971 util.SMlog('Synchronize metadata...')
972 if self.cmd == 'sr_attach':
973 try:
974 util.SMlog(
975 'Synchronize SR metadata and the state on the storage.'
976 )
977 self._synchronize_metadata_and_xapi()
978 except Exception as e:
979 util.SMlog('Failed to synchronize metadata: {}'.format(e))
981 # --------------------------------------------------------------------------
982 # Stats.
983 # --------------------------------------------------------------------------
985 def _update_stats(self, virt_alloc_delta):
986 valloc = int(self.session.xenapi.SR.get_virtual_allocation(
987 self.sr_ref
988 ))
990 # Update size attributes of the SR parent class.
991 self.virtual_allocation = valloc + virt_alloc_delta
993 self._update_physical_size()
995 # Notify SR parent class.
996 self._db_update()
998 def _update_physical_size(self):
999 # We use the size of the smallest disk, this is an approximation that
1000 # ensures the displayed physical size is reachable by the user.
1001 (min_physical_size, pool_count) = self._linstor.get_min_physical_size()
1002 self.physical_size = min_physical_size * pool_count // \
1003 self._linstor.redundancy
1005 self.physical_utilisation = self._linstor.allocated_volume_size
1007 # --------------------------------------------------------------------------
1008 # VDIs.
1009 # --------------------------------------------------------------------------
1011 def _load_vdis(self):
1012 if self._vdis_loaded:
1013 return
1015 assert self._is_master
1017 # We use a cache to avoid repeated JSON parsing.
1018 # The performance gain is not big but we can still
1019 # enjoy it with a few lines.
1020 self._create_linstor_cache()
1021 self._load_vdis_ex()
1022 self._destroy_linstor_cache()
1024 # We must mark VDIs as loaded only if the load is a success.
1025 self._vdis_loaded = True
1027 self._undo_all_journal_transactions()
1029 def _load_vdis_ex(self):
1030 # 1. Get existing VDIs in XAPI.
1031 xenapi = self.session.xenapi
1032 xapi_vdi_uuids = set()
1033 for vdi in xenapi.SR.get_VDIs(self.sr_ref):
1034 xapi_vdi_uuids.add(xenapi.VDI.get_uuid(vdi))
1036 # 2. Get volumes info.
1037 all_volume_info = self._all_volume_info_cache
1038 volumes_metadata = self._all_volume_metadata_cache
1040 # 3. Get CBT vdis.
1041 # See: https://support.citrix.com/article/CTX230619
1042 cbt_vdis = set()
1043 for volume_metadata in volumes_metadata.values():
1044 cbt_uuid = volume_metadata.get(CBTLOG_TAG)
1045 if cbt_uuid:
1046 cbt_vdis.add(cbt_uuid)
1048 introduce = False
1050 # Try to introduce VDIs only during scan/attach.
1051 if self.cmd == 'sr_scan' or self.cmd == 'sr_attach':
1052 has_clone_entries = list(self._journaler.get_all(
1053 LinstorJournaler.CLONE
1054 ).items())
1056 if has_clone_entries:
1057 util.SMlog(
1058 'Cannot introduce VDIs during scan because it exists '
1059 'CLONE entries in journaler on SR {}'.format(self.uuid)
1060 )
1061 else:
1062 introduce = True
1064 # 4. Now check all volume info.
1065 vdi_to_snaps = {}
1066 for vdi_uuid, volume_info in all_volume_info.items():
1067 if vdi_uuid.startswith(cleanup.SR.TMP_RENAME_PREFIX):
1068 continue
1070 # 4.a. Check if the VDI in LINSTOR is in XAPI VDIs.
1071 if vdi_uuid not in xapi_vdi_uuids:
1072 if not introduce:
1073 continue
1075 if vdi_uuid.startswith('DELETED_'):
1076 continue
1078 volume_metadata = volumes_metadata.get(vdi_uuid)
1079 if not volume_metadata:
1080 util.SMlog(
1081 'Skipping volume {} because no metadata could be found'
1082 .format(vdi_uuid)
1083 )
1084 continue
1086 util.SMlog(
1087 'Trying to introduce VDI {} as it is present in '
1088 'LINSTOR and not in XAPI...'
1089 .format(vdi_uuid)
1090 )
1092 try:
1093 self._linstor.get_device_path(vdi_uuid)
1094 except Exception as e:
1095 util.SMlog(
1096 'Cannot introduce {}, unable to get path: {}'
1097 .format(vdi_uuid, e)
1098 )
1099 continue
1101 name_label = volume_metadata.get(NAME_LABEL_TAG) or ''
1102 type = volume_metadata.get(TYPE_TAG) or 'user'
1103 vdi_type = volume_metadata.get(VDI_TYPE_TAG)
1105 if not vdi_type:
1106 util.SMlog(
1107 'Cannot introduce {} '.format(vdi_uuid) +
1108 'without vdi_type'
1109 )
1110 continue
1112 sm_config = {
1113 'vdi_type': vdi_type
1114 }
1116 if vdi_type == vhdutil.VDI_TYPE_RAW:
1117 managed = not volume_metadata.get(HIDDEN_TAG)
1118 elif vdi_type == vhdutil.VDI_TYPE_VHD:
1119 vhd_info = self._vhdutil.get_vhd_info(vdi_uuid)
1120 managed = not vhd_info.hidden
1121 if vhd_info.parentUuid:
1122 sm_config['vhd-parent'] = vhd_info.parentUuid
1123 else:
1124 util.SMlog(
1125 'Cannot introduce {} with invalid VDI type {}'
1126 .format(vdi_uuid, vdi_type)
1127 )
1128 continue
1130 util.SMlog(
1131 'Introducing VDI {} '.format(vdi_uuid) +
1132 ' (name={}, virtual_size={}, allocated_size={})'.format(
1133 name_label,
1134 volume_info.virtual_size,
1135 volume_info.allocated_size
1136 )
1137 )
1139 vdi_ref = xenapi.VDI.db_introduce(
1140 vdi_uuid,
1141 name_label,
1142 volume_metadata.get(NAME_DESCRIPTION_TAG) or '',
1143 self.sr_ref,
1144 type,
1145 False, # sharable
1146 bool(volume_metadata.get(READ_ONLY_TAG)),
1147 {}, # other_config
1148 vdi_uuid, # location
1149 {}, # xenstore_data
1150 sm_config,
1151 managed,
1152 str(volume_info.virtual_size),
1153 str(volume_info.allocated_size)
1154 )
1156 is_a_snapshot = volume_metadata.get(IS_A_SNAPSHOT_TAG)
1157 xenapi.VDI.set_is_a_snapshot(vdi_ref, bool(is_a_snapshot))
1158 if is_a_snapshot:
1159 xenapi.VDI.set_snapshot_time(
1160 vdi_ref,
1161 xmlrpc.client.DateTime(
1162 volume_metadata[SNAPSHOT_TIME_TAG] or
1163 '19700101T00:00:00Z'
1164 )
1165 )
1167 snap_uuid = volume_metadata[SNAPSHOT_OF_TAG]
1168 if snap_uuid in vdi_to_snaps:
1169 vdi_to_snaps[snap_uuid].append(vdi_uuid)
1170 else:
1171 vdi_to_snaps[snap_uuid] = [vdi_uuid]
1173 # 4.b. Add the VDI in the list.
1174 vdi = self.vdi(vdi_uuid)
1175 self.vdis[vdi_uuid] = vdi
1177 if USE_KEY_HASH and vdi.vdi_type == vhdutil.VDI_TYPE_VHD:
1178 # TODO: Replace pylint comment with this feature when possible:
1179 # https://github.com/PyCQA/pylint/pull/2926
1180 vdi.sm_config_override['key_hash'] = \
1181 self._vhdutil.get_key_hash(vdi_uuid) # pylint: disable = E1120
1183 # 4.c. Update CBT status of disks either just added
1184 # or already in XAPI.
1185 cbt_uuid = volume_metadata.get(CBTLOG_TAG)
1186 if cbt_uuid in cbt_vdis:
1187 vdi_ref = xenapi.VDI.get_by_uuid(vdi_uuid)
1188 xenapi.VDI.set_cbt_enabled(vdi_ref, True)
1189 # For existing VDIs, update local state too.
1190 # Scan in base class SR updates existing VDIs
1191 # again based on local states.
1192 self.vdis[vdi_uuid].cbt_enabled = True
1193 cbt_vdis.remove(cbt_uuid)
1195 # 5. Now set the snapshot statuses correctly in XAPI.
1196 for src_uuid in vdi_to_snaps:
1197 try:
1198 src_ref = xenapi.VDI.get_by_uuid(src_uuid)
1199 except Exception:
1200 # The source VDI no longer exists, continue.
1201 continue
1203 for snap_uuid in vdi_to_snaps[src_uuid]:
1204 try:
1205 # This might fail in cases where its already set.
1206 snap_ref = xenapi.VDI.get_by_uuid(snap_uuid)
1207 xenapi.VDI.set_snapshot_of(snap_ref, src_ref)
1208 except Exception as e:
1209 util.SMlog('Setting snapshot failed: {}'.format(e))
1211 # TODO: Check correctly how to use CBT.
1212 # Update cbt_enabled on the right VDI, check LVM/FileSR code.
1214 # 6. If we have items remaining in this list,
1215 # they are cbt_metadata VDI that XAPI doesn't know about.
1216 # Add them to self.vdis and they'll get added to the DB.
1217 for cbt_uuid in cbt_vdis:
1218 new_vdi = self.vdi(cbt_uuid)
1219 new_vdi.ty = 'cbt_metadata'
1220 new_vdi.cbt_enabled = True
1221 self.vdis[cbt_uuid] = new_vdi
1223 # 7. Update virtual allocation, build geneology and remove useless VDIs
1224 self.virtual_allocation = 0
1226 # 8. Build geneology.
1227 geneology = {}
1229 for vdi_uuid, vdi in self.vdis.items():
1230 if vdi.parent:
1231 if vdi.parent in self.vdis:
1232 self.vdis[vdi.parent].read_only = True
1233 if vdi.parent in geneology:
1234 geneology[vdi.parent].append(vdi_uuid)
1235 else:
1236 geneology[vdi.parent] = [vdi_uuid]
1237 if not vdi.hidden:
1238 self.virtual_allocation += vdi.size
1240 # 9. Remove all hidden leaf nodes to avoid introducing records that
1241 # will be GC'ed.
1242 for vdi_uuid in list(self.vdis.keys()):
1243 if vdi_uuid not in geneology and self.vdis[vdi_uuid].hidden:
1244 util.SMlog(
1245 'Scan found hidden leaf ({}), ignoring'.format(vdi_uuid)
1246 )
1247 del self.vdis[vdi_uuid]
1249 # --------------------------------------------------------------------------
1250 # Journals.
1251 # --------------------------------------------------------------------------
1253 def _get_vdi_path_and_parent(self, vdi_uuid, volume_name):
1254 try:
1255 device_path = self._linstor.build_device_path(volume_name)
1256 if not util.pathexists(device_path):
1257 return (None, None)
1259 # If it's a RAW VDI, there is no parent.
1260 volume_metadata = self._linstor.get_volume_metadata(vdi_uuid)
1261 vdi_type = volume_metadata[VDI_TYPE_TAG]
1262 if vdi_type == vhdutil.VDI_TYPE_RAW:
1263 return (device_path, None)
1265 # Otherwise it's a VHD and a parent can exist.
1266 if not self._vhdutil.check(vdi_uuid):
1267 return (None, None)
1269 vhd_info = self._vhdutil.get_vhd_info(vdi_uuid)
1270 if vhd_info:
1271 return (device_path, vhd_info.parentUuid)
1272 except Exception as e:
1273 util.SMlog(
1274 'Failed to get VDI path and parent, ignoring: {}'
1275 .format(e)
1276 )
1277 return (None, None)
1279 def _undo_all_journal_transactions(self):
1280 util.SMlog('Undoing all journal transactions...')
1281 self.lock.acquire()
1282 try:
1283 self._handle_interrupted_inflate_ops()
1284 self._handle_interrupted_clone_ops()
1285 pass
1286 finally:
1287 self.lock.release()
1289 def _handle_interrupted_inflate_ops(self):
1290 transactions = self._journaler.get_all(LinstorJournaler.INFLATE)
1291 for vdi_uuid, old_size in transactions.items():
1292 self._handle_interrupted_inflate(vdi_uuid, old_size)
1293 self._journaler.remove(LinstorJournaler.INFLATE, vdi_uuid)
1295 def _handle_interrupted_clone_ops(self):
1296 transactions = self._journaler.get_all(LinstorJournaler.CLONE)
1297 for vdi_uuid, old_size in transactions.items():
1298 self._handle_interrupted_clone(vdi_uuid, old_size)
1299 self._journaler.remove(LinstorJournaler.CLONE, vdi_uuid)
1301 def _handle_interrupted_inflate(self, vdi_uuid, old_size):
1302 util.SMlog(
1303 '*** INTERRUPTED INFLATE OP: for {} ({})'
1304 .format(vdi_uuid, old_size)
1305 )
1307 vdi = self.vdis.get(vdi_uuid)
1308 if not vdi:
1309 util.SMlog('Cannot deflate missing VDI {}'.format(vdi_uuid))
1310 return
1312 assert not self._all_volume_info_cache
1313 volume_info = self._linstor.get_volume_info(vdi_uuid)
1315 current_size = volume_info.virtual_size
1316 assert current_size > 0
1317 self._vhdutil.force_deflate(vdi.path, old_size, current_size, zeroize=True)
1319 def _handle_interrupted_clone(
1320 self, vdi_uuid, clone_info, force_undo=False
1321 ):
1322 util.SMlog(
1323 '*** INTERRUPTED CLONE OP: for {} ({})'
1324 .format(vdi_uuid, clone_info)
1325 )
1327 base_uuid, snap_uuid = clone_info.split('_')
1329 # Use LINSTOR data because new VDIs may not be in the XAPI.
1330 volume_names = self._linstor.get_volumes_with_name()
1332 # Check if we don't have a base VDI. (If clone failed at startup.)
1333 if base_uuid not in volume_names:
1334 if vdi_uuid in volume_names:
1335 util.SMlog('*** INTERRUPTED CLONE OP: nothing to do')
1336 return
1337 raise util.SMException(
1338 'Base copy {} not present, but no original {} found'
1339 .format(base_uuid, vdi_uuid)
1340 )
1342 if force_undo:
1343 util.SMlog('Explicit revert')
1344 self._undo_clone(
1345 volume_names, vdi_uuid, base_uuid, snap_uuid
1346 )
1347 return
1349 # If VDI or snap uuid is missing...
1350 if vdi_uuid not in volume_names or \
1351 (snap_uuid and snap_uuid not in volume_names):
1352 util.SMlog('One or both leaves missing => revert')
1353 self._undo_clone(volume_names, vdi_uuid, base_uuid, snap_uuid)
1354 return
1356 vdi_path, vdi_parent_uuid = self._get_vdi_path_and_parent(
1357 vdi_uuid, volume_names[vdi_uuid]
1358 )
1359 snap_path, snap_parent_uuid = self._get_vdi_path_and_parent(
1360 snap_uuid, volume_names[snap_uuid]
1361 )
1363 if not vdi_path or (snap_uuid and not snap_path):
1364 util.SMlog('One or both leaves invalid (and path(s)) => revert')
1365 self._undo_clone(volume_names, vdi_uuid, base_uuid, snap_uuid)
1366 return
1368 util.SMlog('Leaves valid but => revert')
1369 self._undo_clone(volume_names, vdi_uuid, base_uuid, snap_uuid)
1371 def _undo_clone(self, volume_names, vdi_uuid, base_uuid, snap_uuid):
1372 base_path = self._linstor.build_device_path(volume_names[base_uuid])
1373 base_metadata = self._linstor.get_volume_metadata(base_uuid)
1374 base_type = base_metadata[VDI_TYPE_TAG]
1376 if not util.pathexists(base_path):
1377 util.SMlog('Base not found! Exit...')
1378 util.SMlog('*** INTERRUPTED CLONE OP: rollback fail')
1379 return
1381 # Un-hide the parent.
1382 self._linstor.update_volume_metadata(base_uuid, {READ_ONLY_TAG: False})
1383 if base_type == vhdutil.VDI_TYPE_VHD:
1384 vhd_info = self._vhdutil.get_vhd_info(base_uuid, False)
1385 if vhd_info.hidden:
1386 self._vhdutil.set_hidden(base_path, False)
1387 elif base_type == vhdutil.VDI_TYPE_RAW and \
1388 base_metadata.get(HIDDEN_TAG):
1389 self._linstor.update_volume_metadata(
1390 base_uuid, {HIDDEN_TAG: False}
1391 )
1393 # Remove the child nodes.
1394 if snap_uuid and snap_uuid in volume_names:
1395 util.SMlog('Destroying snap {}...'.format(snap_uuid))
1397 try:
1398 self._linstor.destroy_volume(snap_uuid)
1399 except Exception as e:
1400 util.SMlog(
1401 'Cannot destroy snap {} during undo clone: {}'
1402 .format(snap_uuid, e)
1403 )
1405 if vdi_uuid in volume_names:
1406 try:
1407 util.SMlog('Destroying {}...'.format(vdi_uuid))
1408 self._linstor.destroy_volume(vdi_uuid)
1409 except Exception as e:
1410 util.SMlog(
1411 'Cannot destroy VDI {} during undo clone: {}'
1412 .format(vdi_uuid, e)
1413 )
1414 # We can get an exception like this:
1415 # "Shutdown of the DRBD resource 'XXX failed", so the
1416 # volume info remains... The problem is we can't rename
1417 # properly the base VDI below this line, so we must change the
1418 # UUID of this bad VDI before.
1419 self._linstor.update_volume_uuid(
1420 vdi_uuid, 'DELETED_' + vdi_uuid, force=True
1421 )
1423 # Rename!
1424 self._linstor.update_volume_uuid(base_uuid, vdi_uuid)
1426 # Inflate to the right size.
1427 if base_type == vhdutil.VDI_TYPE_VHD:
1428 vdi = self.vdi(vdi_uuid)
1429 volume_size = LinstorVhdUtil.compute_volume_size(vdi.size, vdi.vdi_type)
1430 self._vhdutil.inflate(
1431 self._journaler, vdi_uuid, vdi.path,
1432 volume_size, vdi.capacity
1433 )
1434 self.vdis[vdi_uuid] = vdi
1436 # At this stage, tapdisk and SM vdi will be in paused state. Remove
1437 # flag to facilitate vm deactivate.
1438 vdi_ref = self.session.xenapi.VDI.get_by_uuid(vdi_uuid)
1439 self.session.xenapi.VDI.remove_from_sm_config(vdi_ref, 'paused')
1441 util.SMlog('*** INTERRUPTED CLONE OP: rollback success')
1443 # --------------------------------------------------------------------------
1444 # Cache.
1445 # --------------------------------------------------------------------------
1447 def _create_linstor_cache(self):
1448 reconnect = False
1450 def create_cache():
1451 nonlocal reconnect
1452 try:
1453 if reconnect:
1454 self._reconnect()
1455 return self._linstor.get_volumes_with_info()
1456 except Exception as e:
1457 reconnect = True
1458 raise e
1460 self._all_volume_metadata_cache = \
1461 self._linstor.get_volumes_with_metadata()
1462 self._all_volume_info_cache = util.retry(
1463 create_cache,
1464 maxretry=10,
1465 period=3
1466 )
1468 def _destroy_linstor_cache(self):
1469 self._all_volume_info_cache = None
1470 self._all_volume_metadata_cache = None
1472 # --------------------------------------------------------------------------
1473 # Misc.
1474 # --------------------------------------------------------------------------
1476 def _reconnect(self):
1477 controller_uri = get_controller_uri()
1479 self._journaler = LinstorJournaler(
1480 controller_uri, self._group_name, logger=util.SMlog
1481 )
1483 # Try to open SR if exists.
1484 # We can repair only if we are on the master AND if
1485 # we are trying to execute an exclusive operation.
1486 # Otherwise we could try to delete a VDI being created or
1487 # during a snapshot. An exclusive op is the guarantee that
1488 # the SR is locked.
1489 self._linstor = LinstorVolumeManager(
1490 controller_uri,
1491 self._group_name,
1492 repair=(
1493 self._is_master and
1494 self.srcmd.cmd in self.ops_exclusive
1495 ),
1496 logger=util.SMlog
1497 )
1498 self._vhdutil = LinstorVhdUtil(self.session, self._linstor)
1500 def _ensure_space_available(self, amount_needed):
1501 space_available = self._linstor.max_volume_size_allowed
1502 if (space_available < amount_needed):
1503 util.SMlog(
1504 'Not enough space! Free space: {}, need: {}'.format(
1505 space_available, amount_needed
1506 )
1507 )
1508 raise xs_errors.XenError('SRNoSpace')
1510 def _kick_gc(self):
1511 # Don't bother if an instance already running. This is just an
1512 # optimization to reduce the overhead of forking a new process if we
1513 # don't have to, but the process will check the lock anyways.
1514 lock = Lock(cleanup.LOCK_TYPE_RUNNING, self.uuid)
1515 if not lock.acquireNoblock():
1516 if not cleanup.should_preempt(self.session, self.uuid):
1517 util.SMlog('A GC instance already running, not kicking')
1518 return
1520 util.SMlog('Aborting currently-running coalesce of garbage VDI')
1521 try:
1522 if not cleanup.abort(self.uuid, soft=True):
1523 util.SMlog('The GC has already been scheduled to re-start')
1524 except util.CommandException as e:
1525 if e.code != errno.ETIMEDOUT:
1526 raise
1527 util.SMlog('Failed to abort the GC')
1528 else:
1529 lock.release()
1531 util.SMlog('Kicking GC')
1532 cleanup.gc(self.session, self.uuid, True)
1534# ==============================================================================
1535# LinstorSr VDI
1536# ==============================================================================
1539class LinstorVDI(VDI.VDI):
1540 # Warning: Not the same values than vhdutil.VDI_TYPE_*.
1541 # These values represents the types given on the command line.
1542 TYPE_RAW = 'raw'
1543 TYPE_VHD = 'vhd'
1545 # Metadata size given to the "S" param of vhd-util create.
1546 # "-S size (MB) for metadata preallocation".
1547 # Increase the performance when resize is called.
1548 MAX_METADATA_VIRT_SIZE = 2 * 1024 * 1024
1550 # --------------------------------------------------------------------------
1551 # VDI methods.
1552 # --------------------------------------------------------------------------
1554 def load(self, vdi_uuid):
1555 self._lock = self.sr.lock
1556 self._exists = True
1557 self._linstor = self.sr._linstor
1559 # Update hidden parent property.
1560 self.hidden = False
1562 def raise_bad_load(e):
1563 util.SMlog(
1564 'Got exception in LinstorVDI.load: {}'.format(e)
1565 )
1566 util.SMlog(traceback.format_exc())
1567 raise xs_errors.XenError(
1568 'VDIUnavailable',
1569 opterr='Could not load {} because: {}'.format(self.uuid, e)
1570 )
1572 # Try to load VDI.
1573 try:
1574 if (
1575 self.sr.srcmd.cmd == 'vdi_attach_from_config' or
1576 self.sr.srcmd.cmd == 'vdi_detach_from_config'
1577 ):
1578 self.vdi_type = vhdutil.VDI_TYPE_RAW
1579 self.path = self.sr.srcmd.params['vdi_path']
1580 else:
1581 self._determine_type_and_path()
1582 self._load_this()
1584 util.SMlog('VDI {} loaded! (path={}, hidden={})'.format(
1585 self.uuid, self.path, self.hidden
1586 ))
1587 except LinstorVolumeManagerError as e:
1588 # 1. It may be a VDI deletion.
1589 if e.code == LinstorVolumeManagerError.ERR_VOLUME_NOT_EXISTS:
1590 if self.sr.srcmd.cmd == 'vdi_delete':
1591 self.deleted = True
1592 return
1594 # 2. Or maybe a creation.
1595 if self.sr.srcmd.cmd == 'vdi_create':
1596 # Set type attribute of VDI parent class.
1597 # We use VHD by default.
1598 self.vdi_type = vhdutil.VDI_TYPE_VHD
1599 self._key_hash = None # Only used in create.
1601 self._exists = False
1602 vdi_sm_config = self.sr.srcmd.params.get('vdi_sm_config')
1603 if vdi_sm_config is not None:
1604 type = vdi_sm_config.get('type')
1605 if type is not None:
1606 if type == self.TYPE_RAW:
1607 self.vdi_type = vhdutil.VDI_TYPE_RAW
1608 elif type == self.TYPE_VHD:
1609 self.vdi_type = vhdutil.VDI_TYPE_VHD
1610 else:
1611 raise xs_errors.XenError(
1612 'VDICreate',
1613 opterr='Invalid VDI type {}'.format(type)
1614 )
1615 if self.vdi_type == vhdutil.VDI_TYPE_VHD:
1616 self._key_hash = vdi_sm_config.get('key_hash')
1618 # For the moment we don't have a path.
1619 self._update_device_name(None)
1620 return
1621 raise_bad_load(e)
1622 except Exception as e:
1623 raise_bad_load(e)
1625 def create(self, sr_uuid, vdi_uuid, size):
1626 # Usage example:
1627 # xe vdi-create sr-uuid=39a5826b-5a90-73eb-dd09-51e3a116f937
1628 # name-label="linstor-vdi-1" virtual-size=4096MiB sm-config:type=vhd
1630 # 1. Check if we are on the master and if the VDI doesn't exist.
1631 util.SMlog('LinstorVDI.create for {}'.format(self.uuid))
1632 if self._exists:
1633 raise xs_errors.XenError('VDIExists')
1635 assert self.uuid
1636 assert self.ty
1637 assert self.vdi_type
1639 # 2. Compute size and check space available.
1640 size = vhdutil.validate_and_round_vhd_size(int(size))
1641 volume_size = LinstorVhdUtil.compute_volume_size(size, self.vdi_type)
1642 util.SMlog(
1643 'LinstorVDI.create: type={}, vhd-size={}, volume-size={}'
1644 .format(self.vdi_type, size, volume_size)
1645 )
1646 self.sr._ensure_space_available(volume_size)
1648 # 3. Set sm_config attribute of VDI parent class.
1649 self.sm_config = self.sr.srcmd.params['vdi_sm_config']
1651 # 4. Create!
1652 failed = False
1653 try:
1654 volume_name = None
1655 if self.ty == 'ha_statefile':
1656 volume_name = HA_VOLUME_NAME
1657 elif self.ty == 'redo_log':
1658 volume_name = REDO_LOG_VOLUME_NAME
1660 self._linstor.create_volume(
1661 self.uuid, volume_size, persistent=False,
1662 volume_name=volume_name
1663 )
1664 volume_info = self._linstor.get_volume_info(self.uuid)
1666 self._update_device_name(volume_info.name)
1668 if self.vdi_type == vhdutil.VDI_TYPE_RAW:
1669 self.size = volume_info.virtual_size
1670 else:
1671 self.sr._vhdutil.create(
1672 self.path, size, False, self.MAX_METADATA_VIRT_SIZE
1673 )
1674 self.size = self.sr._vhdutil.get_size_virt(self.uuid)
1676 if self._key_hash:
1677 self.sr._vhdutil.set_key(self.path, self._key_hash)
1679 # Because vhdutil commands modify the volume data,
1680 # we must retrieve a new time the utilization size.
1681 volume_info = self._linstor.get_volume_info(self.uuid)
1683 volume_metadata = {
1684 NAME_LABEL_TAG: util.to_plain_string(self.label),
1685 NAME_DESCRIPTION_TAG: util.to_plain_string(self.description),
1686 IS_A_SNAPSHOT_TAG: False,
1687 SNAPSHOT_OF_TAG: '',
1688 SNAPSHOT_TIME_TAG: '',
1689 TYPE_TAG: self.ty,
1690 VDI_TYPE_TAG: self.vdi_type,
1691 READ_ONLY_TAG: bool(self.read_only),
1692 METADATA_OF_POOL_TAG: ''
1693 }
1694 self._linstor.set_volume_metadata(self.uuid, volume_metadata)
1696 # Set the open timeout to 1min to reduce CPU usage
1697 # in http-disk-server when a secondary server tries to open
1698 # an already opened volume.
1699 if self.ty == 'ha_statefile' or self.ty == 'redo_log':
1700 self._linstor.set_auto_promote_timeout(self.uuid, 600)
1702 self._linstor.mark_volume_as_persistent(self.uuid)
1703 except util.CommandException as e:
1704 failed = True
1705 raise xs_errors.XenError(
1706 'VDICreate', opterr='error {}'.format(e.code)
1707 )
1708 except Exception as e:
1709 failed = True
1710 raise xs_errors.XenError('VDICreate', opterr='error {}'.format(e))
1711 finally:
1712 if failed:
1713 util.SMlog('Unable to create VDI {}'.format(self.uuid))
1714 try:
1715 self._linstor.destroy_volume(self.uuid)
1716 except Exception as e:
1717 util.SMlog(
1718 'Ignoring exception after fail in LinstorVDI.create: '
1719 '{}'.format(e)
1720 )
1722 self.utilisation = volume_info.allocated_size
1723 self.sm_config['vdi_type'] = self.vdi_type
1725 self.ref = self._db_introduce()
1726 self.sr._update_stats(self.size)
1728 return VDI.VDI.get_params(self)
1730 def delete(self, sr_uuid, vdi_uuid, data_only=False):
1731 util.SMlog('LinstorVDI.delete for {}'.format(self.uuid))
1732 if self.attached:
1733 raise xs_errors.XenError('VDIInUse')
1735 if self.deleted:
1736 return super(LinstorVDI, self).delete(
1737 sr_uuid, vdi_uuid, data_only
1738 )
1740 vdi_ref = self.sr.srcmd.params['vdi_ref']
1741 if not self.session.xenapi.VDI.get_managed(vdi_ref):
1742 raise xs_errors.XenError(
1743 'VDIDelete',
1744 opterr='Deleting non-leaf node not permitted'
1745 )
1747 try:
1748 # Remove from XAPI and delete from LINSTOR.
1749 self._linstor.destroy_volume(self.uuid)
1750 if not data_only:
1751 self._db_forget()
1753 self.sr.lock.cleanupAll(vdi_uuid)
1754 except Exception as e:
1755 util.SMlog(
1756 'Failed to remove the volume (maybe is leaf coalescing) '
1757 'for {} err: {}'.format(self.uuid, e)
1758 )
1759 raise xs_errors.XenError('VDIDelete', opterr=str(e))
1761 if self.uuid in self.sr.vdis:
1762 del self.sr.vdis[self.uuid]
1764 # TODO: Check size after delete.
1765 self.sr._update_stats(-self.size)
1766 self.sr._kick_gc()
1767 return super(LinstorVDI, self).delete(sr_uuid, vdi_uuid, data_only)
1769 def attach(self, sr_uuid, vdi_uuid):
1770 util.SMlog('LinstorVDI.attach for {}'.format(self.uuid))
1771 attach_from_config = self.sr.srcmd.cmd == 'vdi_attach_from_config'
1772 if (
1773 not attach_from_config or
1774 self.sr.srcmd.params['vdi_uuid'] != self.uuid
1775 ) and self.sr._journaler.has_entries(self.uuid):
1776 raise xs_errors.XenError(
1777 'VDIUnavailable',
1778 opterr='Interrupted operation detected on this VDI, '
1779 'scan SR first to trigger auto-repair'
1780 )
1782 if not attach_from_config or self.sr._is_master:
1783 writable = 'args' not in self.sr.srcmd.params or \
1784 self.sr.srcmd.params['args'][0] == 'true'
1786 # We need to inflate the volume if we don't have enough place
1787 # to mount the VHD image. I.e. the volume capacity must be greater
1788 # than the VHD size + bitmap size.
1789 need_inflate = True
1790 if (
1791 self.vdi_type == vhdutil.VDI_TYPE_RAW or
1792 not writable or
1793 self.capacity >= LinstorVhdUtil.compute_volume_size(self.size, self.vdi_type)
1794 ):
1795 need_inflate = False
1797 if need_inflate:
1798 try:
1799 self._prepare_thin(True)
1800 except Exception as e:
1801 raise xs_errors.XenError(
1802 'VDIUnavailable',
1803 opterr='Failed to attach VDI during "prepare thin": {}'
1804 .format(e)
1805 )
1807 if not hasattr(self, 'xenstore_data'):
1808 self.xenstore_data = {}
1809 self.xenstore_data['storage-type'] = LinstorSR.DRIVER_TYPE
1811 if (
1812 USE_HTTP_NBD_SERVERS and
1813 attach_from_config and
1814 self.path.startswith('/dev/http-nbd/')
1815 ):
1816 return self._attach_using_http_nbd()
1818 # Ensure we have a path...
1819 while vdi_uuid:
1820 path = self._linstor.get_device_path(vdi_uuid)
1821 if not util.pathexists(path):
1822 raise xs_errors.XenError(
1823 'VDIUnavailable', opterr='Could not find: {}'.format(path)
1824 )
1826 # Diskless path can be created on the fly, ensure we can open it.
1827 def check_volume_usable():
1828 while True:
1829 try:
1830 with open(path, 'r+'):
1831 pass
1832 except IOError as e:
1833 if e.errno == errno.ENODATA:
1834 time.sleep(2)
1835 continue
1836 if e.errno == errno.EROFS:
1837 util.SMlog('Volume not attachable because RO. Openers: {}'.format(
1838 self.sr._linstor.get_volume_openers(vdi_uuid)
1839 ))
1840 raise
1841 break
1842 util.retry(check_volume_usable, 15, 2)
1844 vdi_uuid = self.sr._vhdutil.get_vhd_info(vdi_uuid).parentUuid
1846 self.attached = True
1847 return VDI.VDI.attach(self, self.sr.uuid, self.uuid)
1849 def detach(self, sr_uuid, vdi_uuid):
1850 util.SMlog('LinstorVDI.detach for {}'.format(self.uuid))
1851 detach_from_config = self.sr.srcmd.cmd == 'vdi_detach_from_config'
1852 self.attached = False
1854 if detach_from_config and self.path.startswith('/dev/http-nbd/'):
1855 return self._detach_using_http_nbd()
1857 if self.vdi_type == vhdutil.VDI_TYPE_RAW:
1858 return
1860 # The VDI is already deflated if the VHD image size + metadata is
1861 # equal to the LINSTOR volume size.
1862 volume_size = LinstorVhdUtil.compute_volume_size(self.size, self.vdi_type)
1863 already_deflated = self.capacity <= volume_size
1865 if already_deflated:
1866 util.SMlog(
1867 'VDI {} already deflated (old volume size={}, volume size={})'
1868 .format(self.uuid, self.capacity, volume_size)
1869 )
1871 need_deflate = True
1872 if already_deflated:
1873 need_deflate = False
1874 elif self.sr._provisioning == 'thick':
1875 need_deflate = False
1877 vdi_ref = self.sr.srcmd.params['vdi_ref']
1878 if self.session.xenapi.VDI.get_is_a_snapshot(vdi_ref):
1879 need_deflate = True
1881 if need_deflate:
1882 try:
1883 self._prepare_thin(False)
1884 except Exception as e:
1885 raise xs_errors.XenError(
1886 'VDIUnavailable',
1887 opterr='Failed to detach VDI during "prepare thin": {}'
1888 .format(e)
1889 )
1891 # We remove only on slaves because the volume can be used by the GC.
1892 if self.sr._is_master:
1893 return
1895 while vdi_uuid:
1896 try:
1897 path = self._linstor.build_device_path(self._linstor.get_volume_name(vdi_uuid))
1898 parent_vdi_uuid = self.sr._vhdutil.get_vhd_info(vdi_uuid).parentUuid
1899 except Exception:
1900 break
1902 if util.pathexists(path):
1903 try:
1904 self._linstor.remove_volume_if_diskless(vdi_uuid)
1905 except Exception as e:
1906 # Ensure we can always detach properly.
1907 # I don't want to corrupt the XAPI info.
1908 util.SMlog('Failed to clean VDI {} during detach: {}'.format(vdi_uuid, e))
1909 vdi_uuid = parent_vdi_uuid
1911 def resize(self, sr_uuid, vdi_uuid, size):
1912 util.SMlog('LinstorVDI.resize for {}'.format(self.uuid))
1913 if not self.sr._is_master:
1914 raise xs_errors.XenError(
1915 'VDISize',
1916 opterr='resize on slave not allowed'
1917 )
1919 if self.hidden:
1920 raise xs_errors.XenError('VDIUnavailable', opterr='hidden VDI')
1922 # Compute the virtual VHD and DRBD volume size.
1923 size = vhdutil.validate_and_round_vhd_size(int(size))
1924 volume_size = LinstorVhdUtil.compute_volume_size(size, self.vdi_type)
1925 util.SMlog(
1926 'LinstorVDI.resize: type={}, vhd-size={}, volume-size={}'
1927 .format(self.vdi_type, size, volume_size)
1928 )
1930 if size < self.size:
1931 util.SMlog(
1932 'vdi_resize: shrinking not supported: '
1933 '(current size: {}, new size: {})'.format(self.size, size)
1934 )
1935 raise xs_errors.XenError('VDISize', opterr='shrinking not allowed')
1937 if size == self.size:
1938 return VDI.VDI.get_params(self)
1940 if self.vdi_type == vhdutil.VDI_TYPE_RAW:
1941 old_volume_size = self.size
1942 new_volume_size = LinstorVolumeManager.round_up_volume_size(size)
1943 else:
1944 old_volume_size = self.utilisation
1945 if self.sr._provisioning == 'thin':
1946 # VDI is currently deflated, so keep it deflated.
1947 new_volume_size = old_volume_size
1948 else:
1949 new_volume_size = LinstorVhdUtil.compute_volume_size(size, self.vdi_type)
1950 assert new_volume_size >= old_volume_size
1952 space_needed = new_volume_size - old_volume_size
1953 self.sr._ensure_space_available(space_needed)
1955 old_size = self.size
1956 if self.vdi_type == vhdutil.VDI_TYPE_RAW:
1957 self._linstor.resize(self.uuid, new_volume_size)
1958 else:
1959 if new_volume_size != old_volume_size:
1960 self.sr._vhdutil.inflate(
1961 self.sr._journaler, self._linstor, self.uuid, self.path,
1962 new_volume_size, old_volume_size
1963 )
1964 self.sr._vhdutil.set_size_virt_fast(self.path, size)
1966 # Reload size attributes.
1967 self._load_this()
1969 vdi_ref = self.sr.srcmd.params['vdi_ref']
1970 self.session.xenapi.VDI.set_virtual_size(vdi_ref, str(self.size))
1971 self.session.xenapi.VDI.set_physical_utilisation(
1972 vdi_ref, str(self.utilisation)
1973 )
1974 self.sr._update_stats(self.size - old_size)
1975 return VDI.VDI.get_params(self)
1977 def clone(self, sr_uuid, vdi_uuid):
1978 return self._do_snapshot(sr_uuid, vdi_uuid, VDI.SNAPSHOT_DOUBLE)
1980 def compose(self, sr_uuid, vdi1, vdi2):
1981 util.SMlog('VDI.compose for {} -> {}'.format(vdi2, vdi1))
1982 if self.vdi_type != vhdutil.VDI_TYPE_VHD:
1983 raise xs_errors.XenError('Unimplemented')
1985 parent_uuid = vdi1
1986 parent_path = self._linstor.get_device_path(parent_uuid)
1988 # We must pause tapdisk to correctly change the parent. Otherwise we
1989 # have a readonly error.
1990 # See: https://github.com/xapi-project/xen-api/blob/b3169a16d36dae0654881b336801910811a399d9/ocaml/xapi/storage_migrate.ml#L928-L929
1991 # and: https://github.com/xapi-project/xen-api/blob/b3169a16d36dae0654881b336801910811a399d9/ocaml/xapi/storage_migrate.ml#L775
1993 if not blktap2.VDI.tap_pause(self.session, self.sr.uuid, self.uuid):
1994 raise util.SMException('Failed to pause VDI {}'.format(self.uuid))
1995 try:
1996 self.sr._vhdutil.set_parent(self.path, parent_path, False)
1997 self.sr._vhdutil.set_hidden(parent_path)
1998 self.sr.session.xenapi.VDI.set_managed(
1999 self.sr.srcmd.params['args'][0], False
2000 )
2001 finally:
2002 blktap2.VDI.tap_unpause(self.session, self.sr.uuid, self.uuid)
2004 if not blktap2.VDI.tap_refresh(self.session, self.sr.uuid, self.uuid):
2005 raise util.SMException(
2006 'Failed to refresh VDI {}'.format(self.uuid)
2007 )
2009 util.SMlog('Compose done')
2011 def generate_config(self, sr_uuid, vdi_uuid):
2012 """
2013 Generate the XML config required to attach and activate
2014 a VDI for use when XAPI is not running. Attach and
2015 activation is handled by vdi_attach_from_config below.
2016 """
2018 util.SMlog('LinstorVDI.generate_config for {}'.format(self.uuid))
2020 resp = {}
2021 resp['device_config'] = self.sr.dconf
2022 resp['sr_uuid'] = sr_uuid
2023 resp['vdi_uuid'] = self.uuid
2024 resp['sr_sm_config'] = self.sr.sm_config
2025 resp['command'] = 'vdi_attach_from_config'
2027 # By default, we generate a normal config.
2028 # But if the disk is persistent, we must use a HTTP/NBD
2029 # server to ensure we can always write or read data.
2030 # Why? DRBD is unsafe when used with more than 4 hosts:
2031 # We are limited to use 1 diskless and 3 full.
2032 # We can't increase this limitation, so we use a NBD/HTTP device
2033 # instead.
2034 volume_name = self._linstor.get_volume_name(self.uuid)
2035 if not USE_HTTP_NBD_SERVERS or volume_name not in [
2036 HA_VOLUME_NAME, REDO_LOG_VOLUME_NAME
2037 ]:
2038 if not self.path or not util.pathexists(self.path):
2039 available = False
2040 # Try to refresh symlink path...
2041 try:
2042 self.path = self._linstor.get_device_path(vdi_uuid)
2043 available = util.pathexists(self.path)
2044 except Exception:
2045 pass
2046 if not available:
2047 raise xs_errors.XenError('VDIUnavailable')
2049 resp['vdi_path'] = self.path
2050 else:
2051 # Axiom: DRBD device is present on at least one host.
2052 resp['vdi_path'] = '/dev/http-nbd/' + volume_name
2054 config = xmlrpc.client.dumps(tuple([resp]), 'vdi_attach_from_config')
2055 return xmlrpc.client.dumps((config,), "", True)
2057 def attach_from_config(self, sr_uuid, vdi_uuid):
2058 """
2059 Attach and activate a VDI using config generated by
2060 vdi_generate_config above. This is used for cases such as
2061 the HA state-file and the redo-log.
2062 """
2064 util.SMlog('LinstorVDI.attach_from_config for {}'.format(vdi_uuid))
2066 try:
2067 if not util.pathexists(self.sr.path):
2068 self.sr.attach(sr_uuid)
2070 if not DRIVER_CONFIG['ATTACH_FROM_CONFIG_WITH_TAPDISK']:
2071 return self.attach(sr_uuid, vdi_uuid)
2072 except Exception:
2073 util.logException('LinstorVDI.attach_from_config')
2074 raise xs_errors.XenError(
2075 'SRUnavailable',
2076 opterr='Unable to attach from config'
2077 )
2079 def reset_leaf(self, sr_uuid, vdi_uuid):
2080 if self.vdi_type != vhdutil.VDI_TYPE_VHD:
2081 raise xs_errors.XenError('Unimplemented')
2083 if not self.sr._vhdutil.has_parent(self.uuid):
2084 raise util.SMException(
2085 'ERROR: VDI {} has no parent, will not reset contents'
2086 .format(self.uuid)
2087 )
2089 self.sr._vhdutil.kill_data(self.path)
2091 def _load_this(self):
2092 volume_metadata = None
2093 if self.sr._all_volume_metadata_cache:
2094 volume_metadata = self.sr._all_volume_metadata_cache.get(self.uuid)
2095 if volume_metadata is None:
2096 volume_metadata = self._linstor.get_volume_metadata(self.uuid)
2098 volume_info = None
2099 if self.sr._all_volume_info_cache:
2100 volume_info = self.sr._all_volume_info_cache.get(self.uuid)
2101 if volume_info is None:
2102 volume_info = self._linstor.get_volume_info(self.uuid)
2104 # Contains the max physical size used on a disk.
2105 # When LINSTOR LVM driver is used, the size should be similar to
2106 # virtual size (i.e. the LINSTOR max volume size).
2107 # When LINSTOR Thin LVM driver is used, the used physical size should
2108 # be lower than virtual size at creation.
2109 # The physical size increases after each write in a new block.
2110 self.utilisation = volume_info.allocated_size
2111 self.capacity = volume_info.virtual_size
2113 if self.vdi_type == vhdutil.VDI_TYPE_RAW:
2114 self.hidden = int(volume_metadata.get(HIDDEN_TAG) or 0)
2115 self.size = volume_info.virtual_size
2116 self.parent = ''
2117 else:
2118 vhd_info = self.sr._vhdutil.get_vhd_info(self.uuid)
2119 self.hidden = vhd_info.hidden
2120 self.size = vhd_info.sizeVirt
2121 self.parent = vhd_info.parentUuid
2123 if self.hidden:
2124 self.managed = False
2126 self.label = volume_metadata.get(NAME_LABEL_TAG) or ''
2127 self.description = volume_metadata.get(NAME_DESCRIPTION_TAG) or ''
2129 # Update sm_config_override of VDI parent class.
2130 self.sm_config_override = {'vhd-parent': self.parent or None}
2132 def _mark_hidden(self, hidden=True):
2133 if self.hidden == hidden:
2134 return
2136 if self.vdi_type == vhdutil.VDI_TYPE_VHD:
2137 self.sr._vhdutil.set_hidden(self.path, hidden)
2138 else:
2139 self._linstor.update_volume_metadata(self.uuid, {
2140 HIDDEN_TAG: hidden
2141 })
2142 self.hidden = hidden
2144 def update(self, sr_uuid, vdi_uuid):
2145 xenapi = self.session.xenapi
2146 vdi_ref = xenapi.VDI.get_by_uuid(self.uuid)
2148 volume_metadata = {
2149 NAME_LABEL_TAG: util.to_plain_string(
2150 xenapi.VDI.get_name_label(vdi_ref)
2151 ),
2152 NAME_DESCRIPTION_TAG: util.to_plain_string(
2153 xenapi.VDI.get_name_description(vdi_ref)
2154 )
2155 }
2157 try:
2158 self._linstor.update_volume_metadata(self.uuid, volume_metadata)
2159 except LinstorVolumeManagerError as e:
2160 if e.code == LinstorVolumeManagerError.ERR_VOLUME_NOT_EXISTS:
2161 raise xs_errors.XenError(
2162 'VDIUnavailable',
2163 opterr='LINSTOR volume {} not found'.format(self.uuid)
2164 )
2165 raise xs_errors.XenError('VDIUnavailable', opterr=str(e))
2167 # --------------------------------------------------------------------------
2168 # Thin provisioning.
2169 # --------------------------------------------------------------------------
2171 def _prepare_thin(self, attach):
2172 if self.sr._is_master:
2173 if attach:
2174 attach_thin(
2175 self.session, self.sr._journaler, self._linstor,
2176 self.sr.uuid, self.uuid
2177 )
2178 else:
2179 detach_thin(
2180 self.session, self._linstor, self.sr.uuid, self.uuid
2181 )
2182 else:
2183 fn = 'attach' if attach else 'detach'
2185 master = util.get_master_ref(self.session)
2187 args = {
2188 'groupName': self.sr._group_name,
2189 'srUuid': self.sr.uuid,
2190 'vdiUuid': self.uuid
2191 }
2193 try:
2194 self.sr._exec_manager_command(master, fn, args, 'VDIUnavailable')
2195 except Exception:
2196 if fn != 'detach':
2197 raise
2199 # Reload size attrs after inflate or deflate!
2200 self._load_this()
2201 self.sr._update_physical_size()
2203 vdi_ref = self.sr.srcmd.params['vdi_ref']
2204 self.session.xenapi.VDI.set_physical_utilisation(
2205 vdi_ref, str(self.utilisation)
2206 )
2208 self.session.xenapi.SR.set_physical_utilisation(
2209 self.sr.sr_ref, str(self.sr.physical_utilisation)
2210 )
2212 # --------------------------------------------------------------------------
2213 # Generic helpers.
2214 # --------------------------------------------------------------------------
2216 def _determine_type_and_path(self):
2217 """
2218 Determine whether this is a RAW or a VHD VDI.
2219 """
2221 # 1. Check vdi_ref and vdi_type in config.
2222 try:
2223 vdi_ref = self.session.xenapi.VDI.get_by_uuid(self.uuid)
2224 if vdi_ref:
2225 sm_config = self.session.xenapi.VDI.get_sm_config(vdi_ref)
2226 vdi_type = sm_config.get('vdi_type')
2227 if vdi_type:
2228 # Update parent fields.
2229 self.vdi_type = vdi_type
2230 self.sm_config_override = sm_config
2231 self._update_device_name(
2232 self._linstor.get_volume_name(self.uuid)
2233 )
2234 return
2235 except Exception:
2236 pass
2238 # 2. Otherwise use the LINSTOR volume manager directly.
2239 # It's probably a new VDI created via snapshot.
2240 volume_metadata = self._linstor.get_volume_metadata(self.uuid)
2241 self.vdi_type = volume_metadata.get(VDI_TYPE_TAG)
2242 if not self.vdi_type:
2243 raise xs_errors.XenError(
2244 'VDIUnavailable',
2245 opterr='failed to get vdi_type in metadata'
2246 )
2247 self._update_device_name(self._linstor.get_volume_name(self.uuid))
2249 def _update_device_name(self, device_name):
2250 self._device_name = device_name
2252 # Mark path of VDI parent class.
2253 if device_name:
2254 self.path = self._linstor.build_device_path(self._device_name)
2255 else:
2256 self.path = None
2258 def _create_snapshot(self, snap_uuid, snap_of_uuid=None):
2259 """
2260 Snapshot self and return the snapshot VDI object.
2261 """
2263 # 1. Create a new LINSTOR volume with the same size than self.
2264 snap_path = self._linstor.shallow_clone_volume(
2265 self.uuid, snap_uuid, persistent=False
2266 )
2268 # 2. Write the snapshot content.
2269 is_raw = (self.vdi_type == vhdutil.VDI_TYPE_RAW)
2270 self.sr._vhdutil.snapshot(
2271 snap_path, self.path, is_raw, self.MAX_METADATA_VIRT_SIZE
2272 )
2274 # 3. Get snapshot parent.
2275 snap_parent = self.sr._vhdutil.get_parent(snap_uuid)
2277 # 4. Update metadata.
2278 util.SMlog('Set VDI {} metadata of snapshot'.format(snap_uuid))
2279 volume_metadata = {
2280 NAME_LABEL_TAG: util.to_plain_string(self.label),
2281 NAME_DESCRIPTION_TAG: util.to_plain_string(self.description),
2282 IS_A_SNAPSHOT_TAG: bool(snap_of_uuid),
2283 SNAPSHOT_OF_TAG: snap_of_uuid,
2284 SNAPSHOT_TIME_TAG: '',
2285 TYPE_TAG: self.ty,
2286 VDI_TYPE_TAG: vhdutil.VDI_TYPE_VHD,
2287 READ_ONLY_TAG: False,
2288 METADATA_OF_POOL_TAG: ''
2289 }
2290 self._linstor.set_volume_metadata(snap_uuid, volume_metadata)
2292 # 5. Set size.
2293 snap_vdi = LinstorVDI(self.sr, snap_uuid)
2294 if not snap_vdi._exists:
2295 raise xs_errors.XenError('VDISnapshot')
2297 volume_info = self._linstor.get_volume_info(snap_uuid)
2299 snap_vdi.size = self.sr._vhdutil.get_size_virt(snap_uuid)
2300 snap_vdi.utilisation = volume_info.allocated_size
2302 # 6. Update sm config.
2303 snap_vdi.sm_config = {}
2304 snap_vdi.sm_config['vdi_type'] = snap_vdi.vdi_type
2305 if snap_parent:
2306 snap_vdi.sm_config['vhd-parent'] = snap_parent
2307 snap_vdi.parent = snap_parent
2309 snap_vdi.label = self.label
2310 snap_vdi.description = self.description
2312 self._linstor.mark_volume_as_persistent(snap_uuid)
2314 return snap_vdi
2316 # --------------------------------------------------------------------------
2317 # Implement specific SR methods.
2318 # --------------------------------------------------------------------------
2320 def _rename(self, oldpath, newpath):
2321 # TODO: I'm not sure... Used by CBT.
2322 volume_uuid = self._linstor.get_volume_uuid_from_device_path(oldpath)
2323 self._linstor.update_volume_name(volume_uuid, newpath)
2325 def _do_snapshot(
2326 self, sr_uuid, vdi_uuid, snap_type, secondary=None, cbtlog=None
2327 ):
2328 # If cbt enabled, save file consistency state.
2329 if cbtlog is not None:
2330 if blktap2.VDI.tap_status(self.session, vdi_uuid):
2331 consistency_state = False
2332 else:
2333 consistency_state = True
2334 util.SMlog(
2335 'Saving log consistency state of {} for vdi: {}'
2336 .format(consistency_state, vdi_uuid)
2337 )
2338 else:
2339 consistency_state = None
2341 if self.vdi_type != vhdutil.VDI_TYPE_VHD:
2342 raise xs_errors.XenError('Unimplemented')
2344 if not blktap2.VDI.tap_pause(self.session, sr_uuid, vdi_uuid):
2345 raise util.SMException('Failed to pause VDI {}'.format(vdi_uuid))
2346 try:
2347 return self._snapshot(snap_type, cbtlog, consistency_state)
2348 finally:
2349 blktap2.VDI.tap_unpause(self.session, sr_uuid, vdi_uuid, secondary)
2351 def _snapshot(self, snap_type, cbtlog=None, cbt_consistency=None):
2352 util.SMlog(
2353 'LinstorVDI._snapshot for {} (type {})'
2354 .format(self.uuid, snap_type)
2355 )
2357 # 1. Checks...
2358 if self.hidden:
2359 raise xs_errors.XenError('VDIClone', opterr='hidden VDI')
2361 depth = self.sr._vhdutil.get_depth(self.uuid)
2362 if depth == -1:
2363 raise xs_errors.XenError(
2364 'VDIUnavailable',
2365 opterr='failed to get VHD depth'
2366 )
2367 elif depth >= vhdutil.MAX_CHAIN_SIZE:
2368 raise xs_errors.XenError('SnapshotChainTooLong')
2370 # Ensure we have a valid path if we don't have a local diskful.
2371 self.sr._linstor.get_device_path(self.uuid)
2373 volume_path = self.path
2374 if not util.pathexists(volume_path):
2375 raise xs_errors.XenError(
2376 'EIO',
2377 opterr='IO error checking path {}'.format(volume_path)
2378 )
2380 # 2. Create base and snap uuid (if required) and a journal entry.
2381 base_uuid = util.gen_uuid()
2382 snap_uuid = None
2384 if snap_type == VDI.SNAPSHOT_DOUBLE:
2385 snap_uuid = util.gen_uuid()
2387 clone_info = '{}_{}'.format(base_uuid, snap_uuid)
2389 active_uuid = self.uuid
2390 self.sr._journaler.create(
2391 LinstorJournaler.CLONE, active_uuid, clone_info
2392 )
2394 try:
2395 # 3. Self becomes the new base.
2396 # The device path remains the same.
2397 self._linstor.update_volume_uuid(self.uuid, base_uuid)
2398 self.uuid = base_uuid
2399 self.location = self.uuid
2400 self.read_only = True
2401 self.managed = False
2403 # 4. Create snapshots (new active and snap).
2404 active_vdi = self._create_snapshot(active_uuid)
2406 snap_vdi = None
2407 if snap_type == VDI.SNAPSHOT_DOUBLE:
2408 snap_vdi = self._create_snapshot(snap_uuid, active_uuid)
2410 self.label = 'base copy'
2411 self.description = ''
2413 # 5. Mark the base VDI as hidden so that it does not show up
2414 # in subsequent scans.
2415 self._mark_hidden()
2416 self._linstor.update_volume_metadata(
2417 self.uuid, {READ_ONLY_TAG: True}
2418 )
2420 # 6. We must update the new active VDI with the "paused" and
2421 # "host_" properties. Why? Because the original VDI has been
2422 # paused and we we must unpause it after the snapshot.
2423 # See: `tap_unpause` in `blktap2.py`.
2424 vdi_ref = self.session.xenapi.VDI.get_by_uuid(active_uuid)
2425 sm_config = self.session.xenapi.VDI.get_sm_config(vdi_ref)
2426 for key in [x for x in sm_config.keys() if x == 'paused' or x.startswith('host_')]:
2427 active_vdi.sm_config[key] = sm_config[key]
2429 # 7. Verify parent locator field of both children and
2430 # delete base if unused.
2431 introduce_parent = True
2432 try:
2433 snap_parent = None
2434 if snap_vdi:
2435 snap_parent = snap_vdi.parent
2437 if active_vdi.parent != self.uuid and (
2438 snap_type == VDI.SNAPSHOT_SINGLE or
2439 snap_type == VDI.SNAPSHOT_INTERNAL or
2440 snap_parent != self.uuid
2441 ):
2442 util.SMlog(
2443 'Destroy unused base volume: {} (path={})'
2444 .format(self.uuid, self.path)
2445 )
2446 introduce_parent = False
2447 self._linstor.destroy_volume(self.uuid)
2448 except Exception as e:
2449 util.SMlog('Ignoring exception: {}'.format(e))
2450 pass
2452 # 8. Introduce the new VDI records.
2453 if snap_vdi:
2454 # If the parent is encrypted set the key_hash for the
2455 # new snapshot disk.
2456 vdi_ref = self.sr.srcmd.params['vdi_ref']
2457 sm_config = self.session.xenapi.VDI.get_sm_config(vdi_ref)
2458 # TODO: Maybe remove key_hash support.
2459 if 'key_hash' in sm_config:
2460 snap_vdi.sm_config['key_hash'] = sm_config['key_hash']
2461 # If we have CBT enabled on the VDI,
2462 # set CBT status for the new snapshot disk.
2463 if cbtlog:
2464 snap_vdi.cbt_enabled = True
2466 if snap_vdi:
2467 snap_vdi_ref = snap_vdi._db_introduce()
2468 util.SMlog(
2469 'vdi_clone: introduced VDI: {} ({})'
2470 .format(snap_vdi_ref, snap_vdi.uuid)
2471 )
2472 if introduce_parent:
2473 base_vdi_ref = self._db_introduce()
2474 self.session.xenapi.VDI.set_managed(base_vdi_ref, False)
2475 util.SMlog(
2476 'vdi_clone: introduced VDI: {} ({})'
2477 .format(base_vdi_ref, self.uuid)
2478 )
2479 self._linstor.update_volume_metadata(self.uuid, {
2480 NAME_LABEL_TAG: util.to_plain_string(self.label),
2481 NAME_DESCRIPTION_TAG: util.to_plain_string(
2482 self.description
2483 ),
2484 READ_ONLY_TAG: True,
2485 METADATA_OF_POOL_TAG: ''
2486 })
2488 # 9. Update cbt files if user created snapshot (SNAPSHOT_DOUBLE)
2489 if snap_type == VDI.SNAPSHOT_DOUBLE and cbtlog:
2490 try:
2491 self._cbt_snapshot(snap_uuid, cbt_consistency)
2492 except Exception:
2493 # CBT operation failed.
2494 # TODO: Implement me.
2495 raise
2497 if snap_type != VDI.SNAPSHOT_INTERNAL:
2498 self.sr._update_stats(self.size)
2500 # 10. Return info on the new user-visible leaf VDI.
2501 ret_vdi = snap_vdi
2502 if not ret_vdi:
2503 ret_vdi = self
2504 if not ret_vdi:
2505 ret_vdi = active_vdi
2507 vdi_ref = self.sr.srcmd.params['vdi_ref']
2508 self.session.xenapi.VDI.set_sm_config(
2509 vdi_ref, active_vdi.sm_config
2510 )
2511 except Exception as e:
2512 util.logException('Failed to snapshot!')
2513 try:
2514 self.sr._handle_interrupted_clone(
2515 active_uuid, clone_info, force_undo=True
2516 )
2517 self.sr._journaler.remove(LinstorJournaler.CLONE, active_uuid)
2518 except Exception as e:
2519 util.SMlog(
2520 'WARNING: Failed to clean up failed snapshot: {}'
2521 .format(e)
2522 )
2523 raise xs_errors.XenError('VDIClone', opterr=str(e))
2525 self.sr._journaler.remove(LinstorJournaler.CLONE, active_uuid)
2527 return ret_vdi.get_params()
2529 @staticmethod
2530 def _start_persistent_http_server(volume_name):
2531 pid_path = None
2532 http_server = None
2534 try:
2535 if volume_name == HA_VOLUME_NAME:
2536 port = '8076'
2537 else:
2538 port = '8077'
2540 try:
2541 # Use a timeout call because XAPI may be unusable on startup
2542 # or if the host has been ejected. So in this case the call can
2543 # block indefinitely.
2544 session = util.timeout_call(5, util.get_localAPI_session)
2545 host_ip = util.get_this_host_address(session)
2546 except:
2547 # Fallback using the XHA file if session not available.
2548 host_ip, _ = get_ips_from_xha_config_file()
2549 if not host_ip:
2550 raise Exception(
2551 'Cannot start persistent HTTP server: no XAPI session, nor XHA config file'
2552 )
2554 arguments = [
2555 'http-disk-server',
2556 '--disk',
2557 '/dev/drbd/by-res/{}/0'.format(volume_name),
2558 '--ip',
2559 host_ip,
2560 '--port',
2561 port
2562 ]
2564 util.SMlog('Starting {} on port {}...'.format(arguments[0], port))
2565 http_server = subprocess.Popen(
2566 [FORK_LOG_DAEMON] + arguments,
2567 stdout=subprocess.PIPE,
2568 stderr=subprocess.STDOUT,
2569 # Ensure we use another group id to kill this process without
2570 # touch the current one.
2571 preexec_fn=os.setsid
2572 )
2574 pid_path = '/run/http-server-{}.pid'.format(volume_name)
2575 with open(pid_path, 'w') as pid_file:
2576 pid_file.write(str(http_server.pid))
2578 reg_server_ready = re.compile("Server ready!$")
2579 def is_ready():
2580 while http_server.poll() is None:
2581 line = http_server.stdout.readline()
2582 if reg_server_ready.search(line):
2583 return True
2584 return False
2585 try:
2586 if not util.timeout_call(10, is_ready):
2587 raise Exception('Failed to wait HTTP server startup, bad output')
2588 except util.TimeoutException:
2589 raise Exception('Failed to wait for HTTP server startup during given delay')
2590 except Exception as e:
2591 if pid_path:
2592 try:
2593 os.remove(pid_path)
2594 except Exception:
2595 pass
2597 if http_server:
2598 # Kill process and children in this case...
2599 try:
2600 os.killpg(os.getpgid(http_server.pid), signal.SIGTERM)
2601 except:
2602 pass
2604 raise xs_errors.XenError(
2605 'VDIUnavailable',
2606 opterr='Failed to start http-server: {}'.format(e)
2607 )
2609 def _start_persistent_nbd_server(self, volume_name):
2610 pid_path = None
2611 nbd_path = None
2612 nbd_server = None
2614 try:
2615 # We use a precomputed device size.
2616 # So if the XAPI is modified, we must update these values!
2617 if volume_name == HA_VOLUME_NAME:
2618 # See: https://github.com/xapi-project/xen-api/blob/703479fa448a8d7141954bb6e8964d8e25c4ac2e/ocaml/xapi/xha_statefile.ml#L32-L37
2619 port = '8076'
2620 device_size = 4 * 1024 * 1024
2621 else:
2622 # See: https://github.com/xapi-project/xen-api/blob/703479fa448a8d7141954bb6e8964d8e25c4ac2e/ocaml/database/redo_log.ml#L41-L44
2623 port = '8077'
2624 device_size = 256 * 1024 * 1024
2626 try:
2627 session = util.timeout_call(5, util.get_localAPI_session)
2628 ips = util.get_host_addresses(session)
2629 except Exception as e:
2630 _, ips = get_ips_from_xha_config_file()
2631 if not ips:
2632 raise Exception(
2633 'Cannot start persistent NBD server: no XAPI session, nor XHA config file ({})'.format(e)
2634 )
2635 ips = ips.values()
2637 arguments = [
2638 'nbd-http-server',
2639 '--socket-path',
2640 '/run/{}.socket'.format(volume_name),
2641 '--nbd-name',
2642 volume_name,
2643 '--urls',
2644 ','.join(['http://' + ip + ':' + port for ip in ips]),
2645 '--device-size',
2646 str(device_size)
2647 ]
2649 util.SMlog('Starting {} using port {}...'.format(arguments[0], port))
2650 nbd_server = subprocess.Popen(
2651 [FORK_LOG_DAEMON] + arguments,
2652 stdout=subprocess.PIPE,
2653 stderr=subprocess.STDOUT,
2654 # Ensure we use another group id to kill this process without
2655 # touch the current one.
2656 preexec_fn=os.setsid
2657 )
2659 pid_path = '/run/nbd-server-{}.pid'.format(volume_name)
2660 with open(pid_path, 'w') as pid_file:
2661 pid_file.write(str(nbd_server.pid))
2663 reg_nbd_path = re.compile("NBD `(/dev/nbd[0-9]+)` is now attached.$")
2664 def get_nbd_path():
2665 while nbd_server.poll() is None:
2666 line = nbd_server.stdout.readline()
2667 match = reg_nbd_path.search(line)
2668 if match:
2669 return match.group(1)
2670 # Use a timeout to never block the smapi if there is a problem.
2671 try:
2672 nbd_path = util.timeout_call(10, get_nbd_path)
2673 if nbd_path is None:
2674 raise Exception('Empty NBD path (NBD server is probably dead)')
2675 except util.TimeoutException:
2676 raise Exception('Unable to read NBD path')
2678 util.SMlog('Create symlink: {} -> {}'.format(self.path, nbd_path))
2679 os.symlink(nbd_path, self.path)
2680 except Exception as e:
2681 if pid_path:
2682 try:
2683 os.remove(pid_path)
2684 except Exception:
2685 pass
2687 if nbd_path:
2688 try:
2689 os.remove(nbd_path)
2690 except Exception:
2691 pass
2693 if nbd_server:
2694 # Kill process and children in this case...
2695 try:
2696 os.killpg(os.getpgid(nbd_server.pid), signal.SIGTERM)
2697 except:
2698 pass
2700 raise xs_errors.XenError(
2701 'VDIUnavailable',
2702 opterr='Failed to start nbd-server: {}'.format(e)
2703 )
2705 @classmethod
2706 def _kill_persistent_server(self, type, volume_name, sig):
2707 try:
2708 path = '/run/{}-server-{}.pid'.format(type, volume_name)
2709 if not os.path.exists(path):
2710 return
2712 pid = None
2713 with open(path, 'r') as pid_file:
2714 try:
2715 pid = int(pid_file.read())
2716 except Exception:
2717 pass
2719 if pid is not None and util.check_pid_exists(pid):
2720 util.SMlog('Kill {} server {} (pid={})'.format(type, path, pid))
2721 try:
2722 os.killpg(os.getpgid(pid), sig)
2723 except Exception as e:
2724 util.SMlog('Failed to kill {} server: {}'.format(type, e))
2726 os.remove(path)
2727 except:
2728 pass
2730 @classmethod
2731 def _kill_persistent_http_server(self, volume_name, sig=signal.SIGTERM):
2732 return self._kill_persistent_server('nbd', volume_name, sig)
2734 @classmethod
2735 def _kill_persistent_nbd_server(self, volume_name, sig=signal.SIGTERM):
2736 return self._kill_persistent_server('http', volume_name, sig)
2738 def _check_http_nbd_volume_name(self):
2739 volume_name = self.path[14:]
2740 if volume_name not in [
2741 HA_VOLUME_NAME, REDO_LOG_VOLUME_NAME
2742 ]:
2743 raise xs_errors.XenError(
2744 'VDIUnavailable',
2745 opterr='Unsupported path: {}'.format(self.path)
2746 )
2747 return volume_name
2749 def _attach_using_http_nbd(self):
2750 volume_name = self._check_http_nbd_volume_name()
2752 # Ensure there is no NBD and HTTP server running.
2753 self._kill_persistent_nbd_server(volume_name)
2754 self._kill_persistent_http_server(volume_name)
2756 # 0. Fetch drbd path.
2757 must_get_device_path = True
2758 if not self.sr._is_master:
2759 # We are on a slave, we must try to find a diskful locally.
2760 try:
2761 volume_info = self._linstor.get_volume_info(self.uuid)
2762 except Exception as e:
2763 raise xs_errors.XenError(
2764 'VDIUnavailable',
2765 opterr='Cannot get volume info of {}: {}'
2766 .format(self.uuid, e)
2767 )
2769 hostname = socket.gethostname()
2770 must_get_device_path = hostname in volume_info.diskful
2772 drbd_path = None
2773 if must_get_device_path or self.sr._is_master:
2774 # If we are master, we must ensure we have a diskless
2775 # or diskful available to init HA.
2776 # It also avoid this error in xensource.log
2777 # (/usr/libexec/xapi/cluster-stack/xhad/ha_set_pool_state):
2778 # init exited with code 8 [stdout = ''; stderr = 'SF: failed to write in State-File \x10 (fd 4208696). (sys 28)\x0A']
2779 # init returned MTC_EXIT_CAN_NOT_ACCESS_STATEFILE (State-File is inaccessible)
2780 available = False
2781 try:
2782 drbd_path = self._linstor.get_device_path(self.uuid)
2783 available = util.pathexists(drbd_path)
2784 except Exception:
2785 pass
2787 if not available:
2788 raise xs_errors.XenError(
2789 'VDIUnavailable',
2790 opterr='Cannot get device path of {}'.format(self.uuid)
2791 )
2793 # 1. Prepare http-nbd folder.
2794 try:
2795 if not os.path.exists('/dev/http-nbd/'):
2796 os.makedirs('/dev/http-nbd/')
2797 elif os.path.islink(self.path):
2798 os.remove(self.path)
2799 except OSError as e:
2800 if e.errno != errno.EEXIST:
2801 raise xs_errors.XenError(
2802 'VDIUnavailable',
2803 opterr='Cannot prepare http-nbd: {}'.format(e)
2804 )
2806 # 2. Start HTTP service if we have a diskful or if we are master.
2807 http_service = None
2808 if drbd_path:
2809 assert(drbd_path in (
2810 '/dev/drbd/by-res/{}/0'.format(HA_VOLUME_NAME),
2811 '/dev/drbd/by-res/{}/0'.format(REDO_LOG_VOLUME_NAME)
2812 ))
2813 self._start_persistent_http_server(volume_name)
2815 # 3. Start NBD server in all cases.
2816 try:
2817 self._start_persistent_nbd_server(volume_name)
2818 except Exception as e:
2819 if drbd_path:
2820 self._kill_persistent_http_server(volume_name)
2821 raise
2823 self.attached = True
2824 return VDI.VDI.attach(self, self.sr.uuid, self.uuid)
2826 def _detach_using_http_nbd(self):
2827 volume_name = self._check_http_nbd_volume_name()
2828 self._kill_persistent_nbd_server(volume_name)
2829 self._kill_persistent_http_server(volume_name)
2831# ------------------------------------------------------------------------------
2834if __name__ == '__main__':
2835 def run():
2836 SRCommand.run(LinstorSR, DRIVER_INFO)
2838 if not TRACE_PERFS:
2839 run()
2840 else:
2841 util.make_profile('LinstorSR', run)
2842else:
2843 SR.registerSR(LinstorSR)