Coverage for nova/network/model.py: 95%
325 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-04-17 15:08 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-04-17 15:08 +0000
1# Copyright 2011 OpenStack Foundation
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 functools
18import netaddr
19from oslo_serialization import jsonutils
21from nova import exception
22from nova.i18n import _
23from nova import utils
26# Constants for the 'vif_type' field in VIF class
27VIF_TYPE_OVS = 'ovs'
28VIF_TYPE_IVS = 'ivs'
29VIF_TYPE_DVS = 'dvs'
30VIF_TYPE_IOVISOR = 'iovisor'
31VIF_TYPE_BRIDGE = 'bridge'
32VIF_TYPE_802_QBG = '802.1qbg'
33VIF_TYPE_802_QBH = '802.1qbh'
34VIF_TYPE_HW_VEB = 'hw_veb'
35VIF_TYPE_HYPERV = 'hyperv'
36VIF_TYPE_HOSTDEV = 'hostdev_physical'
37VIF_TYPE_IB_HOSTDEV = 'ib_hostdev'
38VIF_TYPE_MIDONET = 'midonet'
39VIF_TYPE_VHOSTUSER = 'vhostuser'
40VIF_TYPE_VROUTER = 'vrouter'
41VIF_TYPE_OTHER = 'other'
42VIF_TYPE_TAP = 'tap'
43VIF_TYPE_MACVTAP = 'macvtap'
44VIF_TYPE_AGILIO_OVS = 'agilio_ovs'
45VIF_TYPE_BINDING_FAILED = 'binding_failed'
46VIF_TYPE_VIF = 'vif'
47VIF_TYPE_UNBOUND = 'unbound'
50# Constants for dictionary keys in the 'vif_details' field in the VIF
51# class
52VIF_DETAILS_PORT_FILTER = 'port_filter'
53VIF_DETAILS_OVS_HYBRID_PLUG = 'ovs_hybrid_plug'
54VIF_DETAILS_PHYSICAL_NETWORK = 'physical_network'
55VIF_DETAILS_BRIDGE_NAME = 'bridge_name'
56VIF_DETAILS_OVS_DATAPATH_TYPE = 'datapath_type'
58# The following constant defines an SR-IOV related parameter in the
59# 'vif_details'. 'profileid' should be used for VIF_TYPE_802_QBH
60VIF_DETAILS_PROFILEID = 'profileid'
62# The following constant defines an SR-IOV and macvtap related parameter in
63# the 'vif_details'. 'vlan' should be used for VIF_TYPE_HW_VEB or
64# VIF_TYPE_MACVTAP
65VIF_DETAILS_VLAN = 'vlan'
67# The following three constants define the macvtap related fields in
68# the 'vif_details'.
69VIF_DETAILS_MACVTAP_SOURCE = 'macvtap_source'
70VIF_DETAILS_MACVTAP_MODE = 'macvtap_mode'
71VIF_DETAILS_PHYS_INTERFACE = 'physical_interface'
73# Constants for vhost-user related fields in 'vif_details'.
74# Sets mode on vhost-user socket, valid values are 'client'
75# and 'server'
76VIF_DETAILS_VHOSTUSER_MODE = 'vhostuser_mode'
77# vhost-user socket path
78VIF_DETAILS_VHOSTUSER_SOCKET = 'vhostuser_socket'
79# Specifies whether vhost-user socket should be plugged
80# into ovs bridge. Valid values are True and False
81VIF_DETAILS_VHOSTUSER_OVS_PLUG = 'vhostuser_ovs_plug'
82# Specifies whether vhost-user socket should be used to
83# create a fp netdevice interface.
84VIF_DETAILS_VHOSTUSER_FP_PLUG = 'vhostuser_fp_plug'
85# Specifies whether vhost-user socket should be used to
86# create a vrouter netdevice interface
87# TODO(mhenkel): Consider renaming this to be contrail-specific.
88VIF_DETAILS_VHOSTUSER_VROUTER_PLUG = 'vhostuser_vrouter_plug'
90# Constants for dictionary keys in the 'vif_details' field that are
91# valid for VIF_TYPE_TAP.
92VIF_DETAILS_TAP_MAC_ADDRESS = 'mac_address'
94# Open vSwitch datapath types.
95VIF_DETAILS_OVS_DATAPATH_SYSTEM = 'system'
96VIF_DETAILS_OVS_DATAPATH_NETDEV = 'netdev'
98# Define supported virtual NIC types. VNIC_TYPE_DIRECT and VNIC_TYPE_MACVTAP
99# are used for SR-IOV ports
100VNIC_TYPE_NORMAL = 'normal'
101VNIC_TYPE_DIRECT = 'direct'
102VNIC_TYPE_MACVTAP = 'macvtap'
103VNIC_TYPE_DIRECT_PHYSICAL = 'direct-physical'
104VNIC_TYPE_BAREMETAL = 'baremetal'
105VNIC_TYPE_VIRTIO_FORWARDER = 'virtio-forwarder'
106VNIC_TYPE_VDPA = 'vdpa'
107VNIC_TYPE_ACCELERATOR_DIRECT = 'accelerator-direct'
108VNIC_TYPE_ACCELERATOR_DIRECT_PHYSICAL = 'accelerator-direct-physical'
109VNIC_TYPE_REMOTE_MANAGED = "remote-managed"
111# Define list of ports which needs pci request.
112# Note: The macvtap port needs a PCI request as it is a tap interface
113# with VF as the lower physical interface.
114# Note: Currently, VNIC_TYPE_VIRTIO_FORWARDER assumes a 1:1
115# relationship with a VF. This is expected to change in the future.
116# Note:
117# VNIC_TYPE_ACCELERATOR_DIRECT and VNIC_TYPE_ACCELERATOR_DIRECT_PHYSICAL
118# does not need a PCI request, these devices are not tracked by the pci
119# tracker in nova but tracked by cyborg. The scheduling will use the
120# cyborg provided resource request to find a compute with such devices,
121# and the device claiming will be done via binding the cyborg arqs to the
122# selected compute node.
123VNIC_TYPES_SRIOV = (
124 VNIC_TYPE_DIRECT, VNIC_TYPE_MACVTAP, VNIC_TYPE_DIRECT_PHYSICAL,
125 VNIC_TYPE_VIRTIO_FORWARDER, VNIC_TYPE_VDPA, VNIC_TYPE_REMOTE_MANAGED
126)
128# Define list of ports which are passthrough to the guest
129# and need a special treatment on snapshot and suspend/resume
130VNIC_TYPES_DIRECT_PASSTHROUGH = (
131 VNIC_TYPE_DIRECT, VNIC_TYPE_DIRECT_PHYSICAL,
132 VNIC_TYPE_ACCELERATOR_DIRECT, VNIC_TYPE_ACCELERATOR_DIRECT_PHYSICAL,
133 VNIC_TYPE_REMOTE_MANAGED, VNIC_TYPE_VDPA
134)
136# Define list of ports which contains devices managed by cyborg.
137VNIC_TYPES_ACCELERATOR = (
138 VNIC_TYPE_ACCELERATOR_DIRECT, VNIC_TYPE_ACCELERATOR_DIRECT_PHYSICAL
139)
141# Constants for the 'vif_model' values
142VIF_MODEL_VIRTIO = 'virtio'
143VIF_MODEL_NE2K_PCI = 'ne2k_pci'
144VIF_MODEL_PCNET = 'pcnet'
145VIF_MODEL_RTL8139 = 'rtl8139'
146VIF_MODEL_E1000 = 'e1000'
147VIF_MODEL_E1000E = 'e1000e'
148VIF_MODEL_NETFRONT = 'netfront'
149VIF_MODEL_SPAPR_VLAN = 'spapr-vlan'
150VIF_MODEL_LAN9118 = 'lan9118'
151VIF_MODEL_IGB = 'igb'
153VIF_MODEL_SRIOV = 'sriov'
154VIF_MODEL_VMXNET = 'vmxnet'
155VIF_MODEL_VMXNET3 = 'vmxnet3'
157VIF_MODEL_ALL = (
158 VIF_MODEL_VIRTIO,
159 VIF_MODEL_NE2K_PCI,
160 VIF_MODEL_PCNET,
161 VIF_MODEL_RTL8139,
162 VIF_MODEL_E1000,
163 VIF_MODEL_E1000E,
164 VIF_MODEL_NETFRONT,
165 VIF_MODEL_SPAPR_VLAN,
166 VIF_MODEL_LAN9118,
167 VIF_MODEL_SRIOV,
168 VIF_MODEL_VMXNET,
169 VIF_MODEL_VMXNET3,
170 VIF_MODEL_IGB,
171)
173# these types have been leaked to guests in network_data.json
174LEGACY_EXPOSED_VIF_TYPES = (
175 VIF_TYPE_BRIDGE,
176 VIF_TYPE_DVS,
177 VIF_TYPE_HW_VEB,
178 VIF_TYPE_HYPERV,
179 VIF_TYPE_OVS,
180 VIF_TYPE_TAP,
181 VIF_TYPE_VHOSTUSER,
182 VIF_TYPE_VIF,
183)
185# Constant for max length of network interface names
186# eg 'bridge' in the Network class or 'devname' in
187# the VIF class
188NIC_NAME_LEN = 14
191class Model(dict):
192 """Defines some necessary structures for most of the network models."""
194 def __repr__(self):
195 return jsonutils.dumps(self)
197 def _set_meta(self, kwargs):
198 # pull meta out of kwargs if it's there
199 self['meta'] = kwargs.pop('meta', {})
200 # update meta with any additional kwargs that may exist
201 self['meta'].update(kwargs)
203 def get_meta(self, key, default=None):
204 """calls get(key, default) on self['meta']."""
205 return self['meta'].get(key, default)
208class IP(Model):
209 """Represents an IP address in Nova."""
211 def __init__(self, address=None, type=None, **kwargs):
212 super(IP, self).__init__()
214 self['address'] = address
215 self['type'] = type
216 self['version'] = kwargs.pop('version', None)
218 self._set_meta(kwargs)
220 # determine version from address if not passed in
221 if self['address'] and not self['version']:
222 try:
223 self['version'] = netaddr.IPAddress(self['address']).version
224 except netaddr.AddrFormatError:
225 msg = _("Invalid IP format %s") % self['address']
226 raise exception.InvalidIpAddressError(msg)
228 def __eq__(self, other):
229 keys = ['address', 'type', 'version']
230 return all(self[k] == other[k] for k in keys)
232 def __ne__(self, other):
233 return not self.__eq__(other)
235 def is_in_subnet(self, subnet):
236 if self['address'] and subnet['cidr']: 236 ↛ 240line 236 didn't jump to line 240 because the condition on line 236 was always true
237 return (netaddr.IPAddress(self['address']) in
238 netaddr.IPNetwork(subnet['cidr']))
239 else:
240 return False
242 @classmethod
243 def hydrate(cls, ip):
244 if ip:
245 return cls(**ip)
246 return None
249class FixedIP(IP):
250 """Represents a Fixed IP address in Nova."""
252 def __init__(self, floating_ips=None, **kwargs):
253 super(FixedIP, self).__init__(**kwargs)
254 self['floating_ips'] = floating_ips or []
256 if not self['type']:
257 self['type'] = 'fixed'
259 def add_floating_ip(self, floating_ip):
260 if floating_ip not in self['floating_ips']:
261 self['floating_ips'].append(floating_ip)
263 def floating_ip_addresses(self):
264 return [ip['address'] for ip in self['floating_ips']]
266 @staticmethod
267 def hydrate(fixed_ip):
268 fixed_ip = FixedIP(**fixed_ip)
269 fixed_ip['floating_ips'] = [IP.hydrate(floating_ip)
270 for floating_ip in fixed_ip['floating_ips']]
271 return fixed_ip
273 def __eq__(self, other):
274 keys = ['address', 'type', 'version', 'floating_ips']
275 return all(self[k] == other[k] for k in keys)
277 def __ne__(self, other):
278 return not self.__eq__(other)
281class Route(Model):
282 """Represents an IP Route in Nova."""
284 def __init__(self, cidr=None, gateway=None, interface=None, **kwargs):
285 super(Route, self).__init__()
287 self['cidr'] = cidr
288 self['gateway'] = gateway
289 # FIXME(mriedem): Is this actually used? It's never set.
290 self['interface'] = interface
292 self._set_meta(kwargs)
294 @classmethod
295 def hydrate(cls, route):
296 route = cls(**route)
297 route['gateway'] = IP.hydrate(route['gateway'])
298 return route
301class Subnet(Model):
302 """Represents a Subnet in Nova."""
304 def __init__(self, cidr=None, dns=None, gateway=None, ips=None,
305 routes=None, **kwargs):
306 super(Subnet, self).__init__()
308 self['cidr'] = cidr
309 self['dns'] = dns or []
310 self['gateway'] = gateway
311 self['ips'] = ips or []
312 self['routes'] = routes or []
313 self['version'] = kwargs.pop('version', None)
315 self._set_meta(kwargs)
317 if self['cidr'] and not self['version']:
318 self['version'] = netaddr.IPNetwork(self['cidr']).version
320 def __eq__(self, other):
321 keys = ['cidr', 'dns', 'gateway', 'ips', 'routes', 'version']
322 return all(self[k] == other[k] for k in keys)
324 def __ne__(self, other):
325 return not self.__eq__(other)
327 def add_route(self, new_route):
328 if new_route not in self['routes']:
329 self['routes'].append(new_route)
331 def add_dns(self, dns):
332 if dns not in self['dns']:
333 self['dns'].append(dns)
335 def add_ip(self, ip):
336 if ip not in self['ips']:
337 self['ips'].append(ip)
339 def as_netaddr(self):
340 """Convenient function to get cidr as a netaddr object."""
341 return netaddr.IPNetwork(self['cidr'])
343 @classmethod
344 def hydrate(cls, subnet):
345 subnet = cls(**subnet)
346 subnet['dns'] = [IP.hydrate(dns) for dns in subnet['dns']]
347 subnet['ips'] = [FixedIP.hydrate(ip) for ip in subnet['ips']]
348 subnet['routes'] = [Route.hydrate(route) for route in subnet['routes']]
349 subnet['gateway'] = IP.hydrate(subnet['gateway'])
350 return subnet
353class Network(Model):
354 """Represents a Network in Nova."""
356 def __init__(self, id=None, bridge=None, label=None,
357 subnets=None, **kwargs):
358 super(Network, self).__init__()
360 self['id'] = id
361 self['bridge'] = bridge
362 self['label'] = label
363 self['subnets'] = subnets or []
365 self._set_meta(kwargs)
367 def add_subnet(self, subnet):
368 if subnet not in self['subnets']:
369 self['subnets'].append(subnet)
371 @classmethod
372 def hydrate(cls, network):
373 if network:
374 network = cls(**network)
375 network['subnets'] = [Subnet.hydrate(subnet)
376 for subnet in network['subnets']]
377 return network
379 def __eq__(self, other):
380 keys = ['id', 'bridge', 'label', 'subnets']
381 return all(self[k] == other[k] for k in keys)
383 def __ne__(self, other):
384 return not self.__eq__(other)
387class VIF8021QbgParams(Model):
388 """Represents the parameters for a 802.1qbg VIF."""
390 def __init__(self, managerid, typeid, typeidversion, instanceid):
391 super(VIF8021QbgParams, self).__init__()
393 self['managerid'] = managerid
394 self['typeid'] = typeid
395 self['typeidversion'] = typeidversion
396 self['instanceid'] = instanceid
399class VIF8021QbhParams(Model):
400 """Represents the parameters for a 802.1qbh VIF."""
402 def __init__(self, profileid):
403 super(VIF8021QbhParams, self).__init__()
405 self['profileid'] = profileid
408class VIF(Model):
409 """Represents a Virtual Interface in Nova."""
411 def __init__(self, id=None, address=None, network=None, type=None,
412 details=None, devname=None, ovs_interfaceid=None,
413 qbh_params=None, qbg_params=None, active=False,
414 vnic_type=VNIC_TYPE_NORMAL, profile=None,
415 preserve_on_delete=False, delegate_create=False,
416 **kwargs):
417 super(VIF, self).__init__()
419 self['id'] = id
420 self['address'] = address
421 self['network'] = network or None
422 self['type'] = type
423 self['details'] = details or {}
424 self['devname'] = devname
426 self['ovs_interfaceid'] = ovs_interfaceid
427 self['qbh_params'] = qbh_params
428 self['qbg_params'] = qbg_params
429 self['active'] = active
430 self['vnic_type'] = vnic_type
431 self['profile'] = profile
432 self['preserve_on_delete'] = preserve_on_delete
433 self['delegate_create'] = delegate_create
435 self._set_meta(kwargs)
437 def __eq__(self, other):
438 keys = ['id', 'address', 'network', 'vnic_type',
439 'type', 'profile', 'details', 'devname',
440 'ovs_interfaceid', 'qbh_params', 'qbg_params',
441 'active', 'preserve_on_delete', 'delegate_create']
442 return all(self[k] == other[k] for k in keys)
444 def __ne__(self, other):
445 return not self.__eq__(other)
447 def fixed_ips(self):
448 if self['network']:
449 return [fixed_ip for subnet in self['network']['subnets']
450 for fixed_ip in subnet['ips']]
451 else:
452 return []
454 def floating_ips(self):
455 return [floating_ip for fixed_ip in self.fixed_ips()
456 for floating_ip in fixed_ip['floating_ips']]
458 def labeled_ips(self):
459 """Returns the list of all IPs
461 The return value looks like this flat structure::
463 {'network_label': 'my_network',
464 'network_id': 'n8v29837fn234782f08fjxk3ofhb84',
465 'ips': [{'address': '123.123.123.123',
466 'version': 4,
467 'type: 'fixed',
468 'meta': {...}},
469 {'address': '124.124.124.124',
470 'version': 4,
471 'type': 'floating',
472 'meta': {...}},
473 {'address': 'fe80::4',
474 'version': 6,
475 'type': 'fixed',
476 'meta': {...}}]
477 """
478 if self['network']: 478 ↛ 490line 478 didn't jump to line 490 because the condition on line 478 was always true
479 # remove unnecessary fields on fixed_ips
480 ips = [IP(**ip) for ip in self.fixed_ips()]
481 for ip in ips:
482 # remove floating ips from IP, since this is a flat structure
483 # of all IPs
484 del ip['meta']['floating_ips']
485 # add floating ips to list (if any)
486 ips.extend(self.floating_ips())
487 return {'network_label': self['network']['label'],
488 'network_id': self['network']['id'],
489 'ips': ips}
490 return []
492 @property
493 def has_live_migration_plug_time_event(self):
494 """Returns whether this VIF's network-vif-plugged external event will
495 be sent by Neutron at "plugtime" - in other words, as soon as neutron
496 completes configuring the network backend.
497 """
498 return self.is_hybrid_plug_enabled()
500 def is_hybrid_plug_enabled(self):
501 return self['details'].get(VIF_DETAILS_OVS_HYBRID_PLUG, False)
503 def is_neutron_filtering_enabled(self):
504 return self['details'].get(VIF_DETAILS_PORT_FILTER, False)
506 def get_physical_network(self):
507 phy_network = self['network']['meta'].get('physical_network')
508 if not phy_network:
509 phy_network = self['details'].get(VIF_DETAILS_PHYSICAL_NETWORK)
510 return phy_network
512 @classmethod
513 def hydrate(cls, vif):
514 vif = cls(**vif)
515 vif['network'] = Network.hydrate(vif['network'])
516 return vif
518 def has_allocation(self):
519 return self['profile'] and bool(self['profile'].get('allocation'))
522def get_netmask(ip, subnet):
523 """Returns the netmask appropriate for injection into a guest."""
524 if ip['version'] == 4:
525 return str(subnet.as_netaddr().netmask)
526 return subnet.as_netaddr()._prefixlen
529class NetworkInfo(list):
530 """Stores and manipulates network information for a Nova instance."""
532 # NetworkInfo is a list of VIFs
534 def fixed_ips(self):
535 """Returns all fixed_ips without floating_ips attached."""
536 return [ip for vif in self for ip in vif.fixed_ips()]
538 def floating_ips(self):
539 """Returns all floating_ips."""
540 return [ip for vif in self for ip in vif.floating_ips()]
542 @classmethod
543 def hydrate(cls, network_info):
544 if isinstance(network_info, str):
545 network_info = jsonutils.loads(network_info)
546 return cls([VIF.hydrate(vif) for vif in network_info])
548 def wait(self, do_raise=True):
549 """Wait for asynchronous call to finish."""
550 # There is no asynchronous call for this class, so this is a no-op
551 # here, but subclasses may override to provide asynchronous
552 # capabilities. Must be defined here in the parent class so that code
553 # which works with both parent and subclass types can reference this
554 # method.
555 pass
557 def json(self):
558 return jsonutils.dumps(self)
560 def get_live_migration_plug_time_events(self):
561 """Returns a list of external events for any VIFs that have
562 "plug-time" events during live migration.
563 """
564 return [('network-vif-plugged', vif['id'])
565 for vif in self if vif.has_live_migration_plug_time_event]
567 def has_port_with_allocation(self):
568 return any(vif.has_allocation() for vif in self)
571class NetworkInfoAsyncWrapper(NetworkInfo):
572 """Wrapper around NetworkInfo that allows retrieving NetworkInfo
573 in an async manner.
575 This allows one to start querying for network information before
576 you know you will need it. If you have a long-running
577 operation, this allows the network model retrieval to occur in the
578 background. When you need the data, it will ensure the async
579 operation has completed.
581 As an example:
583 def allocate_net_info(arg1, arg2)
584 return call_neutron_to_allocate(arg1, arg2)
586 network_info = NetworkInfoAsyncWrapper(allocate_net_info, arg1, arg2)
587 [do a long running operation -- real network_info will be retrieved
588 in the background]
589 [do something with network_info]
590 """
592 def __init__(self, async_method, *args, **kwargs):
593 super(NetworkInfoAsyncWrapper, self).__init__()
595 self._gt = utils.spawn(async_method, *args, **kwargs)
596 methods = ['json', 'fixed_ips', 'floating_ips']
597 for method in methods:
598 fn = getattr(self, method)
599 wrapper = functools.partial(self._sync_wrapper, fn)
600 functools.update_wrapper(wrapper, fn)
601 setattr(self, method, wrapper)
603 def _sync_wrapper(self, wrapped, *args, **kwargs):
604 """Synchronize the model before running a method."""
605 self.wait()
606 return wrapped(*args, **kwargs)
608 def __getitem__(self, *args, **kwargs):
609 fn = super(NetworkInfoAsyncWrapper, self).__getitem__
610 return self._sync_wrapper(fn, *args, **kwargs)
612 def __iter__(self, *args, **kwargs):
613 fn = super(NetworkInfoAsyncWrapper, self).__iter__
614 return self._sync_wrapper(fn, *args, **kwargs)
616 def __len__(self, *args, **kwargs):
617 fn = super(NetworkInfoAsyncWrapper, self).__len__
618 return self._sync_wrapper(fn, *args, **kwargs)
620 def __str__(self, *args, **kwargs):
621 fn = super(NetworkInfoAsyncWrapper, self).__str__
622 return self._sync_wrapper(fn, *args, **kwargs)
624 def __repr__(self, *args, **kwargs):
625 fn = super(NetworkInfoAsyncWrapper, self).__repr__
626 return self._sync_wrapper(fn, *args, **kwargs)
628 def wait(self, do_raise=True):
629 """Wait for asynchronous call to finish."""
630 if self._gt is not None:
631 try:
632 # NOTE(comstud): This looks funky, but this object is
633 # subclassed from list. In other words, 'self' is really
634 # just a list with a bunch of extra methods. So this
635 # line just replaces the current list (which should be
636 # empty) with the result.
637 self[:] = self._gt.wait()
638 except Exception:
639 if do_raise:
640 raise
641 finally:
642 self._gt = None