Coverage for manila/network/linux/interface.py: 89%

145 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2026-02-18 22:19 +0000

1# Copyright 2014 Mirantis Inc. 

2# All Rights Reserved. 

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 

16import abc 

17 

18import netaddr 

19from oslo_config import cfg 

20from oslo_log import log 

21 

22from manila import exception 

23from manila.i18n import _ 

24from manila.network.linux import ip_lib 

25from manila.network.linux import ovs_lib 

26from manila import utils 

27 

28 

29LOG = log.getLogger(__name__) 

30 

31OPTS = [ 

32 cfg.StrOpt('ovs_integration_bridge', 

33 default='br-int', 

34 help=_('Name of Open vSwitch bridge to use.')), 

35] 

36 

37CONF = cfg.CONF 

38CONF.register_opts(OPTS) 

39 

40 

41def device_name_synchronized(f): 

42 """Wraps methods with interprocess locks by device names.""" 

43 

44 def wrapped_func(self, *args, **kwargs): 

45 device_name = "device_name_%s" % args[0] 

46 

47 @utils.synchronized("linux_interface_%s" % device_name, external=True) 

48 def source_func(self, *args, **kwargs): 

49 return f(self, *args, **kwargs) 

50 

51 return source_func(self, *args, **kwargs) 

52 

53 return wrapped_func 

54 

55 

56class LinuxInterfaceDriver(metaclass=abc.ABCMeta): 

57 

58 # from linux IF_NAMESIZE 

59 DEV_NAME_LEN = 14 

60 DEV_NAME_PREFIX = 'tap' 

61 

62 def __init__(self): 

63 self.conf = CONF 

64 

65 @device_name_synchronized 

66 def init_l3(self, device_name, ip_cidrs, namespace=None, clear_cidrs=[]): 

67 """Set the L3 settings for the interface using data from the port. 

68 

69 ip_cidrs: list of 'X.X.X.X/YY' strings 

70 """ 

71 device = ip_lib.IPDevice(device_name, 

72 namespace=namespace) 

73 

74 for cidr in clear_cidrs: 

75 device.route.clear_outdated_routes(cidr) 

76 

77 previous = {} 

78 for address in device.addr.list(scope='global', filters=['permanent']): 

79 previous[address['cidr']] = address['ip_version'] 

80 

81 # add new addresses 

82 for ip_cidr in ip_cidrs: 

83 

84 net = netaddr.IPNetwork(ip_cidr) 

85 if ip_cidr in previous: 85 ↛ 86line 85 didn't jump to line 86 because the condition on line 85 was never true

86 del previous[ip_cidr] 

87 continue 

88 

89 device.addr.add(net.version, ip_cidr, str(net.broadcast)) 

90 

91 # clean up any old addresses 

92 for ip_cidr, ip_version in previous.items(): 

93 device.addr.delete(ip_version, ip_cidr) 

94 

95 # ensure that interface is first in the list 

96 device.route.pullup_route(device_name) 

97 

98 # here we are checking for garbage devices from removed service port 

99 self._remove_outdated_interfaces(device) 

100 

101 def _remove_outdated_interfaces(self, device): 

102 """Finds and removes unused network device.""" 

103 device_cidr_set = self._get_set_of_device_cidrs(device) 

104 for dev in ip_lib.IPWrapper().get_devices(): 

105 if dev.name != device.name and dev.name[:3] == device.name[:3]: 105 ↛ 104line 105 didn't jump to line 104 because the condition on line 105 was always true

106 cidr_set = self._get_set_of_device_cidrs(dev) 

107 if device_cidr_set & cidr_set: 107 ↛ 104line 107 didn't jump to line 104 because the condition on line 107 was always true

108 self.unplug(dev.name) 

109 

110 def _get_set_of_device_cidrs(self, device): 

111 cidrs = set() 

112 addr_list = [] 

113 try: 

114 # NOTE(ganso): I could call ip_lib.device_exists here, but since 

115 # this is a concurrency problem, it would not fix the problem. 

116 addr_list = device.addr.list() 

117 except Exception as e: 

118 if 'does not exist' in str(e): 118 ↛ 122line 118 didn't jump to line 122 because the condition on line 118 was always true

119 LOG.warning( 

120 "Device %s does not exist anymore.", device.name) 

121 else: 

122 raise 

123 for addr in addr_list: 

124 if addr['ip_version'] == 4: 

125 cidrs.add(str(netaddr.IPNetwork(addr['cidr']).cidr)) 

126 return cidrs 

127 

128 def check_bridge_exists(self, bridge): 

129 if not ip_lib.device_exists(bridge): 129 ↛ 130line 129 didn't jump to line 130 because the condition on line 129 was never true

130 raise exception.BridgeDoesNotExist(bridge=bridge) 

131 

132 def get_device_name(self, port): 

133 return (self.DEV_NAME_PREFIX + port['id'])[:self.DEV_NAME_LEN] 

134 

135 @abc.abstractmethod 

136 def plug(self, device_name, port_id, mac_address, 

137 bridge=None, namespace=None, prefix=None): 

138 """Plug in the interface.""" 

139 

140 @abc.abstractmethod 

141 def unplug(self, device_name, bridge=None, namespace=None, prefix=None): 

142 """Unplug the interface.""" 

143 

144 

145class NoopInterfaceDriver(LinuxInterfaceDriver): 

146 """Noop driver when manila-share is already connected to admin network""" 

