Coverage for drivers/LinstorSR.py : 7%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1#!/usr/bin/env python3
2#
3# Copyright (C) 2020 Vates SAS - ronan.abhamon@vates.fr
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program. If not, see <https://www.gnu.org/licenses/>.
17from constants import CBTLOG_TAG
19try:
20 from linstorjournaler import LinstorJournaler
21 from linstorvhdutil import LinstorVhdUtil
22 from linstorvolumemanager import get_controller_uri
23 from linstorvolumemanager import get_controller_node_name
24 from linstorvolumemanager import LinstorVolumeManager
25 from linstorvolumemanager import LinstorVolumeManagerError
26 from linstorvolumemanager import PERSISTENT_PREFIX
28 LINSTOR_AVAILABLE = True
29except ImportError:
30 PERSISTENT_PREFIX = 'unknown'
32 LINSTOR_AVAILABLE = False
34from lock import Lock
35import blktap2
36import cleanup
37import distutils
38import errno
39import functools
40import lvutil
41import os
42import re
43import scsiutil
44import signal
45import socket
46import SR
47import SRCommand
48import subprocess
49import time
50import traceback
51import util
52import VDI
53import vhdutil
54import xml.etree.ElementTree as xml_parser
55import xmlrpc.client
56import xs_errors
58from srmetadata import \
59 NAME_LABEL_TAG, NAME_DESCRIPTION_TAG, IS_A_SNAPSHOT_TAG, SNAPSHOT_OF_TAG, \
60 TYPE_TAG, VDI_TYPE_TAG, READ_ONLY_TAG, SNAPSHOT_TIME_TAG, \
61 METADATA_OF_POOL_TAG
63HIDDEN_TAG = 'hidden'
65XHA_CONFIG_PATH = '/etc/xensource/xhad.conf'
67FORK_LOG_DAEMON = '/opt/xensource/libexec/fork-log-daemon'
69# This flag can be disabled to debug the DRBD layer.
70# When this config var is False, the HA can only be used under
71# specific conditions:
72# - Only one heartbeat diskless VDI is present in the pool.
73# - The other hearbeat volumes must be diskful and limited to a maximum of 3.
74USE_HTTP_NBD_SERVERS = True
76# Useful flag to trace calls using cProfile.
77TRACE_PERFS = False
79# Enable/Disable VHD key hash support.
80USE_KEY_HASH = False
82# Special volumes.
83HA_VOLUME_NAME = PERSISTENT_PREFIX + 'ha-statefile'
84REDO_LOG_VOLUME_NAME = PERSISTENT_PREFIX + 'redo-log'
86# ==============================================================================
88# TODO: Supports 'VDI_INTRODUCE', 'VDI_RESET_ON_BOOT/2', 'SR_TRIM',
89# 'VDI_CONFIG_CBT', 'SR_PROBE'
91CAPABILITIES = [
92 'ATOMIC_PAUSE',
93 'SR_UPDATE',
94 'VDI_CREATE',
95 'VDI_DELETE',
96 'VDI_UPDATE',
97 'VDI_ATTACH',
98 'VDI_DETACH',
99 'VDI_ACTIVATE',
100 'VDI_DEACTIVATE',
101 'VDI_CLONE',
102 'VDI_MIRROR',
103 'VDI_RESIZE',
104 'VDI_SNAPSHOT',
105 'VDI_GENERATE_CONFIG'
106]
108CONFIGURATION = [
109 ['group-name', 'LVM group name'],
110 ['redundancy', 'replication count'],
111 ['provisioning', '"thin" or "thick" are accepted (optional, defaults to thin)'],
112 ['monitor-db-quorum', 'disable controller when only one host is online (optional, defaults to true)']
113]
115DRIVER_INFO = {
116 'name': 'LINSTOR resources on XCP-ng',
117 'description': 'SR plugin which uses Linstor to manage VDIs',
118 'vendor': 'Vates',
119 'copyright': '(C) 2020 Vates',
120 'driver_version': '1.0',
121 'required_api_version': '1.0',
122 'capabilities': CAPABILITIES,
123 'configuration': CONFIGURATION
124}
126DRIVER_CONFIG = {'ATTACH_FROM_CONFIG_WITH_TAPDISK': False}
128OPS_EXCLUSIVE = [
129 'sr_create', 'sr_delete', 'sr_attach', 'sr_detach', 'sr_scan',
130 'sr_update', 'sr_probe', 'vdi_init', 'vdi_create', 'vdi_delete',
131 'vdi_attach', 'vdi_detach', 'vdi_clone', 'vdi_snapshot',
132]
134# ==============================================================================
135# Misc helpers used by LinstorSR and linstor-thin plugin.
136# ==============================================================================
139def attach_thin(session, journaler, linstor, sr_uuid, vdi_uuid):
140 volume_metadata = linstor.get_volume_metadata(vdi_uuid)
141 image_type = volume_metadata.get(VDI_TYPE_TAG)
142 if image_type == vhdutil.VDI_TYPE_RAW:
143 return
145 device_path = linstor.get_device_path(vdi_uuid)
147 # If the virtual VHD size is lower than the LINSTOR volume size,
148 # there is nothing to do.
149 vhd_size = LinstorVhdUtil.compute_volume_size(
150 # TODO: Replace pylint comment with this feature when possible:
151 # https://github.com/PyCQA/pylint/pull/2926
152 LinstorVhdUtil(session, linstor).get_size_virt(vdi_uuid), # pylint: disable = E1120
153 image_type
154 )
156 volume_info = linstor.get_volume_info(vdi_uuid)
157 volume_size = volume_info.virtual_size
159 if vhd_size > volume_size:
160 LinstorVhdUtil(session, linstor).inflate(
161 journaler, vdi_uuid, device_path, vhd_size, volume_size
162 )
165def detach_thin_impl(session, linstor, sr_uuid, vdi_uuid):
166 volume_metadata = linstor.get_volume_metadata(vdi_uuid)
167 image_type = volume_metadata.get(VDI_TYPE_TAG)
168 if image_type == vhdutil.VDI_TYPE_RAW:
169 return
171 def check_vbd_count():
172 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
173 vbds = session.xenapi.VBD.get_all_records_where(
174 'field "VDI" = "{}"'.format(vdi_ref)
175 )
177 num_plugged = 0
178 for vbd_rec in vbds.values():
179 if vbd_rec['currently_attached']:
180 num_plugged += 1
181 if num_plugged > 1:
182 raise xs_errors.XenError(
183 'VDIUnavailable',
184 opterr='Cannot deflate VDI {}, already used by '
185 'at least 2 VBDs'.format(vdi_uuid)
186 )
188 # We can have multiple VBDs attached to a VDI during a VM-template clone.
189 # So we use a timeout to ensure that we can detach the volume properly.
190 util.retry(check_vbd_count, maxretry=10, period=1)
192 device_path = linstor.get_device_path(vdi_uuid)
193 vhdutil_inst = LinstorVhdUtil(session, linstor)
194 new_volume_size = LinstorVolumeManager.round_up_volume_size(
195 # TODO: Replace pylint comment with this feature when possible:
196 # https://github.com/PyCQA/pylint/pull/2926
197 vhdutil_inst.get_size_phys(vdi_uuid) # pylint: disable = E1120
198 )
200 volume_info = linstor.get_volume_info(vdi_uuid)
201 old_volume_size = volume_info.virtual_size
202 vhdutil_inst.deflate(device_path, new_volume_size, old_volume_size)
205def detach_thin(session, linstor, sr_uuid, vdi_uuid):
206 # This function must always return without errors.
207 # Otherwise it could cause errors in the XAPI regarding the state of the VDI.
208 # It's why we use this `try` block.
209 try:
210 detach_thin_impl(session, linstor, sr_uuid, vdi_uuid)
211 except Exception as e:
212 util.SMlog('Failed to detach properly VDI {}: {}'.format(vdi_uuid, e))
215def get_ips_from_xha_config_file():
216 ips = dict()
217 host_id = None
218 try:
219 # Ensure there is no dirty read problem.
220 # For example if the HA is reloaded.
221 tree = util.retry(
222 lambda: xml_parser.parse(XHA_CONFIG_PATH),
223 maxretry=10,
224 period=1
225 )
226 except:
227 return (None, ips)
229 def parse_host_nodes(ips, node):
230 current_id = None
231 current_ip = None
233 for sub_node in node:
234 if sub_node.tag == 'IPaddress':
235 current_ip = sub_node.text
236 elif sub_node.tag == 'HostID':
237 current_id = sub_node.text
238 else:
239 continue
241 if current_id and current_ip:
242 ips[current_id] = current_ip
243 return
244 util.SMlog('Ill-formed XHA file, missing IPaddress or/and HostID')
246 def parse_common_config(ips, node):
247 for sub_node in node:
248 if sub_node.tag == 'host':
249 parse_host_nodes(ips, sub_node)
251 def parse_local_config(ips, node):
252 for sub_node in node:
253 if sub_node.tag == 'localhost':
254 for host_node in sub_node:
255 if host_node.tag == 'HostID':
256 return host_node.text
258 for node in tree.getroot():
259 if node.tag == 'common-config':
260 parse_common_config(ips, node)
261 elif node.tag == 'local-config':
262 host_id = parse_local_config(ips, node)
263 else:
264 continue
266 if ips and host_id:
267 break
269 return (host_id and ips.get(host_id), ips)
272def activate_lvm_group(group_name):
273 path = group_name.split('/')
274 assert path and len(path) <= 2
275 try:
276 lvutil.setActiveVG(path[0], True)
277 except Exception as e:
278 util.SMlog('Cannot active VG `{}`: {}'.format(path[0], e))
280# ==============================================================================
282# Usage example:
283# xe sr-create type=linstor name-label=linstor-sr
284# host-uuid=d2deba7a-c5ad-4de1-9a20-5c8df3343e93
285# device-config:group-name=vg_loop device-config:redundancy=2
288class LinstorSR(SR.SR):
289 DRIVER_TYPE = 'linstor'
291 PROVISIONING_TYPES = ['thin', 'thick']
292 PROVISIONING_DEFAULT = 'thin'
294 MANAGER_PLUGIN = 'linstor-manager'
296 INIT_STATUS_NOT_SET = 0
297 INIT_STATUS_IN_PROGRESS = 1
298 INIT_STATUS_OK = 2
299 INIT_STATUS_FAIL = 3
301 # --------------------------------------------------------------------------
302 # SR methods.
303 # --------------------------------------------------------------------------
305 @staticmethod
306 def handles(type):
307 return type == LinstorSR.DRIVER_TYPE
309 def load(self, sr_uuid):
310 if not LINSTOR_AVAILABLE:
311 raise util.SMException(
312 'Can\'t load LinstorSR: LINSTOR libraries are missing'
313 )
315 # Check parameters.
316 if 'group-name' not in self.dconf or not self.dconf['group-name']:
317 raise xs_errors.XenError('LinstorConfigGroupNameMissing')
318 if 'redundancy' not in self.dconf or not self.dconf['redundancy']:
319 raise xs_errors.XenError('LinstorConfigRedundancyMissing')
321 self.driver_config = DRIVER_CONFIG
323 # Check provisioning config.
324 provisioning = self.dconf.get('provisioning')
325 if provisioning:
326 if provisioning in self.PROVISIONING_TYPES:
327 self._provisioning = provisioning
328 else:
329 raise xs_errors.XenError(
330 'InvalidArg',
331 opterr='Provisioning parameter must be one of {}'.format(
332 self.PROVISIONING_TYPES
333 )
334 )
335 else:
336 self._provisioning = self.PROVISIONING_DEFAULT
338 monitor_db_quorum = self.dconf.get('monitor-db-quorum')
339 self._monitor_db_quorum = (monitor_db_quorum is None) or \
340 distutils.util.strtobool(monitor_db_quorum)
342 # Note: We don't have access to the session field if the
343 # 'vdi_attach_from_config' command is executed.
344 self._has_session = self.sr_ref and self.session is not None
345 if self._has_session:
346 self.sm_config = self.session.xenapi.SR.get_sm_config(self.sr_ref)
347 else:
348 self.sm_config = self.srcmd.params.get('sr_sm_config') or {}
350 provisioning = self.sm_config.get('provisioning')
351 if provisioning in self.PROVISIONING_TYPES:
352 self._provisioning = provisioning
354 # Define properties for SR parent class.
355 self.ops_exclusive = OPS_EXCLUSIVE
356 self.path = LinstorVolumeManager.DEV_ROOT_PATH
357 self.lock = Lock(vhdutil.LOCK_TYPE_SR, self.uuid)
358 self.sr_vditype = SR.DEFAULT_TAP
360 if self.cmd == 'sr_create':
361 self._redundancy = int(self.dconf['redundancy']) or 1
362 self._linstor = None # Ensure that LINSTOR attribute exists.
363 self._journaler = None
365 self._is_master = False
366 if 'SRmaster' in self.dconf and self.dconf['SRmaster'] == 'true':
367 self._is_master = True
368 self._group_name = self.dconf['group-name']
370 self._vdi_shared_time = 0
372 self._init_status = self.INIT_STATUS_NOT_SET
374 self._vdis_loaded = False
375 self._all_volume_info_cache = None
376 self._all_volume_metadata_cache = None
378 def _locked_load(method):
379 def wrapped_method(self, *args, **kwargs):
380 self._init_status = self.INIT_STATUS_OK
381 return method(self, *args, **kwargs)
383 def load(self, *args, **kwargs):
384 # Activate all LVMs to make drbd-reactor happy.
385 if self.srcmd.cmd == '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, already used by PBD {}'.format(
573 xenapi.PBD.get_uuid(pbd)
574 )
575 )
577 if srs:
578 raise xs_errors.XenError(
579 'LinstorSRCreate',
580 opterr='LINSTOR SR must be unique in a pool'
581 )
583 online_hosts = util.get_online_hosts(self.session)
584 if len(online_hosts) < len(host_adresses):
585 raise xs_errors.XenError(
586 'LinstorSRCreate',
587 opterr='Not enough online hosts'
588 )
590 ips = {}
591 for host_ref in online_hosts:
592 record = self.session.xenapi.host.get_record(host_ref)
593 hostname = record['hostname']
594 ips[hostname] = record['address']
596 if len(ips) != len(online_hosts):
597 raise xs_errors.XenError(
598 'LinstorSRCreate',
599 opterr='Multiple hosts with same hostname'
600 )
602 # Ensure ports are opened and LINSTOR satellites
603 # are activated. In the same time the drbd-reactor instances
604 # must be stopped.
605 self._prepare_sr_on_all_hosts(self._group_name, enabled=True)
607 # Create SR.
608 # Throw if the SR already exists.
609 try:
610 self._linstor = LinstorVolumeManager.create_sr(
611 self._group_name,
612 ips,
613 self._redundancy,
614 thin_provisioning=self._provisioning == 'thin',
615 auto_quorum=self._monitor_db_quorum,
616 logger=util.SMlog
617 )
618 self._vhdutil = LinstorVhdUtil(self.session, self._linstor)
619 except Exception as e:
620 util.SMlog('Failed to create LINSTOR SR: {}'.format(e))
621 raise xs_errors.XenError('LinstorSRCreate', opterr=str(e))
623 try:
624 util.SMlog(
625 "Finishing SR creation, enable drbd-reactor on all hosts..."
626 )
627 self._update_drbd_reactor_on_all_hosts(enabled=True)
628 except Exception as e:
629 try:
630 self._linstor.destroy()
631 except Exception as e2:
632 util.SMlog(
633 'Failed to destroy LINSTOR SR after creation fail: {}'
634 .format(e2)
635 )
636 raise e
638 @_locked_load
639 def delete(self, uuid):
640 util.SMlog('LinstorSR.delete for {}'.format(self.uuid))
641 cleanup.gc_force(self.session, self.uuid)
643 if self.vdis or self._linstor._volumes:
644 raise xs_errors.XenError('SRNotEmpty')
646 node_name = get_controller_node_name()
647 if not node_name:
648 raise xs_errors.XenError(
649 'LinstorSRDelete',
650 opterr='Cannot get controller node name'
651 )
653 host = None
654 if node_name == 'localhost':
655 host = util.get_this_host_ref(self.session)
656 else:
657 for slave in util.get_all_slaves(self.session):
658 r_name = self.session.xenapi.host.get_record(slave)['hostname']
659 if r_name == node_name:
660 host = slave
661 break
663 if not host:
664 raise xs_errors.XenError(
665 'LinstorSRDelete',
666 opterr='Failed to find host with hostname: {}'.format(
667 node_name
668 )
669 )
671 try:
672 self._update_drbd_reactor_on_all_hosts(
673 controller_node_name=node_name, enabled=False
674 )
676 args = {
677 'groupName': self._group_name,
678 }
679 self._exec_manager_command(
680 host, 'destroy', args, 'LinstorSRDelete'
681 )
682 except Exception as e:
683 try:
684 self._update_drbd_reactor_on_all_hosts(
685 controller_node_name=node_name, enabled=True
686 )
687 except Exception as e2:
688 util.SMlog(
689 'Failed to restart drbd-reactor after destroy fail: {}'
690 .format(e2)
691 )
692 util.SMlog('Failed to delete LINSTOR SR: {}'.format(e))
693 raise xs_errors.XenError(
694 'LinstorSRDelete',
695 opterr=str(e)
696 )
698 Lock.cleanupAll(self.uuid)
700 @_locked_load
701 def update(self, uuid):
702 util.SMlog('LinstorSR.update for {}'.format(self.uuid))
704 # Well, how can we update a SR if it doesn't exist? :thinking:
705 if not self._linstor:
706 raise xs_errors.XenError(
707 'SRUnavailable',
708 opterr='no such volume group: {}'.format(self._group_name)
709 )
711 self._update_stats(0)
713 # Update the SR name and description only in LINSTOR metadata.
714 xenapi = self.session.xenapi
715 self._linstor.metadata = {
716 NAME_LABEL_TAG: util.to_plain_string(
717 xenapi.SR.get_name_label(self.sr_ref)
718 ),
719 NAME_DESCRIPTION_TAG: util.to_plain_string(
720 xenapi.SR.get_name_description(self.sr_ref)
721 )
722 }
724 @_locked_load
725 def attach(self, uuid):
726 util.SMlog('LinstorSR.attach for {}'.format(self.uuid))
728 if not self._linstor:
729 raise xs_errors.XenError(
730 'SRUnavailable',
731 opterr='no such group: {}'.format(self._group_name)
732 )
734 @_locked_load
735 def detach(self, uuid):
736 util.SMlog('LinstorSR.detach for {}'.format(self.uuid))
737 cleanup.abort(self.uuid)
739 @_locked_load
740 def probe(self):
741 util.SMlog('LinstorSR.probe for {}'.format(self.uuid))
742 # TODO
744 @_locked_load
745 def scan(self, uuid):
746 if self._init_status == self.INIT_STATUS_FAIL:
747 return
749 util.SMlog('LinstorSR.scan for {}'.format(self.uuid))
750 if not self._linstor:
751 raise xs_errors.XenError(
752 'SRUnavailable',
753 opterr='no such volume group: {}'.format(self._group_name)
754 )
756 # Note: `scan` can be called outside this module, so ensure the VDIs
757 # are loaded.
758 self._load_vdis()
759 self._update_physical_size()
761 for vdi_uuid in list(self.vdis.keys()):
762 if self.vdis[vdi_uuid].deleted:
763 del self.vdis[vdi_uuid]
765 # Security to prevent VDIs from being forgotten if the controller
766 # is started without a shared and mounted /var/lib/linstor path.
767 try:
768 self._linstor.get_database_path()
769 except Exception:
770 # Failed to get database path, ensure we don't have
771 # VDIs in the XAPI database...
772 if self.session.xenapi.SR.get_VDIs(
773 self.session.xenapi.SR.get_by_uuid(self.uuid)
774 ):
775 raise xs_errors.XenError(
776 'SRUnavailable',
777 opterr='Database is not mounted'
778 )
780 # Update the database before the restart of the GC to avoid
781 # bad sync in the process if new VDIs have been introduced.
782 super(LinstorSR, self).scan(self.uuid)
783 self._kick_gc()
785 @_locked_load
786 def vdi(self, uuid):
787 return LinstorVDI(self, uuid)
789 _locked_load = staticmethod(_locked_load)
791 # --------------------------------------------------------------------------
792 # Lock.
793 # --------------------------------------------------------------------------
795 def _shared_lock_vdi(self, vdi_uuid, locked=True):
796 master = util.get_master_ref(self.session)
798 command = 'lockVdi'
799 args = {
800 'groupName': self._group_name,
801 'srUuid': self.uuid,
802 'vdiUuid': vdi_uuid,
803 'locked': str(locked)
804 }
806 # Note: We must avoid to unlock the volume if the timeout is reached
807 # because during volume unlock, the SR lock is not used. Otherwise
808 # we could destroy a valid lock acquired from another host...
809 #
810 # This code is not very clean, the ideal solution would be to acquire
811 # the SR lock during volume unlock (like lock) but it's not easy
812 # to implement without impacting performance.
813 if not locked:
814 elapsed_time = time.time() - self._vdi_shared_time
815 timeout = LinstorVolumeManager.LOCKED_EXPIRATION_DELAY * 0.7
816 if elapsed_time >= timeout:
817 util.SMlog(
818 'Avoid unlock call of {} because timeout has been reached'
819 .format(vdi_uuid)
820 )
821 return
823 self._exec_manager_command(master, command, args, 'VDIUnavailable')
825 # --------------------------------------------------------------------------
826 # Network.
827 # --------------------------------------------------------------------------
829 def _exec_manager_command(self, host_ref, command, args, error):
830 host_rec = self.session.xenapi.host.get_record(host_ref)
831 host_uuid = host_rec['uuid']
833 try:
834 ret = self.session.xenapi.host.call_plugin(
835 host_ref, self.MANAGER_PLUGIN, command, args
836 )
837 except Exception as e:
838 util.SMlog(
839 'call-plugin on {} ({}:{} with {}) raised'.format(
840 host_uuid, self.MANAGER_PLUGIN, command, args
841 )
842 )
843 raise e
845 util.SMlog(
846 'call-plugin on {} ({}:{} with {}) returned: {}'.format(
847 host_uuid, self.MANAGER_PLUGIN, command, args, ret
848 )
849 )
850 if ret == 'False':
851 raise xs_errors.XenError(
852 error,
853 opterr='Plugin {} failed'.format(self.MANAGER_PLUGIN)
854 )
856 def _prepare_sr(self, host, group_name, enabled):
857 self._exec_manager_command(
858 host,
859 'prepareSr' if enabled else 'releaseSr',
860 {'groupName': group_name},
861 'SRUnavailable'
862 )
864 def _prepare_sr_on_all_hosts(self, group_name, enabled):
865 master = util.get_master_ref(self.session)
866 self._prepare_sr(master, group_name, enabled)
868 for slave in util.get_all_slaves(self.session):
869 self._prepare_sr(slave, group_name, enabled)
871 def _update_drbd_reactor(self, host, enabled):
872 self._exec_manager_command(
873 host,
874 'updateDrbdReactor',
875 {'enabled': str(enabled)},
876 'SRUnavailable'
877 )
879 def _update_drbd_reactor_on_all_hosts(
880 self, enabled, controller_node_name=None
881 ):
882 if controller_node_name == 'localhost':
883 controller_node_name = self.session.xenapi.host.get_record(
884 util.get_this_host_ref(self.session)
885 )['hostname']
886 assert controller_node_name
887 assert controller_node_name != 'localhost'
889 controller_host = None
890 secondary_hosts = []
892 hosts = self.session.xenapi.host.get_all_records()
893 for host_ref, host_rec in hosts.items():
894 hostname = host_rec['hostname']
895 if controller_node_name == hostname:
896 controller_host = host_ref
897 else:
898 secondary_hosts.append((host_ref, hostname))
900 action_name = 'Starting' if enabled else 'Stopping'
901 if controller_node_name and not controller_host:
902 util.SMlog('Failed to find controller host: `{}`'.format(
903 controller_node_name
904 ))
906 if enabled and controller_host:
907 util.SMlog('{} drbd-reactor on controller host `{}`...'.format(
908 action_name, controller_node_name
909 ))
910 # If enabled is true, we try to start the controller on the desired
911 # node name first.
912 self._update_drbd_reactor(controller_host, enabled)
914 for host_ref, hostname in secondary_hosts:
915 util.SMlog('{} drbd-reactor on host {}...'.format(
916 action_name, hostname
917 ))
918 self._update_drbd_reactor(host_ref, enabled)
920 if not enabled and controller_host:
921 util.SMlog('{} drbd-reactor on controller host `{}`...'.format(
922 action_name, controller_node_name
923 ))
924 # If enabled is false, we disable the drbd-reactor service of
925 # the controller host last. Why? Otherwise the linstor-controller
926 # of other nodes can be started, and we don't want that.
927 self._update_drbd_reactor(controller_host, enabled)
929 # --------------------------------------------------------------------------
930 # Metadata.
931 # --------------------------------------------------------------------------
933 def _synchronize_metadata_and_xapi(self):
934 try:
935 # First synch SR parameters.
936 self.update(self.uuid)
938 # Now update the VDI information in the metadata if required.
939 xenapi = self.session.xenapi
940 volumes_metadata = self._linstor.get_volumes_with_metadata()
941 for vdi_uuid, volume_metadata in volumes_metadata.items():
942 try:
943 vdi_ref = xenapi.VDI.get_by_uuid(vdi_uuid)
944 except Exception:
945 # May be the VDI is not in XAPI yet dont bother.
946 continue
948 label = util.to_plain_string(
949 xenapi.VDI.get_name_label(vdi_ref)
950 )
951 description = util.to_plain_string(
952 xenapi.VDI.get_name_description(vdi_ref)
953 )
955 if (
956 volume_metadata.get(NAME_LABEL_TAG) != label or
957 volume_metadata.get(NAME_DESCRIPTION_TAG) != description
958 ):
959 self._linstor.update_volume_metadata(vdi_uuid, {
960 NAME_LABEL_TAG: label,
961 NAME_DESCRIPTION_TAG: description
962 })
963 except Exception as e:
964 raise xs_errors.XenError(
965 'MetadataError',
966 opterr='Error synching SR Metadata and XAPI: {}'.format(e)
967 )
969 def _synchronize_metadata(self):
970 if not self._is_master:
971 return
973 util.SMlog('Synchronize metadata...')
974 if self.cmd == 'sr_attach':
975 try:
976 util.SMlog(
977 'Synchronize SR metadata and the state on the storage.'
978 )
979 self._synchronize_metadata_and_xapi()
980 except Exception as e:
981 util.SMlog('Failed to synchronize metadata: {}'.format(e))
983 # --------------------------------------------------------------------------
984 # Stats.
985 # --------------------------------------------------------------------------
987 def _update_stats(self, virt_alloc_delta):
988 valloc = int(self.session.xenapi.SR.get_virtual_allocation(
989 self.sr_ref
990 ))
992 # Update size attributes of the SR parent class.
993 self.virtual_allocation = valloc + virt_alloc_delta
995 self._update_physical_size()
997 # Notify SR parent class.
998 self._db_update()
1000 def _update_physical_size(self):
1001 # We use the size of the smallest disk, this is an approximation that
1002 # ensures the displayed physical size is reachable by the user.
1003 (min_physical_size, pool_count) = self._linstor.get_min_physical_size()
1004 self.physical_size = min_physical_size * pool_count // \
1005 self._linstor.redundancy
1007 self.physical_utilisation = self._linstor.allocated_volume_size
1009 # --------------------------------------------------------------------------
1010 # VDIs.
1011 # --------------------------------------------------------------------------
1013 def _load_vdis(self):
1014 if self._vdis_loaded:
1015 return
1017 assert self._is_master
1019 # We use a cache to avoid repeated JSON parsing.
1020 # The performance gain is not big but we can still
1021 # enjoy it with a few lines.
1022 self._create_linstor_cache()
1023 self._load_vdis_ex()
1024 self._destroy_linstor_cache()
1026 # We must mark VDIs as loaded only if the load is a success.
1027 self._vdis_loaded = True
1029 self._undo_all_journal_transactions()
1031 def _load_vdis_ex(self):
1032 # 1. Get existing VDIs in XAPI.
1033 xenapi = self.session.xenapi
1034 xapi_vdi_uuids = set()
1035 for vdi in xenapi.SR.get_VDIs(self.sr_ref):
1036 xapi_vdi_uuids.add(xenapi.VDI.get_uuid(vdi))
1038 # 2. Get volumes info.
1039 all_volume_info = self._all_volume_info_cache
1040 volumes_metadata = self._all_volume_metadata_cache
1042 # 3. Get CBT vdis.
1043 # See: https://support.citrix.com/article/CTX230619
1044 cbt_vdis = set()
1045 for volume_metadata in volumes_metadata.values():
1046 cbt_uuid = volume_metadata.get(CBTLOG_TAG)
1047 if cbt_uuid:
1048 cbt_vdis.add(cbt_uuid)
1050 introduce = False
1052 # Try to introduce VDIs only during scan/attach.
1053 if self.cmd == 'sr_scan' or self.cmd == 'sr_attach':
1054 has_clone_entries = list(self._journaler.get_all(
1055 LinstorJournaler.CLONE
1056 ).items())
1058 if has_clone_entries:
1059 util.SMlog(
1060 'Cannot introduce VDIs during scan because it exists '
1061 'CLONE entries in journaler on SR {}'.format(self.uuid)
1062 )
1063 else:
1064 introduce = True
1066 # 4. Now check all volume info.
1067 vdi_to_snaps = {}
1068 for vdi_uuid, volume_info in all_volume_info.items():
1069 if vdi_uuid.startswith(cleanup.SR.TMP_RENAME_PREFIX):
1070 continue
1072 # 4.a. Check if the VDI in LINSTOR is in XAPI VDIs.
1073 if vdi_uuid not in xapi_vdi_uuids:
1074 if not introduce:
1075 continue
1077 if vdi_uuid.startswith('DELETED_'):
1078 continue
1080 volume_metadata = volumes_metadata.get(vdi_uuid)
1081 if not volume_metadata:
1082 util.SMlog(
1083 'Skipping volume {} because no metadata could be found'
1084 .format(vdi_uuid)
1085 )
1086 continue
1088 util.SMlog(
1089 'Trying to introduce VDI {} as it is present in '
1090 'LINSTOR and not in XAPI...'
1091 .format(vdi_uuid)
1092 )
1094 try:
1095 self._linstor.get_device_path(vdi_uuid)
1096 except Exception as e:
1097 util.SMlog(
1098 'Cannot introduce {}, unable to get path: {}'
1099 .format(vdi_uuid, e)
1100 )
1101 continue
1103 name_label = volume_metadata.get(NAME_LABEL_TAG) or ''
1104 type = volume_metadata.get(TYPE_TAG) or 'user'
1105 vdi_type = volume_metadata.get(VDI_TYPE_TAG)
1107 if not vdi_type:
1108 util.SMlog(
1109 'Cannot introduce {} '.format(vdi_uuid) +
1110 'without vdi_type'
1111 )
1112 continue
1114 sm_config = {
1115 'vdi_type': vdi_type
1116 }
1118 if vdi_type == vhdutil.VDI_TYPE_RAW:
1119 managed = not volume_metadata.get(HIDDEN_TAG)
1120 elif vdi_type == vhdutil.VDI_TYPE_VHD:
1121 vhd_info = self._vhdutil.get_vhd_info(vdi_uuid)
1122 managed = not vhd_info.hidden
1123 if vhd_info.parentUuid:
1124 sm_config['vhd-parent'] = vhd_info.parentUuid
1125 else:
1126 util.SMlog(
1127 'Cannot introduce {} with invalid VDI type {}'
1128 .format(vdi_uuid, vdi_type)
1129 )
1130 continue
1132 util.SMlog(
1133 'Introducing VDI {} '.format(vdi_uuid) +
1134 ' (name={}, virtual_size={}, allocated_size={})'.format(
1135 name_label,
1136 volume_info.virtual_size,
1137 volume_info.allocated_size
1138 )
1139 )
1141 vdi_ref = xenapi.VDI.db_introduce(
1142 vdi_uuid,
1143 name_label,
1144 volume_metadata.get(NAME_DESCRIPTION_TAG) or '',
1145 self.sr_ref,
1146 type,
1147 False, # sharable
1148 bool(volume_metadata.get(READ_ONLY_TAG)),
1149 {}, # other_config
1150 vdi_uuid, # location
1151 {}, # xenstore_data
1152 sm_config,
1153 managed,
1154 str(volume_info.virtual_size),
1155 str(volume_info.allocated_size)
1156 )
1158 is_a_snapshot = volume_metadata.get(IS_A_SNAPSHOT_TAG)
1159 xenapi.VDI.set_is_a_snapshot(vdi_ref, bool(is_a_snapshot))
1160 if is_a_snapshot:
1161 xenapi.VDI.set_snapshot_time(
1162 vdi_ref,
1163 xmlrpc.client.DateTime(
1164 volume_metadata[SNAPSHOT_TIME_TAG] or
1165 '19700101T00:00:00Z'
1166 )
1167 )
1169 snap_uuid = volume_metadata[SNAPSHOT_OF_TAG]
1170 if snap_uuid in vdi_to_snaps:
1171 vdi_to_snaps[snap_uuid].append(vdi_uuid)
1172 else:
1173 vdi_to_snaps[snap_uuid] = [vdi_uuid]
1175 # 4.b. Add the VDI in the list.
1176 vdi = self.vdi(vdi_uuid)
1177 self.vdis[vdi_uuid] = vdi
1179 if USE_KEY_HASH and vdi.vdi_type == vhdutil.VDI_TYPE_VHD:
1180 # TODO: Replace pylint comment with this feature when possible:
1181 # https://github.com/PyCQA/pylint/pull/2926
1182 vdi.sm_config_override['key_hash'] = \
1183 self._vhdutil.get_key_hash(vdi_uuid) # pylint: disable = E1120
1185 # 4.c. Update CBT status of disks either just added
1186 # or already in XAPI.
1187 cbt_uuid = volume_metadata.get(CBTLOG_TAG)
1188 if cbt_uuid in cbt_vdis:
1189 vdi_ref = xenapi.VDI.get_by_uuid(vdi_uuid)
1190 xenapi.VDI.set_cbt_enabled(vdi_ref, True)
1191 # For existing VDIs, update local state too.
1192 # Scan in base class SR updates existing VDIs
1193 # again based on local states.
1194 self.vdis[vdi_uuid].cbt_enabled = True
1195 cbt_vdis.remove(cbt_uuid)
1197 # 5. Now set the snapshot statuses correctly in XAPI.
1198 for src_uuid in vdi_to_snaps:
1199 try:
1200 src_ref = xenapi.VDI.get_by_uuid(src_uuid)
1201 except Exception:
1202 # The source VDI no longer exists, continue.
1203 continue
1205 for snap_uuid in vdi_to_snaps[src_uuid]:
1206 try:
1207 # This might fail in cases where its already set.
1208 snap_ref = xenapi.VDI.get_by_uuid(snap_uuid)
1209 xenapi.VDI.set_snapshot_of(snap_ref, src_ref)
1210 except Exception as e:
1211 util.SMlog('Setting snapshot failed: {}'.format(e))
1213 # TODO: Check correctly how to use CBT.
1214 # Update cbt_enabled on the right VDI, check LVM/FileSR code.
1216 # 6. If we have items remaining in this list,
1217 # they are cbt_metadata VDI that XAPI doesn't know about.
1218 # Add them to self.vdis and they'll get added to the DB.
1219 for cbt_uuid in cbt_vdis:
1220 new_vdi = self.vdi(cbt_uuid)
1221 new_vdi.ty = 'cbt_metadata'
1222 new_vdi.cbt_enabled = True
1223 self.vdis[cbt_uuid] = new_vdi
1225 # 7. Update virtual allocation, build geneology and remove useless VDIs
1226 self.virtual_allocation = 0
1228 # 8. Build geneology.
1229 geneology = {}
1231 for vdi_uuid, vdi in self.vdis.items():
1232 if vdi.parent:
1233 if vdi.parent in self.vdis:
1234 self.vdis[vdi.parent].read_only = True
1235 if vdi.parent in geneology:
1236 geneology[vdi.parent].append(vdi_uuid)
1237 else:
1238 geneology[vdi.parent] = [vdi_uuid]
1239 if not vdi.hidden:
1240 self.virtual_allocation += vdi.size
1242 # 9. Remove all hidden leaf nodes to avoid introducing records that
1243 # will be GC'ed.
1244 for vdi_uuid in list(self.vdis.keys()):
1245 if vdi_uuid not in geneology and self.vdis[vdi_uuid].hidden:
1246 util.SMlog(
1247 'Scan found hidden leaf ({}), ignoring'.format(vdi_uuid)
1248 )
1249 del self.vdis[vdi_uuid]
1251 # --------------------------------------------------------------------------
1252 # Journals.
1253 # --------------------------------------------------------------------------
1255 def _get_vdi_path_and_parent(self, vdi_uuid, volume_name):
1256 try:
1257 device_path = self._linstor.build_device_path(volume_name)
1258 if not util.pathexists(device_path):
1259 return (None, None)
1261 # If it's a RAW VDI, there is no parent.
1262 volume_metadata = self._linstor.get_volume_metadata(vdi_uuid)
1263 vdi_type = volume_metadata[VDI_TYPE_TAG]
1264 if vdi_type == vhdutil.VDI_TYPE_RAW:
1265 return (device_path, None)
1267 # Otherwise it's a VHD and a parent can exist.
1268 if not self._vhdutil.check(vdi_uuid):
1269 return (None, None)
1271 vhd_info = self._vhdutil.get_vhd_info(vdi_uuid)
1272 if vhd_info:
1273 return (device_path, vhd_info.parentUuid)
1274 except Exception as e:
1275 util.SMlog(
1276 'Failed to get VDI path and parent, ignoring: {}'
1277 .format(e)
1278 )
1279 return (None, None)
1281 def _undo_all_journal_transactions(self):
1282 util.SMlog('Undoing all journal transactions...')
1283 self.lock.acquire()
1284 try:
1285 self._handle_interrupted_inflate_ops()
1286 self._handle_interrupted_clone_ops()
1287 pass
1288 finally:
1289 self.lock.release()
1291 def _handle_interrupted_inflate_ops(self):
1292 transactions = self._journaler.get_all(LinstorJournaler.INFLATE)
1293 for vdi_uuid, old_size in transactions.items():
1294 self._handle_interrupted_inflate(vdi_uuid, old_size)
1295 self._journaler.remove(LinstorJournaler.INFLATE, vdi_uuid)
1297 def _handle_interrupted_clone_ops(self):
1298 transactions = self._journaler.get_all(LinstorJournaler.CLONE)
1299 for vdi_uuid, old_size in transactions.items():
1300 self._handle_interrupted_clone(vdi_uuid, old_size)
1301 self._journaler.remove(LinstorJournaler.CLONE, vdi_uuid)
1303 def _handle_interrupted_inflate(self, vdi_uuid, old_size):
1304 util.SMlog(
1305 '*** INTERRUPTED INFLATE OP: for {} ({})'
1306 .format(vdi_uuid, old_size)
1307 )
1309 vdi = self.vdis.get(vdi_uuid)
1310 if not vdi:
1311 util.SMlog('Cannot deflate missing VDI {}'.format(vdi_uuid))
1312 return
1314 assert not self._all_volume_info_cache
1315 volume_info = self._linstor.get_volume_info(vdi_uuid)
1317 current_size = volume_info.virtual_size
1318 assert current_size > 0
1319 self._vhdutil.force_deflate(vdi.path, old_size, current_size, zeroize=True)
1321 def _handle_interrupted_clone(
1322 self, vdi_uuid, clone_info, force_undo=False
1323 ):
1324 util.SMlog(
1325 '*** INTERRUPTED CLONE OP: for {} ({})'
1326 .format(vdi_uuid, clone_info)
1327 )
1329 base_uuid, snap_uuid = clone_info.split('_')
1331 # Use LINSTOR data because new VDIs may not be in the XAPI.
1332 volume_names = self._linstor.get_volumes_with_name()
1334 # Check if we don't have a base VDI. (If clone failed at startup.)
1335 if base_uuid not in volume_names:
1336 if vdi_uuid in volume_names:
1337 util.SMlog('*** INTERRUPTED CLONE OP: nothing to do')
1338 return
1339 raise util.SMException(
1340 'Base copy {} not present, but no original {} found'
1341 .format(base_uuid, vdi_uuid)
1342 )
1344 if force_undo:
1345 util.SMlog('Explicit revert')
1346 self._undo_clone(
1347 volume_names, vdi_uuid, base_uuid, snap_uuid
1348 )
1349 return
1351 # If VDI or snap uuid is missing...
1352 if vdi_uuid not in volume_names or \
1353 (snap_uuid and snap_uuid not in volume_names):
1354 util.SMlog('One or both leaves missing => revert')
1355 self._undo_clone(volume_names, vdi_uuid, base_uuid, snap_uuid)
1356 return
1358 vdi_path, vdi_parent_uuid = self._get_vdi_path_and_parent(
1359 vdi_uuid, volume_names[vdi_uuid]
1360 )
1361 snap_path, snap_parent_uuid = self._get_vdi_path_and_parent(
1362 snap_uuid, volume_names[snap_uuid]
1363 )
1365 if not vdi_path or (snap_uuid and not snap_path):
1366 util.SMlog('One or both leaves invalid (and path(s)) => revert')
1367 self._undo_clone(volume_names, vdi_uuid, base_uuid, snap_uuid)
1368 return
1370 util.SMlog('Leaves valid but => revert')
1371 self._undo_clone(volume_names, vdi_uuid, base_uuid, snap_uuid)
1373 def _undo_clone(self, volume_names, vdi_uuid, base_uuid, snap_uuid):
1374 base_path = self._linstor.build_device_path(volume_names[base_uuid])
1375 base_metadata = self._linstor.get_volume_metadata(base_uuid)
1376 base_type = base_metadata[VDI_TYPE_TAG]
1378 if not util.pathexists(base_path):
1379 util.SMlog('Base not found! Exit...')
1380 util.SMlog('*** INTERRUPTED CLONE OP: rollback fail')
1381 return
1383 # Un-hide the parent.
1384 self._linstor.update_volume_metadata(base_uuid, {READ_ONLY_TAG: False})
1385 if base_type == vhdutil.VDI_TYPE_VHD:
1386 vhd_info = self._vhdutil.get_vhd_info(base_uuid, False)
1387 if vhd_info.hidden:
1388 self._vhdutil.set_hidden(base_path, False)
1389 elif base_type == vhdutil.VDI_TYPE_RAW and \
1390 base_metadata.get(HIDDEN_TAG):
1391 self._linstor.update_volume_metadata(
1392 base_uuid, {HIDDEN_TAG: False}
1393 )
1395 # Remove the child nodes.
1396 if snap_uuid and snap_uuid in volume_names:
1397 util.SMlog('Destroying snap {}...'.format(snap_uuid))
1399 try:
1400 self._linstor.destroy_volume(snap_uuid)
1401 except Exception as e:
1402 util.SMlog(
1403 'Cannot destroy snap {} during undo clone: {}'
1404 .format(snap_uuid, e)
1405 )
1407 if vdi_uuid in volume_names:
1408 try:
1409 util.SMlog('Destroying {}...'.format(vdi_uuid))
1410 self._linstor.destroy_volume(vdi_uuid)
1411 except Exception as e:
1412 util.SMlog(
1413 'Cannot destroy VDI {} during undo clone: {}'
1414 .format(vdi_uuid, e)
1415 )
1416 # We can get an exception like this:
1417 # "Shutdown of the DRBD resource 'XXX failed", so the
1418 # volume info remains... The problem is we can't rename
1419 # properly the base VDI below this line, so we must change the
1420 # UUID of this bad VDI before.
1421 self._linstor.update_volume_uuid(
1422 vdi_uuid, 'DELETED_' + vdi_uuid, force=True
1423 )
1425 # Rename!
1426 self._linstor.update_volume_uuid(base_uuid, vdi_uuid)
1428 # Inflate to the right size.
1429 if base_type == vhdutil.VDI_TYPE_VHD:
1430 vdi = self.vdi(vdi_uuid)
1431 volume_size = LinstorVhdUtil.compute_volume_size(vdi.size, vdi.vdi_type)
1432 self._vhdutil.inflate(
1433 self._journaler, vdi_uuid, vdi.path,
1434 volume_size, vdi.capacity
1435 )
1436 self.vdis[vdi_uuid] = vdi
1438 # At this stage, tapdisk and SM vdi will be in paused state. Remove
1439 # flag to facilitate vm deactivate.
1440 vdi_ref = self.session.xenapi.VDI.get_by_uuid(vdi_uuid)
1441 self.session.xenapi.VDI.remove_from_sm_config(vdi_ref, 'paused')
1443 util.SMlog('*** INTERRUPTED CLONE OP: rollback success')
1445 # --------------------------------------------------------------------------
1446 # Cache.
1447 # --------------------------------------------------------------------------
1449 def _create_linstor_cache(self):
1450 reconnect = False
1452 def create_cache():
1453 nonlocal reconnect
1454 try:
1455 if reconnect:
1456 self._reconnect()
1457 return self._linstor.get_volumes_with_info()
1458 except Exception as e:
1459 reconnect = True
1460 raise e
1462 self._all_volume_metadata_cache = \
1463 self._linstor.get_volumes_with_metadata()
1464 self._all_volume_info_cache = util.retry(
1465 create_cache,
1466 maxretry=10,
1467 period=3
1468 )
1470 def _destroy_linstor_cache(self):
1471 self._all_volume_info_cache = None
1472 self._all_volume_metadata_cache = None
1474 # --------------------------------------------------------------------------
1475 # Misc.
1476 # --------------------------------------------------------------------------
1478 def _reconnect(self):
1479 controller_uri = get_controller_uri()
1481 self._journaler = LinstorJournaler(
1482 controller_uri, self._group_name, logger=util.SMlog
1483 )
1485 # Try to open SR if exists.
1486 # We can repair only if we are on the master AND if
1487 # we are trying to execute an exclusive operation.
1488 # Otherwise we could try to delete a VDI being created or
1489 # during a snapshot. An exclusive op is the guarantee that
1490 # the SR is locked.
1491 self._linstor = LinstorVolumeManager(
1492 controller_uri,
1493 self._group_name,
1494 repair=(
1495 self._is_master and
1496 self.srcmd.cmd in self.ops_exclusive
1497 ),
1498 logger=util.SMlog
1499 )
1500 self._vhdutil = LinstorVhdUtil(self.session, self._linstor)
1502 def _ensure_space_available(self, amount_needed):
1503 space_available = self._linstor.max_volume_size_allowed
1504 if (space_available < amount_needed):
1505 util.SMlog(
1506 'Not enough space! Free space: {}, need: {}'.format(
1507 space_available, amount_needed
1508 )
1509 )
1510 raise xs_errors.XenError('SRNoSpace')
1512 def _kick_gc(self):
1513 # Don't bother if an instance already running. This is just an
1514 # optimization to reduce the overhead of forking a new process if we
1515 # don't have to, but the process will check the lock anyways.
1516 lock = Lock(cleanup.LOCK_TYPE_RUNNING, self.uuid)
1517 if not lock.acquireNoblock():
1518 if not cleanup.should_preempt(self.session, self.uuid):
1519 util.SMlog('A GC instance already running, not kicking')
1520 return
1522 util.SMlog('Aborting currently-running coalesce of garbage VDI')
1523 try:
1524 if not cleanup.abort(self.uuid, soft=True):
1525 util.SMlog('The GC has already been scheduled to re-start')
1526 except util.CommandException as e:
1527 if e.code != errno.ETIMEDOUT:
1528 raise
1529 util.SMlog('Failed to abort the GC')
1530 else:
1531 lock.release()
1533 util.SMlog('Kicking GC')
1534 cleanup.gc(self.session, self.uuid, True)
1536# ==============================================================================
1537# LinstorSr VDI
1538# ==============================================================================
1541class LinstorVDI(VDI.VDI):
1542 # Warning: Not the same values than vhdutil.VDI_TYPE_*.
1543 # These values represents the types given on the command line.
1544 TYPE_RAW = 'raw'
1545 TYPE_VHD = 'vhd'
1547 # Metadata size given to the "S" param of vhd-util create.
1548 # "-S size (MB) for metadata preallocation".
1549 # Increase the performance when resize is called.
1550 MAX_METADATA_VIRT_SIZE = 2 * 1024 * 1024
1552 # --------------------------------------------------------------------------
1553 # VDI methods.
1554 # --------------------------------------------------------------------------
1556 def load(self, vdi_uuid):
1557 self._lock = self.sr.lock
1558 self._exists = True
1559 self._linstor = self.sr._linstor
1561 # Update hidden parent property.
1562 self.hidden = False
1564 def raise_bad_load(e):
1565 util.SMlog(
1566 'Got exception in LinstorVDI.load: {}'.format(e)
1567 )
1568 util.SMlog(traceback.format_exc())
1569 raise xs_errors.XenError(
1570 'VDIUnavailable',
1571 opterr='Could not load {} because: {}'.format(self.uuid, e)
1572 )
1574 # Try to load VDI.
1575 try:
1576 if (
1577 self.sr.srcmd.cmd == 'vdi_attach_from_config' or
1578 self.sr.srcmd.cmd == 'vdi_detach_from_config'
1579 ):
1580 self.vdi_type = vhdutil.VDI_TYPE_RAW
1581 self.path = self.sr.srcmd.params['vdi_path']
1582 else:
1583 self._determine_type_and_path()
1584 self._load_this()
1586 util.SMlog('VDI {} loaded! (path={}, hidden={})'.format(
1587 self.uuid, self.path, self.hidden
1588 ))
1589 except LinstorVolumeManagerError as e:
1590 # 1. It may be a VDI deletion.
1591 if e.code == LinstorVolumeManagerError.ERR_VOLUME_NOT_EXISTS:
1592 if self.sr.srcmd.cmd == 'vdi_delete':
1593 self.deleted = True
1594 return
1596 # 2. Or maybe a creation.
1597 if self.sr.srcmd.cmd == 'vdi_create':
1598 # Set type attribute of VDI parent class.
1599 # We use VHD by default.
1600 self.vdi_type = vhdutil.VDI_TYPE_VHD
1601 self._key_hash = None # Only used in create.
1603 self._exists = False
1604 vdi_sm_config = self.sr.srcmd.params.get('vdi_sm_config')
1605 if vdi_sm_config is not None:
1606 type = vdi_sm_config.get('type')
1607 if type is not None:
1608 if type == self.TYPE_RAW:
1609 self.vdi_type = vhdutil.VDI_TYPE_RAW
1610 elif type == self.TYPE_VHD:
1611 self.vdi_type = vhdutil.VDI_TYPE_VHD
1612 else:
1613 raise xs_errors.XenError(
1614 'VDICreate',
1615 opterr='Invalid VDI type {}'.format(type)
1616 )
1617 if self.vdi_type == vhdutil.VDI_TYPE_VHD:
1618 self._key_hash = vdi_sm_config.get('key_hash')
1620 # For the moment we don't have a path.
1621 self._update_device_name(None)
1622 return
1623 raise_bad_load(e)
1624 except Exception as e:
1625 raise_bad_load(e)
1627 def create(self, sr_uuid, vdi_uuid, size):
1628 # Usage example:
1629 # xe vdi-create sr-uuid=39a5826b-5a90-73eb-dd09-51e3a116f937
1630 # name-label="linstor-vdi-1" virtual-size=4096MiB sm-config:type=vhd
1632 # 1. Check if we are on the master and if the VDI doesn't exist.
1633 util.SMlog('LinstorVDI.create for {}'.format(self.uuid))
1634 if self._exists:
1635 raise xs_errors.XenError('VDIExists')
1637 assert self.uuid
1638 assert self.ty
1639 assert self.vdi_type
1641 # 2. Compute size and check space available.
1642 size = vhdutil.validate_and_round_vhd_size(int(size))
1643 volume_size = LinstorVhdUtil.compute_volume_size(size, self.vdi_type)
1644 util.SMlog(
1645 'LinstorVDI.create: type={}, vhd-size={}, volume-size={}'
1646 .format(self.vdi_type, size, volume_size)
1647 )
1648 self.sr._ensure_space_available(volume_size)
1650 # 3. Set sm_config attribute of VDI parent class.
1651 self.sm_config = self.sr.srcmd.params['vdi_sm_config']
1653 # 4. Create!
1654 failed = False
1655 try:
1656 volume_name = None
1657 if self.ty == 'ha_statefile':
1658 volume_name = HA_VOLUME_NAME
1659 elif self.ty == 'redo_log':
1660 volume_name = REDO_LOG_VOLUME_NAME
1662 self._linstor.create_volume(
1663 self.uuid, volume_size, persistent=False,
1664 volume_name=volume_name
1665 )
1666 volume_info = self._linstor.get_volume_info(self.uuid)
1668 self._update_device_name(volume_info.name)
1670 if self.vdi_type == vhdutil.VDI_TYPE_RAW:
1671 self.size = volume_info.virtual_size
1672 else:
1673 self.sr._vhdutil.create(
1674 self.path, size, False, self.MAX_METADATA_VIRT_SIZE
1675 )
1676 self.size = self.sr._vhdutil.get_size_virt(self.uuid)
1678 if self._key_hash:
1679 self.sr._vhdutil.set_key(self.path, self._key_hash)
1681 # Because vhdutil commands modify the volume data,
1682 # we must retrieve a new time the utilization size.
1683 volume_info = self._linstor.get_volume_info(self.uuid)
1685 volume_metadata = {
1686 NAME_LABEL_TAG: util.to_plain_string(self.label),
1687 NAME_DESCRIPTION_TAG: util.to_plain_string(self.description),
1688 IS_A_SNAPSHOT_TAG: False,
1689 SNAPSHOT_OF_TAG: '',
1690 SNAPSHOT_TIME_TAG: '',
1691 TYPE_TAG: self.ty,
1692 VDI_TYPE_TAG: self.vdi_type,
1693 READ_ONLY_TAG: bool(self.read_only),
1694 METADATA_OF_POOL_TAG: ''
1695 }
1696 self._linstor.set_volume_metadata(self.uuid, volume_metadata)
1698 # Set the open timeout to 1min to reduce CPU usage
1699 # in http-disk-server when a secondary server tries to open
1700 # an already opened volume.
1701 if self.ty == 'ha_statefile' or self.ty == 'redo_log':
1702 self._linstor.set_auto_promote_timeout(self.uuid, 600)
1704 self._linstor.mark_volume_as_persistent(self.uuid)
1705 except util.CommandException as e:
1706 failed = True
1707 raise xs_errors.XenError(
1708 'VDICreate', opterr='error {}'.format(e.code)
1709 )
1710 except Exception as e:
1711 failed = True
1712 raise xs_errors.XenError('VDICreate', opterr='error {}'.format(e))
1713 finally:
1714 if failed:
1715 util.SMlog('Unable to create VDI {}'.format(self.uuid))
1716 try:
1717 self._linstor.destroy_volume(self.uuid)
1718 except Exception as e:
1719 util.SMlog(
1720 'Ignoring exception after fail in LinstorVDI.create: '
1721 '{}'.format(e)
1722 )
1724 self.utilisation = volume_info.allocated_size
1725 self.sm_config['vdi_type'] = self.vdi_type
1727 self.ref = self._db_introduce()
1728 self.sr._update_stats(self.size)
1730 return VDI.VDI.get_params(self)
1732 def delete(self, sr_uuid, vdi_uuid, data_only=False):
1733 util.SMlog('LinstorVDI.delete for {}'.format(self.uuid))
1734 if self.attached:
1735 raise xs_errors.XenError('VDIInUse')
1737 if self.deleted:
1738 return super(LinstorVDI, self).delete(
1739 sr_uuid, vdi_uuid, data_only
1740 )
1742 vdi_ref = self.sr.srcmd.params['vdi_ref']
1743 if not self.session.xenapi.VDI.get_managed(vdi_ref):
1744 raise xs_errors.XenError(
1745 'VDIDelete',
1746 opterr='Deleting non-leaf node not permitted'
1747 )
1749 try:
1750 # Remove from XAPI and delete from LINSTOR.
1751 self._linstor.destroy_volume(self.uuid)
1752 if not data_only:
1753 self._db_forget()
1755 self.sr.lock.cleanupAll(vdi_uuid)
1756 except Exception as e:
1757 util.SMlog(
1758 'Failed to remove the volume (maybe is leaf coalescing) '
1759 'for {} err: {}'.format(self.uuid, e)
1760 )
1762 try:
1763 raise xs_errors.XenError('VDIDelete', opterr=str(e))
1764 except LinstorVolumeManagerError as e:
1765 if e.code != LinstorVolumeManagerError.ERR_VOLUME_DESTROY:
1766 raise xs_errors.XenError('VDIDelete', opterr=str(e))
1768 return
1770 if self.uuid in self.sr.vdis:
1771 del self.sr.vdis[self.uuid]
1773 # TODO: Check size after delete.
1774 self.sr._update_stats(-self.size)
1775 self.sr._kick_gc()
1776 return super(LinstorVDI, self).delete(sr_uuid, vdi_uuid, data_only)
1778 def attach(self, sr_uuid, vdi_uuid):
1779 util.SMlog('LinstorVDI.attach for {}'.format(self.uuid))
1780 attach_from_config = self.sr.srcmd.cmd == 'vdi_attach_from_config'
1781 if (
1782 not attach_from_config or
1783 self.sr.srcmd.params['vdi_uuid'] != self.uuid
1784 ) and self.sr._journaler.has_entries(self.uuid):
1785 raise xs_errors.XenError(
1786 'VDIUnavailable',
1787 opterr='Interrupted operation detected on this VDI, '
1788 'scan SR first to trigger auto-repair'
1789 )
1791 if not attach_from_config or self.sr._is_master:
1792 writable = 'args' not in self.sr.srcmd.params or \
1793 self.sr.srcmd.params['args'][0] == 'true'
1795 # We need to inflate the volume if we don't have enough place
1796 # to mount the VHD image. I.e. the volume capacity must be greater
1797 # than the VHD size + bitmap size.
1798 need_inflate = True
1799 if (
1800 self.vdi_type == vhdutil.VDI_TYPE_RAW or
1801 not writable or
1802 self.capacity >= LinstorVhdUtil.compute_volume_size(self.size, self.vdi_type)
1803 ):
1804 need_inflate = False
1806 if need_inflate:
1807 try:
1808 self._prepare_thin(True)
1809 except Exception as e:
1810 raise xs_errors.XenError(
1811 'VDIUnavailable',
1812 opterr='Failed to attach VDI during "prepare thin": {}'
1813 .format(e)
1814 )
1816 if not hasattr(self, 'xenstore_data'):
1817 self.xenstore_data = {}
1818 self.xenstore_data['storage-type'] = LinstorSR.DRIVER_TYPE
1820 if (
1821 USE_HTTP_NBD_SERVERS and
1822 attach_from_config and
1823 self.path.startswith('/dev/http-nbd/')
1824 ):
1825 return self._attach_using_http_nbd()
1827 # Ensure we have a path...
1828 while vdi_uuid:
1829 path = self._linstor.get_device_path(vdi_uuid)
1830 if not util.pathexists(path):
1831 raise xs_errors.XenError(
1832 'VDIUnavailable', opterr='Could not find: {}'.format(path)
1833 )
1835 # Diskless path can be created on the fly, ensure we can open it.
1836 def check_volume_usable():
1837 while True:
1838 try:
1839 with open(path, 'r+'):
1840 pass
1841 except IOError as e:
1842 if e.errno == errno.ENODATA:
1843 time.sleep(2)
1844 continue
1845 if e.errno == errno.EROFS:
1846 util.SMlog('Volume not attachable because RO. Openers: {}'.format(
1847 self.sr._linstor.get_volume_openers(vdi_uuid)
1848 ))
1849 raise
1850 break
1851 util.retry(check_volume_usable, 15, 2)
1853 vdi_uuid = self.sr._vhdutil.get_vhd_info(vdi_uuid).parentUuid
1855 self.attached = True
1856 return VDI.VDI.attach(self, self.sr.uuid, self.uuid)
1858 def detach(self, sr_uuid, vdi_uuid):
1859 util.SMlog('LinstorVDI.detach for {}'.format(self.uuid))
1860 detach_from_config = self.sr.srcmd.cmd == 'vdi_detach_from_config'
1861 self.attached = False
1863 if detach_from_config and self.path.startswith('/dev/http-nbd/'):
1864 return self._detach_using_http_nbd()
1866 if self.vdi_type == vhdutil.VDI_TYPE_RAW:
1867 return
1869 # The VDI is already deflated if the VHD image size + metadata is
1870 # equal to the LINSTOR volume size.
1871 volume_size = LinstorVhdUtil.compute_volume_size(self.size, self.vdi_type)
1872 already_deflated = self.capacity <= volume_size
1874 if already_deflated:
1875 util.SMlog(
1876 'VDI {} already deflated (old volume size={}, volume size={})'
1877 .format(self.uuid, self.capacity, volume_size)
1878 )
1880 need_deflate = True
1881 if already_deflated:
1882 need_deflate = False
1883 elif self.sr._provisioning == 'thick':
1884 need_deflate = False
1886 vdi_ref = self.sr.srcmd.params['vdi_ref']
1887 if self.session.xenapi.VDI.get_is_a_snapshot(vdi_ref):
1888 need_deflate = True
1890 if need_deflate:
1891 try:
1892 self._prepare_thin(False)
1893 except Exception as e:
1894 raise xs_errors.XenError(
1895 'VDIUnavailable',
1896 opterr='Failed to detach VDI during "prepare thin": {}'
1897 .format(e)
1898 )
1900 # We remove only on slaves because the volume can be used by the GC.
1901 if self.sr._is_master:
1902 return
1904 while vdi_uuid:
1905 try:
1906 path = self._linstor.build_device_path(self._linstor.get_volume_name(vdi_uuid))
1907 parent_vdi_uuid = self.sr._vhdutil.get_vhd_info(vdi_uuid).parentUuid
1908 except Exception:
1909 break
1911 if util.pathexists(path):
1912 try:
1913 self._linstor.remove_volume_if_diskless(vdi_uuid)
1914 except Exception as e:
1915 # Ensure we can always detach properly.
1916 # I don't want to corrupt the XAPI info.
1917 util.SMlog('Failed to clean VDI {} during detach: {}'.format(vdi_uuid, e))
1918 vdi_uuid = parent_vdi_uuid
1920 def resize(self, sr_uuid, vdi_uuid, size):
1921 util.SMlog('LinstorVDI.resize for {}'.format(self.uuid))
1922 if not self.sr._is_master:
1923 raise xs_errors.XenError(
1924 'VDISize',
1925 opterr='resize on slave not allowed'
1926 )
1928 if self.hidden:
1929 raise xs_errors.XenError('VDIUnavailable', opterr='hidden VDI')
1931 # Compute the virtual VHD and DRBD volume size.
1932 size = vhdutil.validate_and_round_vhd_size(int(size))
1933 volume_size = LinstorVhdUtil.compute_volume_size(size, self.vdi_type)
1934 util.SMlog(
1935 'LinstorVDI.resize: type={}, vhd-size={}, volume-size={}'
1936 .format(self.vdi_type, size, volume_size)
1937 )
1939 if size < self.size:
1940 util.SMlog(
1941 'vdi_resize: shrinking not supported: '
1942 '(current size: {}, new size: {})'.format(self.size, size)
1943 )
1944 raise xs_errors.XenError('VDISize', opterr='shrinking not allowed')
1946 if size == self.size:
1947 return VDI.VDI.get_params(self)
1949 if self.vdi_type == vhdutil.VDI_TYPE_RAW:
1950 old_volume_size = self.size
1951 new_volume_size = LinstorVolumeManager.round_up_volume_size(size)
1952 else:
1953 old_volume_size = self.utilisation
1954 if self.sr._provisioning == 'thin':
1955 # VDI is currently deflated, so keep it deflated.
1956 new_volume_size = old_volume_size
1957 else:
1958 new_volume_size = LinstorVhdUtil.compute_volume_size(size, self.vdi_type)
1959 assert new_volume_size >= old_volume_size
1961 space_needed = new_volume_size - old_volume_size
1962 self.sr._ensure_space_available(space_needed)
1964 old_size = self.size
1965 if self.vdi_type == vhdutil.VDI_TYPE_RAW:
1966 self._linstor.resize(self.uuid, new_volume_size)
1967 else:
1968 if new_volume_size != old_volume_size:
1969 self.sr._vhdutil.inflate(
1970 self.sr._journaler, self._linstor, self.uuid, self.path,
1971 new_volume_size, old_volume_size
1972 )
1973 self.sr._vhdutil.set_size_virt_fast(self.path, size)
1975 # Reload size attributes.
1976 self._load_this()
1978 vdi_ref = self.sr.srcmd.params['vdi_ref']
1979 self.session.xenapi.VDI.set_virtual_size(vdi_ref, str(self.size))
1980 self.session.xenapi.VDI.set_physical_utilisation(
1981 vdi_ref, str(self.utilisation)
1982 )
1983 self.sr._update_stats(self.size - old_size)
1984 return VDI.VDI.get_params(self)
1986 def clone(self, sr_uuid, vdi_uuid):
1987 return self._do_snapshot(sr_uuid, vdi_uuid, VDI.SNAPSHOT_DOUBLE)
1989 def compose(self, sr_uuid, vdi1, vdi2):
1990 util.SMlog('VDI.compose for {} -> {}'.format(vdi2, vdi1))
1991 if self.vdi_type != vhdutil.VDI_TYPE_VHD:
1992 raise xs_errors.XenError('Unimplemented')
1994 parent_uuid = vdi1
1995 parent_path = self._linstor.get_device_path(parent_uuid)
1997 # We must pause tapdisk to correctly change the parent. Otherwise we
1998 # have a readonly error.
1999 # See: https://github.com/xapi-project/xen-api/blob/b3169a16d36dae0654881b336801910811a399d9/ocaml/xapi/storage_migrate.ml#L928-L929
2000 # and: https://github.com/xapi-project/xen-api/blob/b3169a16d36dae0654881b336801910811a399d9/ocaml/xapi/storage_migrate.ml#L775
2002 if not blktap2.VDI.tap_pause(self.session, self.sr.uuid, self.uuid):
2003 raise util.SMException('Failed to pause VDI {}'.format(self.uuid))
2004 try:
2005 self.sr._vhdutil.set_parent(self.path, parent_path, False)
2006 self.sr._vhdutil.set_hidden(parent_path)
2007 self.sr.session.xenapi.VDI.set_managed(
2008 self.sr.srcmd.params['args'][0], False
2009 )
2010 finally:
2011 blktap2.VDI.tap_unpause(self.session, self.sr.uuid, self.uuid)
2013 if not blktap2.VDI.tap_refresh(self.session, self.sr.uuid, self.uuid):
2014 raise util.SMException(
2015 'Failed to refresh VDI {}'.format(self.uuid)
2016 )
2018 util.SMlog('Compose done')
2020 def generate_config(self, sr_uuid, vdi_uuid):
2021 """
2022 Generate the XML config required to attach and activate
2023 a VDI for use when XAPI is not running. Attach and
2024 activation is handled by vdi_attach_from_config below.
2025 """
2027 util.SMlog('LinstorVDI.generate_config for {}'.format(self.uuid))
2029 resp = {}
2030 resp['device_config'] = self.sr.dconf
2031 resp['sr_uuid'] = sr_uuid
2032 resp['vdi_uuid'] = self.uuid
2033 resp['sr_sm_config'] = self.sr.sm_config
2034 resp['command'] = 'vdi_attach_from_config'
2036 # By default, we generate a normal config.
2037 # But if the disk is persistent, we must use a HTTP/NBD
2038 # server to ensure we can always write or read data.
2039 # Why? DRBD is unsafe when used with more than 4 hosts:
2040 # We are limited to use 1 diskless and 3 full.
2041 # We can't increase this limitation, so we use a NBD/HTTP device
2042 # instead.
2043 volume_name = self._linstor.get_volume_name(self.uuid)
2044 if not USE_HTTP_NBD_SERVERS or volume_name not in [
2045 HA_VOLUME_NAME, REDO_LOG_VOLUME_NAME
2046 ]:
2047 if not self.path or not util.pathexists(self.path):
2048 available = False
2049 # Try to refresh symlink path...
2050 try:
2051 self.path = self._linstor.get_device_path(vdi_uuid)
2052 available = util.pathexists(self.path)
2053 except Exception:
2054 pass
2055 if not available:
2056 raise xs_errors.XenError('VDIUnavailable')
2058 resp['vdi_path'] = self.path
2059 else:
2060 # Axiom: DRBD device is present on at least one host.
2061 resp['vdi_path'] = '/dev/http-nbd/' + volume_name
2063 config = xmlrpc.client.dumps(tuple([resp]), 'vdi_attach_from_config')
2064 return xmlrpc.client.dumps((config,), "", True)
2066 def attach_from_config(self, sr_uuid, vdi_uuid):
2067 """
2068 Attach and activate a VDI using config generated by
2069 vdi_generate_config above. This is used for cases such as
2070 the HA state-file and the redo-log.
2071 """
2073 util.SMlog('LinstorVDI.attach_from_config for {}'.format(vdi_uuid))
2075 try:
2076 if not util.pathexists(self.sr.path):
2077 self.sr.attach(sr_uuid)
2079 if not DRIVER_CONFIG['ATTACH_FROM_CONFIG_WITH_TAPDISK']:
2080 return self.attach(sr_uuid, vdi_uuid)
2081 except Exception:
2082 util.logException('LinstorVDI.attach_from_config')
2083 raise xs_errors.XenError(
2084 'SRUnavailable',
2085 opterr='Unable to attach from config'
2086 )
2088 def reset_leaf(self, sr_uuid, vdi_uuid):
2089 if self.vdi_type != vhdutil.VDI_TYPE_VHD:
2090 raise xs_errors.XenError('Unimplemented')
2092 if not self.sr._vhdutil.has_parent(self.uuid):
2093 raise util.SMException(
2094 'ERROR: VDI {} has no parent, will not reset contents'
2095 .format(self.uuid)
2096 )
2098 self.sr._vhdutil.kill_data(self.path)
2100 def _load_this(self):
2101 volume_metadata = None
2102 if self.sr._all_volume_metadata_cache:
2103 volume_metadata = self.sr._all_volume_metadata_cache.get(self.uuid)
2104 if volume_metadata is None:
2105 volume_metadata = self._linstor.get_volume_metadata(self.uuid)
2107 volume_info = None
2108 if self.sr._all_volume_info_cache:
2109 volume_info = self.sr._all_volume_info_cache.get(self.uuid)
2110 if volume_info is None:
2111 volume_info = self._linstor.get_volume_info(self.uuid)
2113 # Contains the max physical size used on a disk.
2114 # When LINSTOR LVM driver is used, the size should be similar to
2115 # virtual size (i.e. the LINSTOR max volume size).
2116 # When LINSTOR Thin LVM driver is used, the used physical size should
2117 # be lower than virtual size at creation.
2118 # The physical size increases after each write in a new block.
2119 self.utilisation = volume_info.allocated_size
2120 self.capacity = volume_info.virtual_size
2122 if self.vdi_type == vhdutil.VDI_TYPE_RAW:
2123 self.hidden = int(volume_metadata.get(HIDDEN_TAG) or 0)
2124 self.size = volume_info.virtual_size
2125 self.parent = ''
2126 else:
2127 vhd_info = self.sr._vhdutil.get_vhd_info(self.uuid)
2128 self.hidden = vhd_info.hidden
2129 self.size = vhd_info.sizeVirt
2130 self.parent = vhd_info.parentUuid
2132 if self.hidden:
2133 self.managed = False
2135 self.label = volume_metadata.get(NAME_LABEL_TAG) or ''
2136 self.description = volume_metadata.get(NAME_DESCRIPTION_TAG) or ''
2138 # Update sm_config_override of VDI parent class.
2139 self.sm_config_override = {'vhd-parent': self.parent or None}
2141 def _mark_hidden(self, hidden=True):
2142 if self.hidden == hidden:
2143 return
2145 if self.vdi_type == vhdutil.VDI_TYPE_VHD:
2146 self.sr._vhdutil.set_hidden(self.path, hidden)
2147 else:
2148 self._linstor.update_volume_metadata(self.uuid, {
2149 HIDDEN_TAG: hidden
2150 })
2151 self.hidden = hidden
2153 def update(self, sr_uuid, vdi_uuid):
2154 xenapi = self.session.xenapi
2155 vdi_ref = xenapi.VDI.get_by_uuid(self.uuid)
2157 volume_metadata = {
2158 NAME_LABEL_TAG: util.to_plain_string(
2159 xenapi.VDI.get_name_label(vdi_ref)
2160 ),
2161 NAME_DESCRIPTION_TAG: util.to_plain_string(
2162 xenapi.VDI.get_name_description(vdi_ref)
2163 )
2164 }
2166 try:
2167 self._linstor.update_volume_metadata(self.uuid, volume_metadata)
2168 except LinstorVolumeManagerError as e:
2169 if e.code == LinstorVolumeManagerError.ERR_VOLUME_NOT_EXISTS:
2170 raise xs_errors.XenError(
2171 'VDIUnavailable',
2172 opterr='LINSTOR volume {} not found'.format(self.uuid)
2173 )
2174 raise xs_errors.XenError('VDIUnavailable', opterr=str(e))
2176 # --------------------------------------------------------------------------
2177 # Thin provisioning.
2178 # --------------------------------------------------------------------------
2180 def _prepare_thin(self, attach):
2181 if self.sr._is_master:
2182 if attach:
2183 attach_thin(
2184 self.session, self.sr._journaler, self._linstor,
2185 self.sr.uuid, self.uuid
2186 )
2187 else:
2188 detach_thin(
2189 self.session, self._linstor, self.sr.uuid, self.uuid
2190 )
2191 else:
2192 fn = 'attach' if attach else 'detach'
2194 master = util.get_master_ref(self.session)
2196 args = {
2197 'groupName': self.sr._group_name,
2198 'srUuid': self.sr.uuid,
2199 'vdiUuid': self.uuid
2200 }
2202 try:
2203 self.sr._exec_manager_command(master, fn, args, 'VDIUnavailable')
2204 except Exception:
2205 if fn != 'detach':
2206 raise
2208 # Reload size attrs after inflate or deflate!
2209 self._load_this()
2210 self.sr._update_physical_size()
2212 vdi_ref = self.sr.srcmd.params['vdi_ref']
2213 self.session.xenapi.VDI.set_physical_utilisation(
2214 vdi_ref, str(self.utilisation)
2215 )
2217 self.session.xenapi.SR.set_physical_utilisation(
2218 self.sr.sr_ref, str(self.sr.physical_utilisation)
2219 )
2221 # --------------------------------------------------------------------------
2222 # Generic helpers.
2223 # --------------------------------------------------------------------------
2225 def _determine_type_and_path(self):
2226 """
2227 Determine whether this is a RAW or a VHD VDI.
2228 """
2230 # 1. Check vdi_ref and vdi_type in config.
2231 try:
2232 vdi_ref = self.session.xenapi.VDI.get_by_uuid(self.uuid)
2233 if vdi_ref:
2234 sm_config = self.session.xenapi.VDI.get_sm_config(vdi_ref)
2235 vdi_type = sm_config.get('vdi_type')
2236 if vdi_type:
2237 # Update parent fields.
2238 self.vdi_type = vdi_type
2239 self.sm_config_override = sm_config
2240 self._update_device_name(
2241 self._linstor.get_volume_name(self.uuid)
2242 )
2243 return
2244 except Exception:
2245 pass
2247 # 2. Otherwise use the LINSTOR volume manager directly.
2248 # It's probably a new VDI created via snapshot.
2249 volume_metadata = self._linstor.get_volume_metadata(self.uuid)
2250 self.vdi_type = volume_metadata.get(VDI_TYPE_TAG)
2251 if not self.vdi_type:
2252 raise xs_errors.XenError(
2253 'VDIUnavailable',
2254 opterr='failed to get vdi_type in metadata'
2255 )
2256 self._update_device_name(self._linstor.get_volume_name(self.uuid))
2258 def _update_device_name(self, device_name):
2259 self._device_name = device_name
2261 # Mark path of VDI parent class.
2262 if device_name:
2263 self.path = self._linstor.build_device_path(self._device_name)
2264 else:
2265 self.path = None
2267 def _create_snapshot(self, snap_uuid, snap_of_uuid=None):
2268 """
2269 Snapshot self and return the snapshot VDI object.
2270 """
2272 # 1. Create a new LINSTOR volume with the same size than self.
2273 snap_path = self._linstor.shallow_clone_volume(
2274 self.uuid, snap_uuid, persistent=False
2275 )
2277 # 2. Write the snapshot content.
2278 is_raw = (self.vdi_type == vhdutil.VDI_TYPE_RAW)
2279 self.sr._vhdutil.snapshot(
2280 snap_path, self.path, is_raw, self.MAX_METADATA_VIRT_SIZE
2281 )
2283 # 3. Get snapshot parent.
2284 snap_parent = self.sr._vhdutil.get_parent(snap_uuid)
2286 # 4. Update metadata.
2287 util.SMlog('Set VDI {} metadata of snapshot'.format(snap_uuid))
2288 volume_metadata = {
2289 NAME_LABEL_TAG: util.to_plain_string(self.label),
2290 NAME_DESCRIPTION_TAG: util.to_plain_string(self.description),
2291 IS_A_SNAPSHOT_TAG: bool(snap_of_uuid),
2292 SNAPSHOT_OF_TAG: snap_of_uuid,
2293 SNAPSHOT_TIME_TAG: '',
2294 TYPE_TAG: self.ty,
2295 VDI_TYPE_TAG: vhdutil.VDI_TYPE_VHD,
2296 READ_ONLY_TAG: False,
2297 METADATA_OF_POOL_TAG: ''
2298 }
2299 self._linstor.set_volume_metadata(snap_uuid, volume_metadata)
2301 # 5. Set size.
2302 snap_vdi = LinstorVDI(self.sr, snap_uuid)
2303 if not snap_vdi._exists:
2304 raise xs_errors.XenError('VDISnapshot')
2306 volume_info = self._linstor.get_volume_info(snap_uuid)
2308 snap_vdi.size = self.sr._vhdutil.get_size_virt(snap_uuid)
2309 snap_vdi.utilisation = volume_info.allocated_size
2311 # 6. Update sm config.
2312 snap_vdi.sm_config = {}
2313 snap_vdi.sm_config['vdi_type'] = snap_vdi.vdi_type
2314 if snap_parent:
2315 snap_vdi.sm_config['vhd-parent'] = snap_parent
2316 snap_vdi.parent = snap_parent
2318 snap_vdi.label = self.label
2319 snap_vdi.description = self.description
2321 self._linstor.mark_volume_as_persistent(snap_uuid)
2323 return snap_vdi
2325 # --------------------------------------------------------------------------
2326 # Implement specific SR methods.
2327 # --------------------------------------------------------------------------
2329 def _rename(self, oldpath, newpath):
2330 # TODO: I'm not sure... Used by CBT.
2331 volume_uuid = self._linstor.get_volume_uuid_from_device_path(oldpath)
2332 self._linstor.update_volume_name(volume_uuid, newpath)
2334 def _do_snapshot(
2335 self, sr_uuid, vdi_uuid, snap_type, secondary=None, cbtlog=None
2336 ):
2337 # If cbt enabled, save file consistency state.
2338 if cbtlog is not None:
2339 if blktap2.VDI.tap_status(self.session, vdi_uuid):
2340 consistency_state = False
2341 else:
2342 consistency_state = True
2343 util.SMlog(
2344 'Saving log consistency state of {} for vdi: {}'
2345 .format(consistency_state, vdi_uuid)
2346 )
2347 else:
2348 consistency_state = None
2350 if self.vdi_type != vhdutil.VDI_TYPE_VHD:
2351 raise xs_errors.XenError('Unimplemented')
2353 if not blktap2.VDI.tap_pause(self.session, sr_uuid, vdi_uuid):
2354 raise util.SMException('Failed to pause VDI {}'.format(vdi_uuid))
2355 try:
2356 return self._snapshot(snap_type, cbtlog, consistency_state)
2357 finally:
2358 blktap2.VDI.tap_unpause(self.session, sr_uuid, vdi_uuid, secondary)
2360 def _snapshot(self, snap_type, cbtlog=None, cbt_consistency=None):
2361 util.SMlog(
2362 'LinstorVDI._snapshot for {} (type {})'
2363 .format(self.uuid, snap_type)
2364 )
2366 # 1. Checks...
2367 if self.hidden:
2368 raise xs_errors.XenError('VDIClone', opterr='hidden VDI')
2370 depth = self.sr._vhdutil.get_depth(self.uuid)
2371 if depth == -1:
2372 raise xs_errors.XenError(
2373 'VDIUnavailable',
2374 opterr='failed to get VHD depth'
2375 )
2376 elif depth >= vhdutil.MAX_CHAIN_SIZE:
2377 raise xs_errors.XenError('SnapshotChainTooLong')
2379 # Ensure we have a valid path if we don't have a local diskful.
2380 self.sr._linstor.get_device_path(self.uuid)
2382 volume_path = self.path
2383 if not util.pathexists(volume_path):
2384 raise xs_errors.XenError(
2385 'EIO',
2386 opterr='IO error checking path {}'.format(volume_path)
2387 )
2389 # 2. Create base and snap uuid (if required) and a journal entry.
2390 base_uuid = util.gen_uuid()
2391 snap_uuid = None
2393 if snap_type == VDI.SNAPSHOT_DOUBLE:
2394 snap_uuid = util.gen_uuid()
2396 clone_info = '{}_{}'.format(base_uuid, snap_uuid)
2398 active_uuid = self.uuid
2399 self.sr._journaler.create(
2400 LinstorJournaler.CLONE, active_uuid, clone_info
2401 )
2403 try:
2404 # 3. Self becomes the new base.
2405 # The device path remains the same.
2406 self._linstor.update_volume_uuid(self.uuid, base_uuid)
2407 self.uuid = base_uuid
2408 self.location = self.uuid
2409 self.read_only = True
2410 self.managed = False
2412 # 4. Create snapshots (new active and snap).
2413 active_vdi = self._create_snapshot(active_uuid)
2415 snap_vdi = None
2416 if snap_type == VDI.SNAPSHOT_DOUBLE:
2417 snap_vdi = self._create_snapshot(snap_uuid, active_uuid)
2419 self.label = 'base copy'
2420 self.description = ''
2422 # 5. Mark the base VDI as hidden so that it does not show up
2423 # in subsequent scans.
2424 self._mark_hidden()
2425 self._linstor.update_volume_metadata(
2426 self.uuid, {READ_ONLY_TAG: True}
2427 )
2429 # 6. We must update the new active VDI with the "paused" and
2430 # "host_" properties. Why? Because the original VDI has been
2431 # paused and we we must unpause it after the snapshot.
2432 # See: `tap_unpause` in `blktap2.py`.
2433 vdi_ref = self.session.xenapi.VDI.get_by_uuid(active_uuid)
2434 sm_config = self.session.xenapi.VDI.get_sm_config(vdi_ref)
2435 for key in [x for x in sm_config.keys() if x == 'paused' or x.startswith('host_')]:
2436 active_vdi.sm_config[key] = sm_config[key]
2438 # 7. Verify parent locator field of both children and
2439 # delete base if unused.
2440 introduce_parent = True
2441 try:
2442 snap_parent = None
2443 if snap_vdi:
2444 snap_parent = snap_vdi.parent
2446 if active_vdi.parent != self.uuid and (
2447 snap_type == VDI.SNAPSHOT_SINGLE or
2448 snap_type == VDI.SNAPSHOT_INTERNAL or
2449 snap_parent != self.uuid
2450 ):
2451 util.SMlog(
2452 'Destroy unused base volume: {} (path={})'
2453 .format(self.uuid, self.path)
2454 )
2455 introduce_parent = False
2456 self._linstor.destroy_volume(self.uuid)
2457 except Exception as e:
2458 util.SMlog('Ignoring exception: {}'.format(e))
2459 pass
2461 # 8. Introduce the new VDI records.
2462 if snap_vdi:
2463 # If the parent is encrypted set the key_hash for the
2464 # new snapshot disk.
2465 vdi_ref = self.sr.srcmd.params['vdi_ref']
2466 sm_config = self.session.xenapi.VDI.get_sm_config(vdi_ref)
2467 # TODO: Maybe remove key_hash support.
2468 if 'key_hash' in sm_config:
2469 snap_vdi.sm_config['key_hash'] = sm_config['key_hash']
2470 # If we have CBT enabled on the VDI,
2471 # set CBT status for the new snapshot disk.
2472 if cbtlog:
2473 snap_vdi.cbt_enabled = True
2475 if snap_vdi:
2476 snap_vdi_ref = snap_vdi._db_introduce()
2477 util.SMlog(
2478 'vdi_clone: introduced VDI: {} ({})'
2479 .format(snap_vdi_ref, snap_vdi.uuid)
2480 )
2481 if introduce_parent:
2482 base_vdi_ref = self._db_introduce()
2483 self.session.xenapi.VDI.set_managed(base_vdi_ref, False)
2484 util.SMlog(
2485 'vdi_clone: introduced VDI: {} ({})'
2486 .format(base_vdi_ref, self.uuid)
2487 )
2488 self._linstor.update_volume_metadata(self.uuid, {
2489 NAME_LABEL_TAG: util.to_plain_string(self.label),
2490 NAME_DESCRIPTION_TAG: util.to_plain_string(
2491 self.description
2492 ),
2493 READ_ONLY_TAG: True,
2494 METADATA_OF_POOL_TAG: ''
2495 })
2497 # 9. Update cbt files if user created snapshot (SNAPSHOT_DOUBLE)
2498 if snap_type == VDI.SNAPSHOT_DOUBLE and cbtlog:
2499 try:
2500 self._cbt_snapshot(snap_uuid, cbt_consistency)
2501 except Exception:
2502 # CBT operation failed.
2503 # TODO: Implement me.
2504 raise
2506 if snap_type != VDI.SNAPSHOT_INTERNAL:
2507 self.sr._update_stats(self.size)
2509 # 10. Return info on the new user-visible leaf VDI.
2510 ret_vdi = snap_vdi
2511 if not ret_vdi:
2512 ret_vdi = self
2513 if not ret_vdi:
2514 ret_vdi = active_vdi
2516 vdi_ref = self.sr.srcmd.params['vdi_ref']
2517 self.session.xenapi.VDI.set_sm_config(
2518 vdi_ref, active_vdi.sm_config
2519 )
2520 except Exception as e:
2521 util.logException('Failed to snapshot!')
2522 try:
2523 self.sr._handle_interrupted_clone(
2524 active_uuid, clone_info, force_undo=True
2525 )
2526 self.sr._journaler.remove(LinstorJournaler.CLONE, active_uuid)
2527 except Exception as e:
2528 util.SMlog(
2529 'WARNING: Failed to clean up failed snapshot: {}'
2530 .format(e)
2531 )
2532 raise xs_errors.XenError('VDIClone', opterr=str(e))
2534 self.sr._journaler.remove(LinstorJournaler.CLONE, active_uuid)
2536 return ret_vdi.get_params()
2538 @staticmethod
2539 def _start_persistent_http_server(volume_name):
2540 pid_path = None
2541 http_server = None
2543 try:
2544 if volume_name == HA_VOLUME_NAME:
2545 port = '8076'
2546 else:
2547 port = '8077'
2549 try:
2550 # Use a timeout call because XAPI may be unusable on startup
2551 # or if the host has been ejected. So in this case the call can
2552 # block indefinitely.
2553 session = util.timeout_call(5, util.get_localAPI_session)
2554 host_ip = util.get_this_host_address(session)
2555 except:
2556 # Fallback using the XHA file if session not available.
2557 host_ip, _ = get_ips_from_xha_config_file()
2558 if not host_ip:
2559 raise Exception(
2560 'Cannot start persistent HTTP server: no XAPI session, nor XHA config file'
2561 )
2563 arguments = [
2564 'http-disk-server',
2565 '--disk',
2566 '/dev/drbd/by-res/{}/0'.format(volume_name),
2567 '--ip',
2568 host_ip,
2569 '--port',
2570 port
2571 ]
2573 util.SMlog('Starting {} on port {}...'.format(arguments[0], port))
2574 http_server = subprocess.Popen(
2575 [FORK_LOG_DAEMON] + arguments,
2576 stdout=subprocess.PIPE,
2577 stderr=subprocess.STDOUT,
2578 # Ensure we use another group id to kill this process without
2579 # touch the current one.
2580 preexec_fn=os.setsid
2581 )
2583 pid_path = '/run/http-server-{}.pid'.format(volume_name)
2584 with open(pid_path, 'w') as pid_file:
2585 pid_file.write(str(http_server.pid))
2587 reg_server_ready = re.compile("Server ready!$")
2588 def is_ready():
2589 while http_server.poll() is None:
2590 line = http_server.stdout.readline()
2591 if reg_server_ready.search(line):
2592 return True
2593 return False
2594 try:
2595 if not util.timeout_call(10, is_ready):
2596 raise Exception('Failed to wait HTTP server startup, bad output')
2597 except util.TimeoutException:
2598 raise Exception('Failed to wait for HTTP server startup during given delay')
2599 except Exception as e:
2600 if pid_path:
2601 try:
2602 os.remove(pid_path)
2603 except Exception:
2604 pass
2606 if http_server:
2607 # Kill process and children in this case...
2608 try:
2609 os.killpg(os.getpgid(http_server.pid), signal.SIGTERM)
2610 except:
2611 pass
2613 raise xs_errors.XenError(
2614 'VDIUnavailable',
2615 opterr='Failed to start http-server: {}'.format(e)
2616 )
2618 def _start_persistent_nbd_server(self, volume_name):
2619 pid_path = None
2620 nbd_path = None
2621 nbd_server = None
2623 try:
2624 # We use a precomputed device size.
2625 # So if the XAPI is modified, we must update these values!
2626 if volume_name == HA_VOLUME_NAME:
2627 # See: https://github.com/xapi-project/xen-api/blob/703479fa448a8d7141954bb6e8964d8e25c4ac2e/ocaml/xapi/xha_statefile.ml#L32-L37
2628 port = '8076'
2629 device_size = 4 * 1024 * 1024
2630 else:
2631 # See: https://github.com/xapi-project/xen-api/blob/703479fa448a8d7141954bb6e8964d8e25c4ac2e/ocaml/database/redo_log.ml#L41-L44
2632 port = '8077'
2633 device_size = 256 * 1024 * 1024
2635 try:
2636 session = util.timeout_call(5, util.get_localAPI_session)
2637 ips = util.get_host_addresses(session)
2638 except Exception as e:
2639 _, ips = get_ips_from_xha_config_file()
2640 if not ips:
2641 raise Exception(
2642 'Cannot start persistent NBD server: no XAPI session, nor XHA config file ({})'.format(e)
2643 )
2644 ips = ips.values()
2646 arguments = [
2647 'nbd-http-server',
2648 '--socket-path',
2649 '/run/{}.socket'.format(volume_name),
2650 '--nbd-name',
2651 volume_name,
2652 '--urls',
2653 ','.join(['http://' + ip + ':' + port for ip in ips]),
2654 '--device-size',
2655 str(device_size)
2656 ]
2658 util.SMlog('Starting {} using port {}...'.format(arguments[0], port))
2659 nbd_server = subprocess.Popen(
2660 [FORK_LOG_DAEMON] + arguments,
2661 stdout=subprocess.PIPE,
2662 stderr=subprocess.STDOUT,
2663 # Ensure we use another group id to kill this process without
2664 # touch the current one.
2665 preexec_fn=os.setsid
2666 )
2668 pid_path = '/run/nbd-server-{}.pid'.format(volume_name)
2669 with open(pid_path, 'w') as pid_file:
2670 pid_file.write(str(nbd_server.pid))
2672 reg_nbd_path = re.compile("NBD `(/dev/nbd[0-9]+)` is now attached.$")
2673 def get_nbd_path():
2674 while nbd_server.poll() is None:
2675 line = nbd_server.stdout.readline()
2676 match = reg_nbd_path.search(line)
2677 if match:
2678 return match.group(1)
2679 # Use a timeout to never block the smapi if there is a problem.
2680 try:
2681 nbd_path = util.timeout_call(10, get_nbd_path)
2682 if nbd_path is None:
2683 raise Exception('Empty NBD path (NBD server is probably dead)')
2684 except util.TimeoutException:
2685 raise Exception('Unable to read NBD path')
2687 util.SMlog('Create symlink: {} -> {}'.format(self.path, nbd_path))
2688 os.symlink(nbd_path, self.path)
2689 except Exception as e:
2690 if pid_path:
2691 try:
2692 os.remove(pid_path)
2693 except Exception:
2694 pass
2696 if nbd_path:
2697 try:
2698 os.remove(nbd_path)
2699 except Exception:
2700 pass
2702 if nbd_server:
2703 # Kill process and children in this case...
2704 try:
2705 os.killpg(os.getpgid(nbd_server.pid), signal.SIGTERM)
2706 except:
2707 pass
2709 raise xs_errors.XenError(
2710 'VDIUnavailable',
2711 opterr='Failed to start nbd-server: {}'.format(e)
2712 )
2714 @classmethod
2715 def _kill_persistent_server(self, type, volume_name, sig):
2716 try:
2717 path = '/run/{}-server-{}.pid'.format(type, volume_name)
2718 if not os.path.exists(path):
2719 return
2721 pid = None
2722 with open(path, 'r') as pid_file:
2723 try:
2724 pid = int(pid_file.read())
2725 except Exception:
2726 pass
2728 if pid is not None and util.check_pid_exists(pid):
2729 util.SMlog('Kill {} server {} (pid={})'.format(type, path, pid))
2730 try:
2731 os.killpg(os.getpgid(pid), sig)
2732 except Exception as e:
2733 util.SMlog('Failed to kill {} server: {}'.format(type, e))
2735 os.remove(path)
2736 except:
2737 pass
2739 @classmethod
2740 def _kill_persistent_http_server(self, volume_name, sig=signal.SIGTERM):
2741 return self._kill_persistent_server('nbd', volume_name, sig)
2743 @classmethod
2744 def _kill_persistent_nbd_server(self, volume_name, sig=signal.SIGTERM):
2745 return self._kill_persistent_server('http', volume_name, sig)
2747 def _check_http_nbd_volume_name(self):
2748 volume_name = self.path[14:]
2749 if volume_name not in [
2750 HA_VOLUME_NAME, REDO_LOG_VOLUME_NAME
2751 ]:
2752 raise xs_errors.XenError(
2753 'VDIUnavailable',
2754 opterr='Unsupported path: {}'.format(self.path)
2755 )
2756 return volume_name
2758 def _attach_using_http_nbd(self):
2759 volume_name = self._check_http_nbd_volume_name()
2761 # Ensure there is no NBD and HTTP server running.
2762 self._kill_persistent_nbd_server(volume_name)
2763 self._kill_persistent_http_server(volume_name)
2765 # 0. Fetch drbd path.
2766 must_get_device_path = True
2767 if not self.sr._is_master:
2768 # We are on a slave, we must try to find a diskful locally.
2769 try:
2770 volume_info = self._linstor.get_volume_info(self.uuid)
2771 except Exception as e:
2772 raise xs_errors.XenError(
2773 'VDIUnavailable',
2774 opterr='Cannot get volume info of {}: {}'
2775 .format(self.uuid, e)
2776 )
2778 hostname = socket.gethostname()
2779 must_get_device_path = hostname in volume_info.diskful
2781 drbd_path = None
2782 if must_get_device_path or self.sr._is_master:
2783 # If we are master, we must ensure we have a diskless
2784 # or diskful available to init HA.
2785 # It also avoid this error in xensource.log
2786 # (/usr/libexec/xapi/cluster-stack/xhad/ha_set_pool_state):
2787 # init exited with code 8 [stdout = ''; stderr = 'SF: failed to write in State-File \x10 (fd 4208696). (sys 28)\x0A']
2788 # init returned MTC_EXIT_CAN_NOT_ACCESS_STATEFILE (State-File is inaccessible)
2789 available = False
2790 try:
2791 drbd_path = self._linstor.get_device_path(self.uuid)
2792 available = util.pathexists(drbd_path)
2793 except Exception:
2794 pass
2796 if not available:
2797 raise xs_errors.XenError(
2798 'VDIUnavailable',
2799 opterr='Cannot get device path of {}'.format(self.uuid)
2800 )
2802 # 1. Prepare http-nbd folder.
2803 try:
2804 if not os.path.exists('/dev/http-nbd/'):
2805 os.makedirs('/dev/http-nbd/')
2806 elif os.path.islink(self.path):
2807 os.remove(self.path)
2808 except OSError as e:
2809 if e.errno != errno.EEXIST:
2810 raise xs_errors.XenError(
2811 'VDIUnavailable',
2812 opterr='Cannot prepare http-nbd: {}'.format(e)
2813 )
2815 # 2. Start HTTP service if we have a diskful or if we are master.
2816 http_service = None
2817 if drbd_path:
2818 assert(drbd_path in (
2819 '/dev/drbd/by-res/{}/0'.format(HA_VOLUME_NAME),
2820 '/dev/drbd/by-res/{}/0'.format(REDO_LOG_VOLUME_NAME)
2821 ))
2822 self._start_persistent_http_server(volume_name)
2824 # 3. Start NBD server in all cases.
2825 try:
2826 self._start_persistent_nbd_server(volume_name)
2827 except Exception as e:
2828 if drbd_path:
2829 self._kill_persistent_http_server(volume_name)
2830 raise
2832 self.attached = True
2833 return VDI.VDI.attach(self, self.sr.uuid, self.uuid)
2835 def _detach_using_http_nbd(self):
2836 volume_name = self._check_http_nbd_volume_name()
2837 self._kill_persistent_nbd_server(volume_name)
2838 self._kill_persistent_http_server(volume_name)
2840# ------------------------------------------------------------------------------
2843if __name__ == '__main__': 2843 ↛ 2844line 2843 didn't jump to line 2844, because the condition on line 2843 was never true
2844 def run():
2845 SRCommand.run(LinstorSR, DRIVER_INFO)
2847 if not TRACE_PERFS:
2848 run()
2849 else:
2850 util.make_profile('LinstorSR', run)
2851else:
2852 SR.registerSR(LinstorSR)