Coverage for nova/privsep/libvirt.py: 90%

94 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-04-17 15:08 +0000

1# Copyright 2016 Red Hat, Inc 

2# Copyright 2017 Rackspace Australia 

3# 

4# Licensed under the Apache License, Version 2.0 (the "License"); you may 

5# not use this file except in compliance with the License. You may obtain 

6# a copy of the License at 

7# 

8# http://www.apache.org/licenses/LICENSE-2.0 

9# 

10# Unless required by applicable law or agreed to in writing, software 

11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 

12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 

13# License for the specific language governing permissions and limitations 

14# under the License. 

15 

16""" 

17libvirt specific routines. 

18""" 

19 

20import binascii 

21import os 

22import stat 

23 

24from oslo_concurrency import processutils 

25from oslo_log import log as logging 

26from oslo_utils import units 

27from oslo_utils import uuidutils 

28 

29import nova.privsep 

30 

31 

32LOG = logging.getLogger(__name__) 

33 

34 

35@nova.privsep.sys_admin_pctxt.entrypoint 

36def dmcrypt_create_volume(target, device, cipher, key_size, key): 

37 """Sets up a dmcrypt mapping 

38 

39 :param target: device mapper logical device name 

40 :param device: underlying block device 

41 :param cipher: encryption cipher string digestible by cryptsetup 

42 :param key_size: encryption key size 

43 :param key: encoded encryption key bytestring 

44 """ 

45 cmd = ('cryptsetup', 

46 'create', 

47 target, 

48 device, 

49 '--cipher=' + cipher, 

50 '--key-size=' + str(key_size), 

51 '--key-file=-') 

52 key = binascii.hexlify(key).decode('utf-8') 

53 processutils.execute(*cmd, process_input=key) 

54 

55 

56@nova.privsep.sys_admin_pctxt.entrypoint 

57def dmcrypt_delete_volume(target): 

58 """Deletes a dmcrypt mapping 

59 

60 :param target: name of the mapped logical device 

61 """ 

62 processutils.execute('cryptsetup', 'remove', target) 

63 

64 

65@nova.privsep.sys_admin_pctxt.entrypoint 

66def ploop_init(size, disk_format, fs_type, disk_path): 

67 """Initialize ploop disk, make it readable for non-root user 

68 

69 :param disk_format: data allocation format (raw or expanded) 

70 :param fs_type: filesystem (ext4, ext3, none) 

71 :param disk_path: ploop image file 

72 """ 

73 processutils.execute('ploop', 'init', '-s', size, '-f', disk_format, '-t', 

74 fs_type, disk_path, check_exit_code=True) 

75 

76 # Add read access for all users, because "ploop init" creates 

77 # disk with rw rights only for root. OpenStack user should have access 

78 # to the disk to request info via "qemu-img info" 

79 # TODO(mikal): this is a faithful rendition of the pre-privsep code from 

80 # the libvirt driver, but it seems undesirable to me. It would be good to 

81 # create the loop file with the right owner or group such that we don't 

82 # need to have it world readable. I don't have access to a system to test 

83 # this on however. 

84 st = os.stat(disk_path) 

85 os.chmod(disk_path, st.st_mode | stat.S_IROTH) 

86 

87 

88@nova.privsep.sys_admin_pctxt.entrypoint 

89def ploop_resize(disk_path, size): 

90 """Resize ploop disk 

91 

92 :param disk_path: ploop image file 

93 :param size: new size (in bytes) 

94 """ 

