Coverage for drivers/linstorvhdutil.py : 29%

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/>.
17import base64
18import distutils.util
19import errno
20import json
21import socket
22import util
23import vhdutil
24import xs_errors
26MANAGER_PLUGIN = 'linstor-manager'
29def linstorhostcall(local_method, remote_method):
30 def decorated(func):
31 def wrapper(*args, **kwargs):
32 self = args[0]
33 vdi_uuid = args[1]
35 device_path = self._linstor.build_device_path(
36 self._linstor.get_volume_name(vdi_uuid)
37 )
39 # A. Try a call using directly the DRBD device to avoid
40 # remote request.
42 # Try to read locally if the device is not in use or if the device
43 # is up to date and not diskless.
44 (node_names, in_use) = \
45 self._linstor.find_up_to_date_diskfull_nodes(vdi_uuid)
47 try:
48 if not in_use or socket.gethostname() in node_names:
49 return local_method(device_path, *args[2:], **kwargs)
50 except util.CommandException as e:
51 # EMEDIUMTYPE constant (124) is not available in python2.
52 if e.code != errno.EROFS and e.code != 124:
53 raise
55 # B. Execute the plugin on master or slave.
56 def exec_remote_method():
57 host_ref = self._get_readonly_host(
58 vdi_uuid, device_path, node_names
59 )
60 args = {
61 'devicePath': device_path,
62 'groupName': self._linstor.group_name
63 }
64 args.update(**kwargs)
66 try:
67 response = self._session.xenapi.host.call_plugin(
68 host_ref, MANAGER_PLUGIN, remote_method, args
69 )
70 except Exception as e:
71 util.SMlog('call-plugin ({} with {}) exception: {}'.format(
72 remote_method, args, e
73 ))
74 raise
76 util.SMlog('call-plugin ({} with {}) returned: {}'.format(
77 remote_method, args, response
78 ))
79 if response == 'False':
80 raise xs_errors.XenError(
81 'VDIUnavailable',
82 opterr='Plugin {} failed'.format(MANAGER_PLUGIN)
83 )
84 kwargs['response'] = response
86 util.retry(exec_remote_method, 5, 3)
87 return func(*args, **kwargs)
88 return wrapper
89 return decorated
92class LinstorVhdUtil:
93 def __init__(self, session, linstor):
94 self._session = session
95 self._linstor = linstor
97 @linstorhostcall(vhdutil.check, 'check')
98 def check(self, vdi_uuid, **kwargs):
99 return distutils.util.strtobool(kwargs['response'])
101 def get_vhd_info(self, vdi_uuid, include_parent=True):
102 kwargs = {'includeParent': str(include_parent)}
103 return self._get_vhd_info(vdi_uuid, self._extract_uuid, **kwargs)
105 @linstorhostcall(vhdutil.getVHDInfo, 'getVHDInfo')
106 def _get_vhd_info(self, vdi_uuid, *args, **kwargs):
107 obj = json.loads(kwargs['response'])
109 vhd_info = vhdutil.VHDInfo(vdi_uuid)
110 vhd_info.sizeVirt = obj['sizeVirt']
111 vhd_info.sizePhys = obj['sizePhys']
112 if 'parentPath' in obj:
113 vhd_info.parentPath = obj['parentPath']
114 vhd_info.parentUuid = obj['parentUuid']
115 vhd_info.hidden = obj['hidden']
116 vhd_info.path = obj['path']
118 return vhd_info
120 @linstorhostcall(vhdutil.hasParent, 'hasParent')
121 def has_parent(self, vdi_uuid, **kwargs):
122 return distutils.util.strtobool(kwargs['response'])
124 def get_parent(self, vdi_uuid):
125 return self._get_parent(vdi_uuid, self._extract_uuid)
127 @linstorhostcall(vhdutil.getParent, 'getParent')
128 def _get_parent(self, vdi_uuid, *args, **kwargs):
129 return kwargs['response']
131 @linstorhostcall(vhdutil.getSizeVirt, 'getSizeVirt')
132 def get_size_virt(self, vdi_uuid, **kwargs):
133 return int(kwargs['response'])
135 @linstorhostcall(vhdutil.getSizePhys, 'getSizePhys')
136 def get_size_phys(self, vdi_uuid, **kwargs):
137 return int(kwargs['response'])
139 @linstorhostcall(vhdutil.getDepth, 'getDepth')
140 def get_depth(self, vdi_uuid, **kwargs):
141 return int(kwargs['response'])
143 @linstorhostcall(vhdutil.getKeyHash, 'getKeyHash')
144 def get_key_hash(self, vdi_uuid, **kwargs):
145 return kwargs['response'] or None
147 @linstorhostcall(vhdutil.getBlockBitmap, 'getBlockBitmap')
148 def get_block_bitmap(self, vdi_uuid, **kwargs):
149 return base64.b64decode(kwargs['response'])
151 # --------------------------------------------------------------------------
152 # Helpers.
153 # --------------------------------------------------------------------------
155 def _extract_uuid(self, device_path):
156 # TODO: Remove new line in the vhdutil module. Not here.
157 return self._linstor.get_volume_uuid_from_device_path(
158 device_path.rstrip('\n')
159 )
161 def _get_readonly_host(self, vdi_uuid, device_path, node_names):
162 """
163 When vhd-util is called to fetch VDI info we must find a
164 diskfull DRBD disk to read the data. It's the goal of this function.
165 Why? Because when a VHD is open in RO mode, the LVM layer is used
166 directly to bypass DRBD verifications (we can have only one process
167 that reads/writes to disk with DRBD devices).
168 """
170 if not node_names:
171 raise xs_errors.XenError(
172 'VDIUnavailable',
173 opterr='Unable to find diskfull node: {} (path={})'
174 .format(vdi_uuid, device_path)
175 )
177 hosts = self._session.xenapi.host.get_all_records()
178 for host_ref, host_record in hosts.items():
179 if host_record['hostname'] in node_names:
180 return host_ref
182 raise xs_errors.XenError(
183 'VDIUnavailable',
184 opterr='Unable to find a valid host from VDI: {} (path={})'
185 .format(vdi_uuid, device_path)
186 )