147 

148 def init_l3(self, device_name, ip_cidrs, namespace=None, clear_cidrs=[]): 

149 pass 

150 

151 def plug(self, device_name, port_id, mac_address, 

152 bridge=None, namespace=None, prefix=None): 

153 pass 

154 

155 def unplug(self, device_name, bridge=None, namespace=None, prefix=None): 

156 pass 

157 

158 

159class OVSInterfaceDriver(LinuxInterfaceDriver): 

160 """Driver for creating an internal interface on an OVS bridge.""" 

161 

162 DEV_NAME_PREFIX = 'tap' 

163 

164 def _get_tap_name(self, dev_name): 

165 return dev_name 

166 

167 def _ovs_add_port(self, bridge, device_name, port_id, mac_address, 

168 internal=True): 

169 cmd = ['ovs-vsctl', '--', '--may-exist', 

170 'add-port', bridge, device_name] 

171 if internal: 171 ↛ 173line 171 didn't jump to line 173 because the condition on line 171 was always true

172 cmd += ['--', 'set', 'Interface', device_name, 'type=internal'] 

173 cmd += ['--', 'set', 'Interface', device_name, 

174 'external-ids:iface-id=%s' % port_id, 

175 '--', 'set', 'Interface', device_name, 

176 'external-ids:iface-status=active', 

177 '--', 'set', 'Interface', device_name, 

178 'external-ids:attached-mac=%s' % mac_address] 

179 utils.execute(*cmd, run_as_root=True) 

180 

181 @device_name_synchronized 

182 def plug(self, device_name, port_id, mac_address, 

183 bridge=None, namespace=None, prefix=None): 

184 """Plug in the interface.""" 

185 if not bridge: 185 ↛ 186line 185 didn't jump to line 186 because the condition on line 185 was never true

186 bridge = self.conf.ovs_integration_bridge 

187 

188 self.check_bridge_exists(bridge) 

189 ip = ip_lib.IPWrapper() 

190 ns_dev = ip.device(device_name) 

191 

192 if not ip_lib.device_exists(device_name, 

193 namespace=namespace): 

194 LOG.info("Device %s does not exist - creating ....", device_name) 

195 tap_name = self._get_tap_name(device_name) 

196 self._ovs_add_port(bridge, tap_name, port_id, mac_address) 

197 ns_dev.link.set_address(mac_address) 

198 

199 # Add an interface created by ovs to the namespace. 

200 if namespace: 

201 namespace_obj = ip.ensure_namespace(namespace) 

202 namespace_obj.add_device_to_namespace(ns_dev) 

203 

204 else: 

205 LOG.info("Device %s already exists.", device_name) 

206 if ns_dev.link.address != mac_address: 206 ↛ 209line 206 didn't jump to line 209 because the condition on line 206 was always true

207 LOG.warning("Reset mac address to %s", mac_address) 

208 ns_dev.link.set_address(mac_address) 

209 ns_dev.link.set_up() 

210 

211 @device_name_synchronized 

212 def unplug(self, device_name, bridge=None, namespace=None, prefix=None): 

213 """Unplug the interface.""" 

214 if not bridge: 214 ↛ 217line 214 didn't jump to line 217 because the condition on line 214 was always true

215 bridge = self.conf.ovs_integration_bridge 

216 

217 tap_name = self._get_tap_name(device_name) 

218 self.check_bridge_exists(bridge) 

219 ovs = ovs_lib.OVSBridge(bridge) 

220 

221 try: 

222 ovs.delete_port(tap_name) 

223 except RuntimeError: 

224 LOG.error("Failed unplugging interface '%s'", 

225 device_name) 

226 

227 

228class BridgeInterfaceDriver(LinuxInterfaceDriver): 

229 """Driver for creating bridge interfaces.""" 

230 

231 DEV_NAME_PREFIX = 'ns-' 

232 

233 @device_name_synchronized 

234 def plug(self, device_name, port_id, mac_address, 

235 bridge=None, namespace=None, prefix=None): 

236 """Plugin the interface.""" 

237 ip = ip_lib.IPWrapper() 

238 if prefix: 238 ↛ 239line 238 didn't jump to line 239 because the condition on line 238 was never true

239 tap_name = device_name.replace(prefix, 'tap') 

240 else: 

241 tap_name = device_name.replace(self.DEV_NAME_PREFIX, 'tap') 

242 

243 if not ip_lib.device_exists(device_name, 

244 namespace=namespace): 

245 # Create ns_veth in a namespace if one is configured. 

246 root_veth, ns_veth = ip.add_veth(tap_name, device_name, 

247 namespace2=namespace) 

248 ns_veth.link.set_address(mac_address) 

249 

250 else: 

251 ns_veth = ip.device(device_name) 

252 root_veth = ip.device(tap_name) 

253 LOG.warning("Device %s already exists.", device_name) 

254 

255 root_veth.link.set_up() 

256 ns_veth.link.set_up() 

257 

258 @device_name_synchronized 

259 def unplug(self, device_name, bridge=None, namespace=None, prefix=None): 

260 """Unplug the interface.""" 

261 device = ip_lib.IPDevice(device_name, namespace) 

262 try: 

263 device.link.delete() 

264 LOG.debug("Unplugged interface '%s'", device_name) 

265 except RuntimeError: 

266 LOG.error("Failed unplugging interface '%s'", 

267 device_name)