95 processutils.execute('prl_disk_tool', 'resize', 

96 '--size', '%dM' % (size // units.Mi), 

97 '--resize_partition', 

98 '--hdd', disk_path, 

99 check_exit_code=True) 

100 

101 

102@nova.privsep.sys_admin_pctxt.entrypoint 

103def ploop_restore_descriptor(image_dir, base_delta, fmt): 

104 """Restore ploop disk descriptor XML 

105 

106 :param image_dir: path to where descriptor XML is created 

107 :param base_delta: ploop image file containing the data 

108 :param fmt: ploop data allocation format (raw or expanded) 

109 """ 

110 processutils.execute('ploop', 'restore-descriptor', '-f', fmt, 

111 image_dir, base_delta, 

112 check_exit_code=True) 

113 

114 

115@nova.privsep.sys_admin_pctxt.entrypoint 

116def plug_infiniband_vif(vnic_mac, device_id, fabric, net_model, pci_slot): 

117 processutils.execute('ebrctl', 'add-port', vnic_mac, device_id, 

118 fabric, net_model, pci_slot) 

119 

120 

121@nova.privsep.sys_admin_pctxt.entrypoint 

122def unplug_infiniband_vif(fabric, vnic_mac): 

123 processutils.execute('ebrctl', 'del-port', fabric, vnic_mac) 

124 

125 

126@nova.privsep.sys_admin_pctxt.entrypoint 

127def plug_midonet_vif(port_id, dev): 

128 processutils.execute('mm-ctl', '--bind-port', port_id, dev) 

129 

130 

131@nova.privsep.sys_admin_pctxt.entrypoint 

132def unplug_midonet_vif(port_id): 

133 processutils.execute('mm-ctl', '--unbind-port', port_id) 

134 

135 

136@nova.privsep.sys_admin_pctxt.entrypoint 

137def plug_plumgrid_vif(dev, iface_id, vif_address, net_id, tenant_id): 

138 processutils.execute('ifc_ctl', 'gateway', 'add_port', dev) 

139 processutils.execute('ifc_ctl', 'gateway', 'ifup', dev, 

140 'access_vm', iface_id, vif_address, 

141 'pgtag2=%s' % net_id, 'pgtag1=%s' % tenant_id) 

142 

143 

144@nova.privsep.sys_admin_pctxt.entrypoint 

145def unplug_plumgrid_vif(dev): 

146 processutils.execute('ifc_ctl', 'gateway', 'ifdown', dev) 

147 processutils.execute('ifc_ctl', 'gateway', 'del_port', dev) 

148 

149 

150@nova.privsep.sys_admin_pctxt.entrypoint 

151def readpty(path): 

152 # TODO(mikal): I'm not a huge fan that we don't enforce a valid pty path 

153 # here, but I haven't come up with a great way of doing that. 

154 

155 # NOTE(mikal): I am deliberately not catching the ImportError 

156 # exception here... Some platforms (I'm looking at you Windows) 

157 # don't have a fcntl and we may as well let them know that 

158 # with an ImportError, not that they should be calling this at all. 

159 import fcntl 

160 

161 try: 

162 with open(path, 'r') as f: 

163 current_flags = fcntl.fcntl(f.fileno(), fcntl.F_GETFL) 

164 fcntl.fcntl(f.fileno(), fcntl.F_SETFL, 

165 current_flags | os.O_NONBLOCK) 

166 

167 return f.read() 

168 

169 except Exception as exc: 

170 # NOTE(mikal): dear internet, I see you looking at me with your 

171 # judging eyes. There's a story behind why we do this. You see, the 

172 # previous implementation did this: 

173 # 

174 # out, err = utils.execute('dd', 

175 # 'if=%s' % pty, 

176 # 'iflag=nonblock', 

177 # run_as_root=True, 

178 # check_exit_code=False) 

179 # return out 

180 # 

181 # So, it never checked stderr or the return code of the process it 

182 # ran to read the pty. Doing something better than that has turned 

183 # out to be unexpectedly hard because there are a surprisingly large 

184 # variety of errors which appear to be thrown when doing this read. 

185 # 

186 # Therefore for now we log the errors, but keep on rolling. Volunteers 

187 # to help clean this up are welcome and will receive free beverages. 

188 LOG.info( 

189 'Ignored error while reading from instance console pty: %s', exc 

190 ) 

191 return '' 

192 

193 

194@nova.privsep.sys_admin_pctxt.entrypoint 

195def create_mdev(physical_device, mdev_type, uuid=None): 

196 """Instantiate a mediated device.""" 

197 if uuid is None: 197 ↛ 198line 197 didn't jump to line 198 because the condition on line 197 was never true

198 uuid = uuidutils.generate_uuid() 

199 fpath = '/sys/class/mdev_bus/{0}/mdev_supported_types/{1}/create' 

200 fpath = fpath.format(physical_device, mdev_type) 

201 with open(fpath, 'w') as f: 

202 f.write(uuid) 

203 return uuid 

204 

205 

206@nova.privsep.sys_admin_pctxt.entrypoint 

207def systemd_run_qb_mount(qb_vol, mnt_base, cfg_file=None): 

208 """Mount QB volume in separate CGROUP""" 

209 # Note(kaisers): Details on why we run without --user at bug #1756823 

210 sysdr_cmd = ['systemd-run', '--scope', 'mount.quobyte', '--disable-xattrs', 

211 qb_vol, mnt_base] 

212 if cfg_file: 

213 sysdr_cmd.extend(['-c', cfg_file]) 

214 return processutils.execute(*sysdr_cmd) 

215 

216 

217# NOTE(kaisers): this method is deliberately not wrapped in a privsep entry. 

218def unprivileged_qb_mount(qb_vol, mnt_base, cfg_file=None): 

219 """Mount QB volume""" 

220 mnt_cmd = ['mount.quobyte', '--disable-xattrs', qb_vol, mnt_base] 

221 if cfg_file: 

222 mnt_cmd.extend(['-c', cfg_file]) 

223 return processutils.execute(*mnt_cmd) 

224 

225 

226@nova.privsep.sys_admin_pctxt.entrypoint 

227def umount(mnt_base): 

228 """Unmount volume""" 

229 unprivileged_umount(mnt_base) 

230 

231 

232# NOTE(kaisers): this method is deliberately not wrapped in a privsep entry. 

233def unprivileged_umount(mnt_base): 

234 """Unmount volume""" 

235 umnt_cmd = ['umount', mnt_base] 

236 return processutils.execute(*umnt_cmd) 

237 

238 

239@nova.privsep.sys_admin_pctxt.entrypoint 

240def get_pmem_namespaces(): 

241 ndctl_cmd = ['ndctl', 'list', '-X'] 

242 nss_info = processutils.execute(*ndctl_cmd)[0] 

243 return nss_info 

244 

245 

246@nova.privsep.sys_admin_pctxt.entrypoint 

247def cleanup_vpmem(devpath): 

248 daxio_cmd = ['daxio', '-z', '-o', '%s' % devpath] 

249 processutils.execute(*daxio_cmd)