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
« 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.
16import abc
18import netaddr
19from oslo_config import cfg
20from oslo_log import log
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
29LOG = log.getLogger(__name__)
31OPTS = [
32 cfg.StrOpt('ovs_integration_bridge',
33 default='br-int',
34 help=_('Name of Open vSwitch bridge to use.')),
35]
37CONF = cfg.CONF
38CONF.register_opts(OPTS)
41def device_name_synchronized(f):
42 """Wraps methods with interprocess locks by device names."""
44 def wrapped_func(self, *args, **kwargs):
45 device_name = "device_name_%s" % args[0]
47 @utils.synchronized("linux_interface_%s" % device_name, external=True)
48 def source_func(self, *args, **kwargs):
49 return f(self, *args, **kwargs)
51 return source_func(self, *args, **kwargs)
53 return wrapped_func
56class LinuxInterfaceDriver(metaclass=abc.ABCMeta):
58 # from linux IF_NAMESIZE
59 DEV_NAME_LEN = 14
60 DEV_NAME_PREFIX = 'tap'
62 def __init__(self):
63 self.conf = CONF
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.
69 ip_cidrs: list of 'X.X.X.X/YY' strings
70 """
71 device = ip_lib.IPDevice(device_name,
72 namespace=namespace)
74 for cidr in clear_cidrs:
75 device.route.clear_outdated_routes(cidr)
77 previous = {}
78 for address in device.addr.list(scope='global', filters=['permanent']):
79 previous[address['cidr']] = address['ip_version']
81 # add new addresses
82 for ip_cidr in ip_cidrs:
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
89 device.addr.add(net.version, ip_cidr, str(net.broadcast))
91 # clean up any old addresses
92 for ip_cidr, ip_version in previous.items():
93 device.addr.delete(ip_version, ip_cidr)
95 # ensure that interface is first in the list
96 device.route.pullup_route(device_name)
98 # here we are checking for garbage devices from removed service port
99 self._remove_outdated_interfaces(device)
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)
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
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)
132 def get_device_name(self, port):
133 return (self.DEV_NAME_PREFIX + port['id'])[:self.DEV_NAME_LEN]
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."""
140 @abc.abstractmethod
141 def unplug(self, device_name, bridge=None, namespace=None, prefix=None):
142 """Unplug the interface."""
145class NoopInterfaceDriver(LinuxInterfaceDriver):
146 """Noop driver when manila-share is already connected to admin network"""
148 def init_l3(self, device_name, ip_cidrs, namespace=None, clear_cidrs=[]):
149 pass
151 def plug(self, device_name, port_id, mac_address,
152 bridge=None, namespace=None, prefix=None):
153 pass
155 def unplug(self, device_name, bridge=None, namespace=None, prefix=None):
156 pass
159class OVSInterfaceDriver(LinuxInterfaceDriver):
160 """Driver for creating an internal interface on an OVS bridge."""
162 DEV_NAME_PREFIX = 'tap'
164 def _get_tap_name(self, dev_name):
165 return dev_name
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)
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
188 self.check_bridge_exists(bridge)
189 ip = ip_lib.IPWrapper()
190 ns_dev = ip.device(device_name)
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)
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)
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()
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
217 tap_name = self._get_tap_name(device_name)
218 self.check_bridge_exists(bridge)
219 ovs = ovs_lib.OVSBridge(bridge)
221 try:
222 ovs.delete_port(tap_name)
223 except RuntimeError:
224 LOG.error("Failed unplugging interface '%s'",
225 device_name)
228class BridgeInterfaceDriver(LinuxInterfaceDriver):
229 """Driver for creating bridge interfaces."""
231 DEV_NAME_PREFIX = 'ns-'
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')
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)
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)
255 root_veth.link.set_up()
256 ns_veth.link.set_up()
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)