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

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. 

15 

16import functools 

17 

18import netaddr 

19from oslo_serialization import jsonutils 

20 

21from nova import exception 

22from nova.i18n import _ 

23from nova import utils 

24 

25 

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' 

48 

49 

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' 

57 

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' 

61 

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' 

66 

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' 

72 

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' 

89 

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' 

93 

94# Open vSwitch datapath types. 

95VIF_DETAILS_OVS_DATAPATH_SYSTEM = 'system' 

96VIF_DETAILS_OVS_DATAPATH_NETDEV = 'netdev' 

97 

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" 

110 

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) 

127 

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) 

135 

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) 

140 

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' 

152 

153VIF_MODEL_SRIOV = 'sriov' 

154VIF_MODEL_VMXNET = 'vmxnet' 

155VIF_MODEL_VMXNET3 = 'vmxnet3' 

156 

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) 

172 

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) 

184 

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 

189 

190 

191class Model(dict): 

192 """Defines some necessary structures for most of the network models.""" 

193 

194 def __repr__(self): 

195 return jsonutils.dumps(self) 

196 

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) 

202 

203 def get_meta(self, key, default=None): 

204 """calls get(key, default) on self['meta'].""" 

205 return self['meta'].get(key, default) 

206 

207 

208class IP(Model): 

209 """Represents an IP address in Nova.""" 

210 

211 def __init__(self, address=None, type=None, **kwargs): 

212 super(IP, self).__init__() 

213 

214 self['address'] = address 

215 self['type'] = type 

216 self['version'] = kwargs.pop('version', None) 

217 

218 self._set_meta(kwargs) 

219 

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) 

227 

228 def __eq__(self, other): 

229 keys = ['address', 'type', 'version'] 

230 return all(self[k] == other[k] for k in keys) 

231 

232 def __ne__(self, other): 

233 return not self.__eq__(other) 

234 

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 

241 

242 @classmethod 

243 def hydrate(cls, ip): 

244 if ip: 

245 return cls(**ip) 

246 return None 

247 

248 

249class FixedIP(IP): 

250 """Represents a Fixed IP address in Nova.""" 

251 

252 def __init__(self, floating_ips=None, **kwargs): 

253 super(FixedIP, self).__init__(**kwargs) 

254 self['floating_ips'] = floating_ips or [] 

255 

256 if not self['type']: 

257 self['type'] = 'fixed' 

258 

259 def add_floating_ip(self, floating_ip): 

260 if floating_ip not in self['floating_ips']: 

261 self['floating_ips'].append(floating_ip) 

262 

263 def floating_ip_addresses(self): 

264 return [ip['address'] for ip in self['floating_ips']] 

265 

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 

272 

273 def __eq__(self, other): 

274 keys = ['address', 'type', 'version', 'floating_ips'] 

275 return all(self[k] == other[k] for k in keys) 

276 

277 def __ne__(self, other): 

278 return not self.__eq__(other) 

279 

280 

281class Route(Model): 

282 """Represents an IP Route in Nova.""" 

283 

284 def __init__(self, cidr=None, gateway=None, interface=None, **kwargs): 

285 super(Route, self).__init__() 

286 

287 self['cidr'] = cidr 

288 self['gateway'] = gateway 

289 # FIXME(mriedem): Is this actually used? It's never set. 

290 self['interface'] = interface 

291 

292 self._set_meta(kwargs) 

293 

294 @classmethod 

295 def hydrate(cls, route): 

296 route = cls(**route) 

297 route['gateway'] = IP.hydrate(route['gateway']) 

298 return route 

299 

300 

301class Subnet(Model): 

302 """Represents a Subnet in Nova.""" 

303 

304 def __init__(self, cidr=None, dns=None, gateway=None, ips=None, 

305 routes=None, **kwargs): 

306 super(Subnet, self).__init__() 

307 

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) 

314 

315 self._set_meta(kwargs) 

316 

317 if self['cidr'] and not self['version']: 

318 self['version'] = netaddr.IPNetwork(self['cidr']).version 

319 

320 def __eq__(self, other): 

321 keys = ['cidr', 'dns', 'gateway', 'ips', 'routes', 'version'] 

322 return all(self[k] == other[k] for k in keys) 

323 

324 def __ne__(self, other): 

325 return not self.__eq__(other) 

326 

327 def add_route(self, new_route): 

328 if new_route not in self['routes']: 

329 self['routes'].append(new_route) 

330 

331 def add_dns(self, dns): 

332 if dns not in self['dns']: 

333 self['dns'].append(dns) 

334 

335 def add_ip(self, ip): 

336 if ip not in self['ips']: 

337 self['ips'].append(ip) 

338 

339 def as_netaddr(self): 

340 """Convenient function to get cidr as a netaddr object.""" 

341 return netaddr.IPNetwork(self['cidr']) 

342 

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 

351 

352 

353class Network(Model): 

354 """Represents a Network in Nova.""" 

355 

