Coverage for libs/sm/trim_util.py: 100%

96 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2026-07-10 07:59 +0000

1# Copyright (C) Citrix Systems Inc. 

2# 

3# This program is free software; you can redistribute it and/or modify 

4# it under the terms of the GNU Lesser General Public License as published 

5# by the Free Software Foundation; version 2.1 only. 

6# 

7# This program is distributed in the hope that it will be useful, 

8# but WITHOUT ANY WARRANTY; without even the implied warranty of 

9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

10# GNU Lesser General Public License for more details. 

11# 

12# You should have received a copy of the GNU Lesser General Public License 

13# along with this program; if not, write to the Free Software Foundation, Inc., 

14# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 

15# 

16# A plugin for enabling trim on LVM based SRs to free up storage space 

17# in Storage arrays. 

18 

19import xml 

20import os 

21import time 

22from sm.core import util 

23from sm.core import lock 

24from sm import lvutil 

25from sm import lvhdutil 

26from sm import vhdutil 

27 

28TRIM_LV_TAG = "_trim_lv" 

29TRIM_CAP = "SR_TRIM" 

30LOCK_RETRY_ATTEMPTS = 3 

31LOCK_RETRY_INTERVAL = 1 

32ERROR_CODE_KEY = "errcode" 

33ERROR_MSG_KEY = "errmsg" 

34 

35TRIM_LAST_TRIGGERED_KEY = "trim_last_triggered" 

36MASTER_LVM_CONF = '/etc/lvm/master' 

37 

38 

39def _vg_by_sr_uuid(sr_uuid): 

40 return lvhdutil.VG_PREFIX + sr_uuid 

41 

42 

43def _lvpath_by_vg_lv_name(vg_name, lv_name): 

44 return os.path.join(lvhdutil.VG_LOCATION, vg_name, lv_name) 

45 

46 

47def to_xml(d): 

48 

49 dom = xml.dom.minidom.Document() 

50 trim_response = dom.createElement("trim_response") 

51 dom.appendChild(trim_response) 

52 

53 for key, value in sorted(d.items()): 

54 key_value_element = dom.createElement("key_value_pair") 

55 trim_response.appendChild(key_value_element) 

56 

57 key_element = dom.createElement("key") 

58 key_text_node = dom.createTextNode(key) 

59 key_element.appendChild(key_text_node) 

60 key_value_element.appendChild(key_element) 

61 

62 value_element = dom.createElement("value") 

63 value_text_mode = dom.createTextNode(value) 

64 value_element.appendChild(value_text_mode) 

65 key_value_element.appendChild(value_element) 

66 

67 return dom.toxml() 

68 

69 

70def _handle_discard_error(vg_name, sr_uuid, error): 

71 if "BLKDISCARD ioctl failed: Operation not supported" not in error.reason: 

72 err_msg = { 

73 ERROR_CODE_KEY: 'TrimException', 

74 ERROR_MSG_KEY: error.reason 

75 } 

76 return to_xml(err_msg) 

77 else: 

78 return str(True) 

79 

80 

81# Note: This function is expected to be called from a context where 

82# the SR is locked by the thread calling the function; therefore removing 

83# any risk of a race condition updating the LAST_TRIGGERED value. 

84def _log_last_triggered(session, sr_uuid): 

85 try: 

86 sr_ref = session.xenapi.SR.get_by_uuid(sr_uuid) 

87 other_config = session.xenapi.SR.get_other_config(sr_ref) 

88 if TRIM_LAST_TRIGGERED_KEY in other_config: 

89 session.xenapi.SR.remove_from_other_config(sr_ref, TRIM_LAST_TRIGGERED_KEY) 

90 session.xenapi.SR.add_to_other_config(sr_ref, TRIM_LAST_TRIGGERED_KEY, str(time.time())) 

91 except: 

92 util.logException("Unable to set other-config:%s" % TRIM_LAST_TRIGGERED_KEY) 

93 

94 

95def do_trim(session, args): 

96 """Attempt to trim the given LVHDSR""" 

97 util.SMlog("do_trim: %s" % args) 

98 sr_uuid = args["sr_uuid"] 

99 os.environ['LVM_SYSTEM_DIR'] = MASTER_LVM_CONF 

100 

101 if TRIM_CAP not in util.sr_get_capability(sr_uuid, session=session): 

102 util.SMlog("Trim command ignored on unsupported SR %s" % sr_uuid) 

103 err_msg = {ERROR_CODE_KEY: 'UnsupportedSRForTrim', 

104 ERROR_MSG_KEY: 'Trim on [%s] not supported' % sr_uuid} 

105 return to_xml(err_msg) 

106 

107 # Lock SR, get vg empty space details 

108 sr_lock = lock.Lock(vhdutil.LOCK_TYPE_SR, sr_uuid) 

109 got_lock = False 

110 for i in range(LOCK_RETRY_ATTEMPTS): 

111 got_lock = sr_lock.acquireNoblock() 

112 if got_lock: 

113 break 

114 time.sleep(LOCK_RETRY_INTERVAL) 

115 

116 if got_lock: 

117 try: 

118 vg_name = _vg_by_sr_uuid(sr_uuid) 

119 lv_name = sr_uuid + TRIM_LV_TAG 

120 lv_path = _lvpath_by_vg_lv_name(vg_name, lv_name) 

121 

122 # Clean trim LV in case the previous trim attemp failed 

123 if lvutil.exists(lv_path): 

124 lvutil.remove(lv_path) 

125 

126 #Check if VG limits are enough for creating LV. 

127 stats = lvutil._getVGstats(vg_name) 

128 if (stats['freespace'] < lvutil.LVM_SIZE_INCREMENT): 

129 util.SMlog("No space to claim on a full SR %s" % sr_uuid) 

130 err_msg = {ERROR_CODE_KEY: 'Trim failed on full SR', 

131 ERROR_MSG_KEY: 'No space to claim on a full SR'} 

132 result = to_xml(err_msg) 

133 else: 

134 # Perform a lvcreate, blkdiscard and lvremove to 

135 # trigger trim on the array 

136 lvutil.create(lv_name, 0, vg_name, size_in_percentage="100%F") 

137 cmd = ["/usr/sbin/blkdiscard", "-v", lv_path] 

138 stdout = util.pread2(cmd) 

139 util.SMlog("Stdout is %s" % stdout) 

140 util.SMlog("Trim on SR: %s complete. " % sr_uuid) 

141 result = str(True) 

142 except util.CommandException as e: 

143 result = _handle_discard_error(vg_name, sr_uuid, e) 

144 except: 

145 err_msg = { 

146 ERROR_CODE_KEY: 'UnknownTrimException', 

147 ERROR_MSG_KEY: 'Unknown Exception: trim failed on SR [%s]' 

148 % sr_uuid 

149 } 

150 result = to_xml(err_msg) 

151 finally: 

152 if lvutil.exists(lv_path): 

153 lvutil.remove(lv_path) 

154 

155 _log_last_triggered(session, sr_uuid) 

156 

157 sr_lock.release() 

158 return result 

159 else: 

160 util.SMlog("Could not complete Trim on %s, Lock unavailable !" \ 

161 % sr_uuid) 

162 err_msg = {ERROR_CODE_KEY: 'SRUnavailable', 

163 ERROR_MSG_KEY: 'Unable to get SR lock [%s]' % sr_uuid} 

164 return to_xml(err_msg)