Hide keyboard shortcuts

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/>. 

16 

17import base64 

18import distutils.util 

19import errno 

20import json 

21import socket 

22import util 

23import vhdutil 

24import xs_errors 

25 

26MANAGER_PLUGIN = 'linstor-manager' 

27 

28 

29def linstorhostcall(local_method, remote_method): 

30 def decorated(func): 

31 def wrapper(*args, **kwargs): 

32 self = args[0] 

33 vdi_uuid = args[1] 

34 

35 device_path = self._linstor.build_device_path( 

36 self._linstor.get_volume_name(vdi_uuid) 

37 ) 

38 

39 # A. Try a call using directly the DRBD device to avoid 

40 # remote request. 

41 

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) 

46 

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 

54 

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) 

65 

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 

75 

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 

85 

86 util.retry(exec_remote_method, 5, 3) 

87 return func(*args, **kwargs) 

88 return wrapper 

89 return decorated 

90 

91 

92class LinstorVhdUtil: 

93 def __init__(self, session, linstor): 

94 self._session = session 

95 self._linstor = linstor 

96 

97 @linstorhostcall(vhdutil.check, 'check') 

98 def check(self, vdi_uuid, **kwargs): 

99 return distutils.util.strtobool(kwargs['response']) 

100 

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) 

104 

105 @linstorhostcall(vhdutil.getVHDInfo, 'getVHDInfo') 

106 def _get_vhd_info(self, vdi_uuid, *args, **kwargs): 

107 obj = json.loads(kwargs['response']) 

108 

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'] 

117 

118 return vhd_info 

119 

120 @linstorhostcall(vhdutil.hasParent, 'hasParent') 

121 def has_parent(self, vdi_uuid, **kwargs): 

122 return distutils.util.strtobool(kwargs['response']) 

123 

124 def get_parent(self, vdi_uuid): 

125 return self._get_parent(vdi_uuid, self._extract_uuid) 

126 

127 @linstorhostcall(vhdutil.getParent, 'getParent') 

128 def _get_parent(self, vdi_uuid, *args, **kwargs): 

129 return kwargs['response'] 

130 

131 @linstorhostcall(vhdutil.getSizeVirt, 'getSizeVirt') 

132 def get_size_virt(self, vdi_uuid, **kwargs): 

133 return int(kwargs['response']) 

134 

135 @linstorhostcall(vhdutil.getSizePhys, 'getSizePhys') 

136 def get_size_phys(self, vdi_uuid, **kwargs): 

137 return int(kwargs['response']) 

138 

139 @linstorhostcall(vhdutil.getDepth, 'getDepth') 

140 def get_depth(self, vdi_uuid, **kwargs): 

141 return int(kwargs['response']) 

142 

143 @linstorhostcall(vhdutil.getKeyHash, 'getKeyHash') 

144 def get_key_hash(self, vdi_uuid, **kwargs): 

145 return kwargs['response'] or None 

146 

147 @linstorhostcall(vhdutil.getBlockBitmap, 'getBlockBitmap') 

148 def get_block_bitmap(self, vdi_uuid, **kwargs): 

149 return base64.b64decode(kwargs['response']) 

150 

151 # -------------------------------------------------------------------------- 

152 # Helpers. 

153 # -------------------------------------------------------------------------- 

154 

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 ) 

160 

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 """ 

169 

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 ) 

176 

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 

181 

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 )