356 def __init__(self, id=None, bridge=None, label=None, 

357 subnets=None, **kwargs): 

358 super(Network, self).__init__() 

359 

360 self['id'] = id 

361 self['bridge'] = bridge 

362 self['label'] = label 

363 self['subnets'] = subnets or [] 

364 

365 self._set_meta(kwargs) 

366 

367 def add_subnet(self, subnet): 

368 if subnet not in self['subnets']: 

369 self['subnets'].append(subnet) 

370 

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 

378 

379 def __eq__(self, other): 

380 keys = ['id', 'bridge', 'label', 'subnets'] 

381 return all(self[k] == other[k] for k in keys) 

382 

383 def __ne__(self, other): 

384 return not self.__eq__(other) 

385 

386 

387class VIF8021QbgParams(Model): 

388 """Represents the parameters for a 802.1qbg VIF.""" 

389 

390 def __init__(self, managerid, typeid, typeidversion, instanceid): 

391 super(VIF8021QbgParams, self).__init__() 

392 

393 self['managerid'] = managerid 

394 self['typeid'] = typeid 

395 self['typeidversion'] = typeidversion 

396 self['instanceid'] = instanceid 

397 

398 

399class VIF8021QbhParams(Model): 

400 """Represents the parameters for a 802.1qbh VIF.""" 

401 

402 def __init__(self, profileid): 

403 super(VIF8021QbhParams, self).__init__() 

404 

405 self['profileid'] = profileid 

406 

407 

408class VIF(Model): 

409 """Represents a Virtual Interface in Nova.""" 

410 

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__() 

418 

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 

425 

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 

434 

435 self._set_meta(kwargs) 

436 

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) 

443 

444 def __ne__(self, other): 

445 return not self.__eq__(other) 

446 

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 [] 

453 

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']] 

457 

458 def labeled_ips(self): 

459 """Returns the list of all IPs 

460 

461 The return value looks like this flat structure:: 

462 

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 [] 

491 

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() 

499 

500 def is_hybrid_plug_enabled(self): 

501 return self['details'].get(VIF_DETAILS_OVS_HYBRID_PLUG, False) 

502 

503 def is_neutron_filtering_enabled(self): 

504 return self['details'].get(VIF_DETAILS_PORT_FILTER, False) 

505 

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 

511 

512 @classmethod 

513 def hydrate(cls, vif): 

514 vif = cls(**vif) 

515 vif['network'] = Network.hydrate(vif['network']) 

516 return vif 

517 

518 def has_allocation(self): 

519 return self['profile'] and bool(self['profile'].get('allocation')) 

520 

521 

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 

527 

528 

529class NetworkInfo(list): 

530 """Stores and manipulates network information for a Nova instance.""" 

531 

532 # NetworkInfo is a list of VIFs 

533 

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()] 

537 

538 def floating_ips(self): 

539 """Returns all floating_ips.""" 

540 return [ip for vif in self for ip in vif.floating_ips()] 

541 

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]) 

547 

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 

556 

557 def json(self): 

558 return jsonutils.dumps(self) 

559 

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] 

566 

567 def has_port_with_allocation(self): 

568 return any(vif.has_allocation() for vif in self) 

569 

570 

571class NetworkInfoAsyncWrapper(NetworkInfo): 

572 """Wrapper around NetworkInfo that allows retrieving NetworkInfo 

573 in an async manner. 

574 

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. 

580 

581 As an example: 

582 

583 def allocate_net_info(arg1, arg2) 

584 return call_neutron_to_allocate(arg1, arg2) 

585 

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 """ 

591 

592 def __init__(self, async_method, *args, **kwargs): 

593 super(NetworkInfoAsyncWrapper, self).__init__() 

594 

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) 

602 

603 def _sync_wrapper(self, wrapped, *args, **kwargs): 

604 """Synchronize the model before running a method.""" 

605 self.wait() 

606 return wrapped(*args, **kwargs) 

607 

608 def __getitem__(self, *args, **kwargs): 

609 fn = super(NetworkInfoAsyncWrapper, self).__getitem__ 

610 return self._sync_wrapper(fn, *args, **kwargs) 

611 

612 def __iter__(self, *args, **kwargs): 

613 fn = super(NetworkInfoAsyncWrapper, self).__iter__ 

614 return self._sync_wrapper(fn, *args, **kwargs) 

615 

616 def __len__(self, *args, **kwargs): 

617 fn = super(NetworkInfoAsyncWrapper, self).__len__ 

618 return self._sync_wrapper(fn, *args, **kwargs) 

619 

620 def __str__(self, *args, **kwargs): 

621 fn = super(NetworkInfoAsyncWrapper, self).__str__ 

622 return self._sync_wrapper(fn, *args, **kwargs) 

623 

624 def __repr__(self, *args, **kwargs): 

625 fn = super(NetworkInfoAsyncWrapper, self).__repr__ 

626 return self._sync_wrapper(fn, *args, **kwargs) 

627 

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