Coverage for manila/network/neutron/neutron_network_plugin.py: 92%

327 statements  

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

1# Copyright 2013 OpenStack Foundation 

2# Copyright 2015 Mirantis, Inc. 

3# All Rights Reserved. 

4# 

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

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

7# a copy of the License at 

8# 

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

10# 

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

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

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

14# License for the specific language governing permissions and limitations 

15# under the License. 

16 

17import ipaddress 

18import socket 

19 

20from oslo_config import cfg 

21from oslo_log import log 

22 

23from manila.common import constants 

24from manila import exception 

25from manila.i18n import _ 

26from manila import network 

27from manila.network.neutron import api as neutron_api 

28from manila.network.neutron import constants as neutron_constants 

29from manila.share import utils as share_utils 

30from manila import utils 

31 

32LOG = log.getLogger(__name__) 

33 

34neutron_network_plugin_opts = [ 

35 cfg.StrOpt( 

36 'neutron_physical_net_name', 

37 help="The name of the physical network to determine which net segment " 

38 "is used. This opt is optional and will only be used for " 

39 "networks configured with multiple segments."), 

40] 

41 

42neutron_single_network_plugin_opts = [ 

43 cfg.StrOpt( 

44 'neutron_net_id', 

45 help="Default Neutron network that will be used for share server " 

46 "creation. This opt is used only with " 

47 "class 'NeutronSingleNetworkPlugin'."), 

48 cfg.StrOpt( 

49 'neutron_subnet_id', 

50 help="Default Neutron subnet that will be used for share server " 

51 "creation. Should be assigned to network defined in opt " 

52 "'neutron_net_id'. This opt is used only with " 

53 "class 'NeutronSingleNetworkPlugin'."), 

54] 

55 

56neutron_bind_network_plugin_opts = [ 

57 cfg.StrOpt( 

58 'neutron_vnic_type', 

59 help="vNIC type used for binding.", 

60 choices=['baremetal', 'normal', 'direct', 

61 'direct-physical', 'macvtap'], 

62 default='baremetal'), 

63 cfg.StrOpt( 

64 "neutron_host_id", 

65 help="Host ID to be used when creating neutron port. If not set " 

66 "host is set to manila-share host by default.", 

67 default=socket.gethostname()), 

68] 

69 

70neutron_binding_profile = [ 

71 cfg.ListOpt( 

72 "neutron_binding_profiles", 

73 help="A list of binding profiles to be used during port binding. This " 

74 "option can be used with the NeutronBindNetworkPlugin. The value for " 

75 "this option has to be a comma separated list of names that " 

76 "correspond to each binding profile. Each binding profile needs to be " 

77 "specified as an individual configuration section using the binding " 

78 "profile name as the section name."), 

79] 

80 

81neutron_binding_profile_opts = [ 

82 cfg.StrOpt( 

83 'neutron_switch_id', 

84 help="Switch ID for binding profile."), 

85 cfg.StrOpt( 

86 'neutron_port_id', 

87 help="Port ID on the given switch.",), 

88 cfg.DictOpt( 

89 'neutron_switch_info', 

90 help="Switch label. For example: 'switch_ip: 10.4.30.5'. Multiple " 

91 "key-value pairs separated by commas are accepted.",), 

92] 

93 

94CONF = cfg.CONF 

95 

96 

97class NeutronNetworkPlugin(network.NetworkBaseAPI): 

98 

99 def __init__(self, *args, **kwargs): 

100 db_driver = kwargs.pop('db_driver', None) 

101 config_group_name = kwargs.get('config_group_name', 'DEFAULT') 

102 super(NeutronNetworkPlugin, 

103 self).__init__(config_group_name=config_group_name, 

104 db_driver=db_driver) 

105 self._neutron_api = None 

106 self._neutron_api_args = args 

107 self._neutron_api_kwargs = kwargs 

108 self._label = kwargs.pop('label', 'user') 

109 CONF.register_opts( 

110 neutron_network_plugin_opts, 

111 group=self.neutron_api.config_group_name) 

112 

113 @property 

114 def label(self): 

115 return self._label 

116 

117 @property 

118 @utils.synchronized("instantiate_neutron_api") 

119 def neutron_api(self): 

120 if not self._neutron_api: 

121 self._neutron_api = neutron_api.API(*self._neutron_api_args, 

122 **self._neutron_api_kwargs) 

123 return self._neutron_api 

124 

125 def include_network_info(self, share_network_subnet): 

126 """Includes share-network-subnet with plugin specific data.""" 

127 self._store_and_get_neutron_net_info(None, 

128 share_network_subnet, 

129 save_db=False) 

130 

131 def _store_and_get_neutron_net_info(self, context, share_network_subnet, 

132 save_db=True): 

133 is_external_network = self._save_neutron_network_data( 

134 context, share_network_subnet, save_db=save_db) 

135 self._save_neutron_subnet_data(context, share_network_subnet, 

136 save_db=save_db) 

137 return is_external_network 

138 

139 def allocate_network(self, context, share_server, share_network=None, 

140 share_network_subnet=None, **kwargs): 

141 """Allocate network resources using given network information. 

142 

143 Create neutron ports for a given neutron network and subnet, 

144 create manila db records for allocated neutron ports. 

145 

146 :param context: RequestContext object 

147 :param share_server: share server data 

148 :param share_network: share network data 

149 :param share_network_subnet: share network subnet data 

150 :param kwargs: allocations parameters given by the back-end 

151 driver. Supported params: 

152 'count' - how many allocations should be created 

153 'device_owner' - set owner for network allocations 

154 :rtype: list of :class: 'dict' 

155 """ 

156 if not self._has_provider_network_extension(): 156 ↛ 157line 156 didn't jump to line 157 because the condition on line 156 was never true

157 msg = "%s extension required" % neutron_constants.PROVIDER_NW_EXT 

158 raise exception.NetworkBadConfigurationException(reason=msg) 

159 

160 self._verify_share_network(share_server['id'], share_network) 

161 self._verify_share_network_subnet(share_server['id'], 

162 share_network_subnet) 

163 is_external_network = self._store_and_get_neutron_net_info( 

164 context, share_network_subnet) 

165 

166 allocation_count = kwargs.get('count', 1) 

167 device_owner = kwargs.get('device_owner', 'share') 

168 

169 ports = [] 

170 for current_count in range(0, allocation_count): 

171 ports.append( 

172 self._create_port(context, 

173 share_server, 

174 share_network, 

175 share_network_subnet, 

176 device_owner, 

177 current_count, 

178 is_external_network=is_external_network), 

179 ) 

180 

181 return ports 

182 

183 def manage_network_allocations( 

184 self, context, allocations, share_server, share_network=None, 

185 share_network_subnet=None): 

186 

187 self._verify_share_network_subnet(share_server['id'], 

188 share_network_subnet) 

189 self._store_and_get_neutron_net_info(context, share_network_subnet) 

190 

191 # We begin matching the allocations to known neutron ports and 

192 # finally return the non-consumed allocations 

193 remaining_allocations = list(allocations) 

194 fixed_ip_filter = ('subnet_id=' + 

195 share_network_subnet['neutron_subnet_id']) 

196 

197 port_list = self.neutron_api.list_ports( 

198 network_id=share_network_subnet['neutron_net_id'], 

199 device_owner='manila:share', 

200 fixed_ips=fixed_ip_filter) 

201 

202 selected_ports = self._get_ports_respective_to_ips( 

203 remaining_allocations, port_list) 

204 

205 LOG.debug("Found matching allocations in Neutron:" 

206 " %s", selected_ports) 

207 

208 for selected_port in selected_ports: 

209 port_dict = { 

210 'id': selected_port['port']['id'], 

211 'share_server_id': share_server['id'], 

212 'ip_address': selected_port['allocation'], 

213 'gateway': share_network_subnet['gateway'], 

214 'mac_address': selected_port['port']['mac_address'], 

215 'status': constants.STATUS_ACTIVE, 

216 'label': self.label, 

217 'network_type': share_network_subnet.get('network_type'), 

218 'segmentation_id': share_network_subnet.get('segmentation_id'), 

219 'ip_version': share_network_subnet['ip_version'], 

220 'cidr': share_network_subnet['cidr'], 

221 'mtu': share_network_subnet['mtu'], 

222 } 

223 # NOTE(felipe_rodrigues): admin plugin does not have any Manila 

224 # share net subnet, its data is from manila configuration file. 

225 if self.label != 'admin': 225 ↛ 230line 225 didn't jump to line 230 because the condition on line 225 was always true

226 port_dict['share_network_subnet_id'] = ( 

227 share_network_subnet['id']) 

228 

229 # There should not be existing allocations with the same port_id. 

230 try: 

231 existing_port = self.db.network_allocation_get( 

232 context, selected_port['port']['id'], read_deleted=False) 

233 except exception.NotFound: 

234 pass 

235 else: 

236 msg = _("There were existing conflicting manila network " 

237 "allocations found while trying to manage share " 

238 "server %(new_ss)s. The conflicting port belongs to " 

239 "share server %(old_ss)s.") % { 

240 'new_ss': share_server['id'], 

241 'old_ss': existing_port['share_server_id'], 

242 } 

243 raise exception.ManageShareServerError(reason=msg) 

244 

245 # If there are previously deleted allocations, we undelete them 

246 try: 

247 self.db.network_allocation_get( 

248 context, selected_port['port']['id'], read_deleted=True) 

249 except exception.NotFound: 

250 self.db.network_allocation_create(context, port_dict) 

251 else: 

252 port_dict.pop('id') 

253 port_dict.update({ 

254 'deleted_at': None, 

255 'deleted': 'False', 

256 }) 

257 self.db.network_allocation_update( 

258 context, selected_port['port']['id'], port_dict, 

259 read_deleted=True) 

260 

261 remaining_allocations.remove(selected_port['allocation']) 

262 

263 return remaining_allocations 

264 

265 def unmanage_network_allocations(self, context, share_server_id): 

266 

267 ports = self.db.network_allocations_get_for_share_server( 

268 context, share_server_id) 

269 

270 for port in ports: 

271 self.db.network_allocation_delete(context, port['id']) 

272 

273 def _get_ports_respective_to_ips(self, allocations, port_list): 

274 

275 selected_ports = [] 

276 

277 for port in port_list: 

278 for ip in port['fixed_ips']: 

279 if ip['ip_address'] in allocations: 

280 if not any(port['id'] == p['port']['id'] 

281 for p in selected_ports): 

282 selected_ports.append( 

283 {'port': port, 'allocation': ip['ip_address']}) 

284 else: 

285 LOG.warning("Port %s has more than one IP that " 

286 "matches allocations, please use ports " 

287 "respective to only one allocation IP.", 

288 port['id']) 

289 

290 return selected_ports 

291 

292 def _get_matched_ip_address(self, fixed_ips, ip_version): 

293 """Get first ip address which matches the specified ip_version.""" 

294 

295 for ip in fixed_ips: 

296 try: 

297 address = ipaddress.ip_address(str(ip['ip_address'])) 

298 if address.version == ip_version: 

299 return ip['ip_address'] 

300 except ValueError: 

301 LOG.error("%(address)s isn't a valid ip " 

302 "address, omitted.", {'address': 

303 ip['ip_address']}) 

304 msg = _("Can not find any IP address with configured IP " 

305 "version %(version)s in share-network.") % {'version': 

306 ip_version} 

307 raise exception.NetworkBadConfigurationException(reason=msg) 

308 

309 def deallocate_network(self, context, share_server_id, 

310 share_network=None, share_network_subnet=None): 

311 """Deallocate neutron network resources for the given share server. 

312 

313 Delete previously allocated neutron ports, delete manila db 

314 records for deleted ports. 

315 

316 :param context: RequestContext object 

317 :param share_server_id: id of share server 

318 :param share_network: share network data 

319 :param share_network_subnet: share network subnet data 

320 :rtype: None 

321 """ 

322 ports = self.db.network_allocations_get_for_share_server( 

323 context, share_server_id) 

324 

325 for port in ports: 

326 self._delete_port(context, port) 

327 

328 # It may be possible that there are ports existing without a 

329 # corresponding manila network allocation entry in the manila db, 

330 # because port create request may have been successfully sent to 

331 # neutron, but the response, the created port could not be stored 

332 # in manila due to unreachable db 

333 if share_network_subnet: 333 ↛ 334line 333 didn't jump to line 334 because the condition on line 333 was never true

334 ports = [] 

335 try: 

336 ports = self.neutron_api.list_ports( 

337 network_id=share_network_subnet['neutron_net_id'], 

338 device_owner='manila:share', 

339 device_id=share_server_id) 

340 except exception.NetworkException: 

341 LOG.warning("Failed to list ports using neutron API during " 

342 "deallocate_network.") 

343 for port in ports: 

344 LOG.debug(f"Deleting orphaned port {port['id']} belonging to " 

345 f"share server {share_server_id} in neutron " 

346 f"network {share_network_subnet['neutron_net_id']}") 

347 self._delete_port(context, port, ignore_db=True) 

348 

349 def _get_port_create_args(self, share_server, share_network_subnet, 

350 device_owner, count=0, 

351 is_external_network=False): 

352 return { 

353 "network_id": share_network_subnet['neutron_net_id'], 

354 "subnet_id": share_network_subnet['neutron_subnet_id'], 

355 "device_owner": 'manila:' + device_owner, 

356 "device_id": share_server.get('id'), 

357 "name": share_server.get('id') + '_' + str(count), 

358 # NOTE (gouthamr): we create disabled ports with external networks 

359 # since the actual ports are not managed by neutron. The ports 

360 # neutron creates merely assist in IPAM. 

361 "admin_state_up": not is_external_network, 

362 } 

363 

364 def _create_port(self, context, share_server, share_network, 

365 share_network_subnet, device_owner, count=0, 

366 is_external_network=False): 

367 create_args = self._get_port_create_args( 

368 share_server, share_network_subnet, device_owner, count, 

369 is_external_network=is_external_network) 

370 

371 port = self.neutron_api.create_port( 

372 share_network['project_id'], **create_args) 

373 

374 if is_external_network: 

375 msg = ( 

376 f"Port '{port['id']}' is disabled to prevent improper " 

377 f"routing on an external network." 

378 ) 

379 LOG.info(msg) 

380 

381 ip_address = self._get_matched_ip_address( 

382 port['fixed_ips'], share_network_subnet['ip_version']) 

383 port_dict = { 

384 'id': port['id'], 

385 'share_server_id': share_server['id'], 

386 'ip_address': ip_address, 

387 'gateway': share_network_subnet['gateway'], 

388 'mac_address': port['mac_address'], 

389 'status': constants.STATUS_ACTIVE, 

390 'label': self.label, 

391 'network_type': share_network_subnet.get('network_type'), 

392 'segmentation_id': share_network_subnet.get('segmentation_id'), 

393 'ip_version': share_network_subnet['ip_version'], 

394 'cidr': share_network_subnet['cidr'], 

395 'mtu': share_network_subnet['mtu'], 

396 } 

397 # NOTE(felipe_rodrigues): admin plugin does not have any Manila 

398 # share net subnet, its data is from manila configuration file. 

399 if self.label != 'admin': 399 ↛ 403line 399 didn't jump to line 403 because the condition on line 399 was always true

400 port_dict['share_network_subnet_id'] = ( 

401 share_network_subnet['id']) 

402 

403 return self.db.network_allocation_create(context, port_dict) 

404 

405 def _delete_port(self, context, port, ignore_db=False): 

406 try: 

407 self.neutron_api.delete_port(port['id']) 

408 except exception.NetworkException: 

409 if not ignore_db: 409 ↛ 412line 409 didn't jump to line 412 because the condition on line 409 was always true

410 self.db.network_allocation_update( 

411 context, port['id'], {'status': constants.STATUS_ERROR}) 

412 raise 

413 else: 

414 if not ignore_db: 414 ↛ exitline 414 didn't return from function '_delete_port' because the condition on line 414 was always true

415 self.db.network_allocation_delete(context, port['id']) 

416 

417 def _has_provider_network_extension(self): 

418 extensions = self.neutron_api.list_extensions() 

419 return neutron_constants.PROVIDER_NW_EXT in extensions 

420 

421 def _is_neutron_multi_segment(self, share_network_subnet, net_info=None): 

422 if net_info is None: 

423 net_info = self.neutron_api.get_network( 

424 share_network_subnet['neutron_net_id']) 

425 return 'segments' in net_info 

426 

427 def _save_neutron_network_data(self, context, share_network_subnet, 

428 save_db=True): 

429 net_info = self.neutron_api.get_network( 

430 share_network_subnet['neutron_net_id']) 

431 segmentation_id = None 

432 network_type = None 

433 is_external_network = net_info.get('router:external', False) 

434 

435 if self._is_neutron_multi_segment(share_network_subnet, net_info): 

436 # we have a multi segment network and need to identify the 

437 # lowest segment used for binding 

438 phy_nets = [] 

439 phy = self.neutron_api.configuration.neutron_physical_net_name 

440 if not phy: 

441 msg = "Cannot identify segment used for binding. Please add " 

442 "neutron_physical_net_name in configuration." 

443 raise exception.NetworkBadConfigurationException(reason=msg) 

444 for segment in net_info['segments']: 

445 phy_nets.append(segment['provider:physical_network']) 

446 if segment['provider:physical_network'] == phy: 

447 segmentation_id = segment['provider:segmentation_id'] 

448 network_type = segment['provider:network_type'] 

449 if not (segmentation_id and network_type): 

450 msg = ("No matching neutron_physical_net_name found for %s " 

451 "(found: %s)." % (phy, phy_nets)) 

452 raise exception.NetworkBadConfigurationException(reason=msg) 

453 else: 

454 network_type = net_info.get('provider:network_type') 

455 segmentation_id = net_info.get('provider:segmentation_id') 

456 

457 provider_nw_dict = { 

458 'network_type': network_type, 

459 'segmentation_id': segmentation_id, 

460 'mtu': net_info.get('mtu'), 

461 } 

462 share_network_subnet.update(provider_nw_dict) 

463 

464 if self.label != 'admin' and save_db: 464 ↛ 468line 464 didn't jump to line 468 because the condition on line 464 was always true

465 self.db.share_network_subnet_update( 

466 context, share_network_subnet['id'], provider_nw_dict) 

467 

468 return is_external_network 

469 

470 def _save_neutron_subnet_data(self, context, share_network_subnet, 

471 save_db=True): 

472 subnet_info = self.neutron_api.get_subnet( 

473 share_network_subnet['neutron_subnet_id']) 

474 

475 subnet_values = { 

476 'cidr': subnet_info['cidr'], 

477 'gateway': subnet_info['gateway_ip'], 

478 'ip_version': subnet_info['ip_version'] 

479 } 

480 share_network_subnet.update(subnet_values) 

481 

482 if self.label != 'admin' and save_db: 482 ↛ exitline 482 didn't return from function '_save_neutron_subnet_data' because the condition on line 482 was always true

483 self.db.share_network_subnet_update( 

484 context, share_network_subnet['id'], subnet_values) 

485 

486 

487class NeutronSingleNetworkPlugin(NeutronNetworkPlugin): 

488 

489 def __init__(self, *args, **kwargs): 

490 super(NeutronSingleNetworkPlugin, self).__init__(*args, **kwargs) 

491 CONF.register_opts( 

492 neutron_single_network_plugin_opts, 

493 group=self.neutron_api.config_group_name) 

494 self.net = self.neutron_api.configuration.neutron_net_id 

495 self.subnet = self.neutron_api.configuration.neutron_subnet_id 

496 self._verify_net_and_subnet() 

497 

498 def _select_proper_share_network_subnet(self, context, 

499 share_network_subnet): 

500 if self.label != 'admin': 

501 share_network_subnet = self._update_share_network_net_data( 

502 context, share_network_subnet) 

503 else: 

504 share_network_subnet = { 

505 'project_id': self.neutron_api.admin_project_id, 

506 'neutron_net_id': self.net, 

507 'neutron_subnet_id': self.subnet, 

508 } 

509 return share_network_subnet 

510 

511 def allocate_network(self, context, share_server, share_network=None, 

512 share_network_subnet=None, **kwargs): 

513 

514 share_network_subnet = self._select_proper_share_network_subnet( 

515 context, share_network_subnet) 

516 # Update share network project_id info if needed 

517 if share_network_subnet.get('project_id', None) is not None: 517 ↛ 518line 517 didn't jump to line 518 because the condition on line 517 was never true

518 share_network['project_id'] = share_network_subnet.pop( 

519 'project_id') 

520 

521 return super(NeutronSingleNetworkPlugin, self).allocate_network( 

522 context, share_server, share_network, share_network_subnet, 

523 **kwargs) 

524 

525 def manage_network_allocations( 

526 self, context, allocations, share_server, share_network=None, 

527 share_network_subnet=None): 

528 share_network_subnet = self._select_proper_share_network_subnet( 

529 context, share_network_subnet) 

530 # Update share network project_id info if needed 

531 if share_network and share_network_subnet.get('project_id', None): 531 ↛ 532line 531 didn't jump to line 532 because the condition on line 531 was never true

532 share_network['project_id'] = ( 

533 share_network_subnet.pop('project_id')) 

534 

535 return super(NeutronSingleNetworkPlugin, 

536 self).manage_network_allocations( 

537 context, allocations, share_server, share_network, 

538 share_network_subnet) 

539 

540 def _verify_net_and_subnet(self): 

541 data = dict(net=self.net, subnet=self.subnet) 

542 if self.net and self.subnet: 

543 net = self.neutron_api.get_network(self.net) 

544 if not (net.get('subnets') and data['subnet'] in net['subnets']): 

545 raise exception.NetworkBadConfigurationException( 

546 "Subnet '%(subnet)s' does not belong to " 

547 "network '%(net)s'." % data) 

548 else: 

549 raise exception.NetworkBadConfigurationException( 

550 "Neutron net and subnet are expected to be both set. " 

551 "Got: net=%(net)s and subnet=%(subnet)s." % data) 

552 

553 def _update_share_network_net_data(self, context, share_network_subnet): 

554 upd = dict() 

555 

556 if not share_network_subnet.get('neutron_net_id') == self.net: 

557 if share_network_subnet.get('neutron_net_id') is not None: 

558 raise exception.NetworkBadConfigurationException( 

559 "Using neutron net id different from None or value " 

560 "specified in the config is forbidden for " 

561 "NeutronSingleNetworkPlugin. Allowed values: (%(net)s, " 

562 "None), received value: %(err)s" % { 

563 "net": self.net, 

564 "err": share_network_subnet.get('neutron_net_id')}) 

565 upd['neutron_net_id'] = self.net 

566 if not share_network_subnet.get('neutron_subnet_id') == self.subnet: 

567 if share_network_subnet.get('neutron_subnet_id') is not None: 

568 raise exception.NetworkBadConfigurationException( 

569 "Using neutron subnet id different from None or value " 

570 "specified in the config is forbidden for " 

571 "NeutronSingleNetworkPlugin. Allowed values: (%(snet)s, " 

572 "None), received value: %(err)s" % { 

573 "snet": self.subnet, 

574 "err": share_network_subnet.get('neutron_subnet_id')}) 

575 upd['neutron_subnet_id'] = self.subnet 

576 if upd: 

577 share_network_subnet = self.db.share_network_subnet_update( 

578 context, share_network_subnet['id'], upd) 

579 return share_network_subnet 

580 

581 

582class NeutronBindNetworkPlugin(NeutronNetworkPlugin): 

583 def __init__(self, *args, **kwargs): 

584 super(NeutronBindNetworkPlugin, self).__init__(*args, **kwargs) 

585 

586 self.binding_profiles = [] 

587 CONF.register_opts( 

588 neutron_binding_profile, 

589 group=self.neutron_api.config_group_name) 

590 conf = CONF[self.neutron_api.config_group_name] 

591 if conf.neutron_binding_profiles: 

592 for profile in conf.neutron_binding_profiles: 

593 CONF.register_opts(neutron_binding_profile_opts, group=profile) 

594 self.binding_profiles.append(profile) 

595 

596 CONF.register_opts( 

597 neutron_bind_network_plugin_opts, 

598 group=self.neutron_api.config_group_name) 

599 self.config = self.neutron_api.configuration 

600 

601 def update_network_allocation(self, context, share_server): 

602 if self.config.neutron_vnic_type == 'normal': 602 ↛ exitline 602 didn't return from function 'update_network_allocation' because the condition on line 602 was always true

603 ports = self.db.network_allocations_get_for_share_server( 

604 context, 

605 share_server['id']) 

606 self._wait_for_ports_bind(ports, share_server) 

607 return ports 

608 

609 @utils.retry(retry_param=exception.NetworkBindException, retries=20) 

610 def _wait_for_ports_bind(self, ports, share_server): 

611 inactive_ports = [] 

612 for port in ports: 

613 port = self._neutron_api.show_port(port['id']) 

614 if (port['status'] == neutron_constants.PORT_STATUS_ERROR or 

615 ('binding:vif_type' in port and 

616 port['binding:vif_type'] == 

617 neutron_constants.VIF_TYPE_BINDING_FAILED)): 

618 msg = _("Port binding %s failed.") % port['id'] 

619 raise exception.NetworkException(msg) 

620 elif port['status'] != neutron_constants.PORT_STATUS_ACTIVE: 

621 LOG.debug("The port %(id)s is in state %(state)s. " 

622 "Wait for active state.", { 

623 "id": port['id'], 

624 "state": port['status']}) 

625 inactive_ports.append(port['id']) 

626 if len(inactive_ports) == 0: 

627 return 

628 msg = _("Ports are not fully bound for share server " 

629 "'%(s_id)s' (inactive ports: %(ports)s)") % { 

630 "s_id": share_server['id'], 

631 "ports": inactive_ports} 

632 raise exception.NetworkBindException(msg) 

633 

634 def _get_port_create_args(self, share_server, share_network_subnet, 

635 device_owner, count=0, 

636 is_external_network=False): 

637 arguments = super( 

638 NeutronBindNetworkPlugin, self)._get_port_create_args( 

639 share_server, share_network_subnet, device_owner, count, 

640 is_external_network=is_external_network 

641 ) 

642 arguments['host_id'] = self.config.neutron_host_id 

643 arguments['binding:vnic_type'] = self.config.neutron_vnic_type 

644 if self.binding_profiles: 

645 local_links = [] 

646 for profile in self.binding_profiles: 

647 local_links.append({ 

648 'switch_id': CONF[profile]['neutron_switch_id'], 

649 'port_id': CONF[profile]['neutron_port_id'], 

650 'switch_info': CONF[profile]['neutron_switch_info'], 

651 }) 

652 

653 arguments['binding:profile'] = { 

654 "local_link_information": local_links} 

655 return arguments 

656 

657 def _save_neutron_network_data(self, context, share_network_subnet, 

658 save_db=True): 

659 """Store the Neutron network info. 

660 

661 In case of dynamic multi segments the segment is determined while 

662 binding the port. Therefore this method will return for multi segments 

663 network without storing network information (apart from mtu). 

664 

665 Instead, multi segments network will wait until ports are bound and 

666 then store network information (see allocate_network()). 

667 """ 

668 if self._is_neutron_multi_segment(share_network_subnet): 668 ↛ 674line 668 didn't jump to line 674 because the condition on line 668 was always true

669 # In case of dynamic multi segment the segment is determined while 

670 # binding the port, only mtu is known and already needed 

671 self._save_neutron_network_mtu(context, share_network_subnet, 

672 save_db=save_db) 

673 return 

674 super(NeutronBindNetworkPlugin, self)._save_neutron_network_data( 

675 context, share_network_subnet, save_db=save_db) 

676 

677 def _save_neutron_network_mtu(self, context, share_network_subnet, 

678 save_db=True): 

679 """Store the Neutron network mtu. 

680 

681 In case of dynamic multi segments only the mtu needs storing before 

682 binding the port. 

683 """ 

684 net_info = self.neutron_api.get_network( 

685 share_network_subnet['neutron_net_id']) 

686 

687 mtu_dict = { 

688 'mtu': net_info['mtu'], 

689 } 

690 share_network_subnet.update(mtu_dict) 

691 

692 if self.label != 'admin' and save_db: 692 ↛ exitline 692 didn't return from function '_save_neutron_network_mtu' because the condition on line 692 was always true

693 self.db.share_network_subnet_update( 

694 context, share_network_subnet['id'], mtu_dict) 

695 

696 def allocate_network(self, context, share_server, share_network=None, 

697 share_network_subnet=None, **kwargs): 

698 ports = super(NeutronBindNetworkPlugin, self).allocate_network( 

699 context, share_server, share_network, share_network_subnet, 

700 **kwargs) 

701 # If vnic type is 'normal' we expect a neutron agent to bind the 

702 # ports. This action requires a vnic to be spawned by the driver. 

703 # Therefore we do not wait for the port binding here, but 

704 # return the unbound ports and expect the share manager to call 

705 # update_network_allocation after the share server was created, in 

706 # order to update the ports with the correct binding. 

707 if self.config.neutron_vnic_type != 'normal': 

708 self._wait_for_ports_bind(ports, share_server) 

709 if self._is_neutron_multi_segment(share_network_subnet): 

710 # update segment information after port bind 

711 super(NeutronBindNetworkPlugin, 

712 self)._save_neutron_network_data(context, 

713 share_network_subnet) 

714 for num, port in enumerate(ports): 

715 port_info = { 

716 'network_type': share_network_subnet['network_type'], 

717 'segmentation_id': 

718 share_network_subnet['segmentation_id'], 

719 'cidr': share_network_subnet['cidr'], 

720 'ip_version': share_network_subnet['ip_version'], 

721 } 

722 ports[num] = self.db.network_allocation_update( 

723 context, port['id'], port_info) 

724 return ports 

725 

726 @utils.retry(retry_param=exception.NetworkException, retries=20) 

727 def _wait_for_network_segment(self, share_server, host): 

728 network_id = share_server['share_network_subnet']['neutron_net_id'] 

729 network = self.neutron_api.get_network(network_id) 

730 for segment in network['segments']: 730 ↛ 734line 730 didn't jump to line 734 because the loop on line 730 didn't complete

731 if segment['provider:physical_network'] == ( 

732 self.config.neutron_physical_net_name): 

733 return segment['provider:segmentation_id'] 

734 msg = _('Network segment not found on host %s') % host 

735 raise exception.NetworkException(msg) 

736 

737 def extend_network_allocations(self, context, share_server): 

738 """Extend network to target host. 

739 

740 This will create port bindings on target host without activating them. 

741 If network segment does not exist on target host, it will be created. 

742 

743 :return: list of port bindings with new segmentation id on target host 

744 """ 

745 vnic_type = self.config.neutron_vnic_type 

746 host_id = self.config.neutron_host_id 

747 

748 active_port_bindings = ( 

749 self.db.network_allocations_get_for_share_server( 

750 context, share_server['id'], label='user')) 

751 if len(active_port_bindings) == 0: 751 ↛ 752line 751 didn't jump to line 752 because the condition on line 751 was never true

752 raise exception.NetworkException( 

753 'Can not extend network with no active bindings') 

754 

755 # Create port binding on destination backend. It's safe to call neutron 

756 # api bind_port_to_host if the port is already bound to destination 

757 # host. 

758 for port in active_port_bindings: 

759 self.neutron_api.bind_port_to_host(port['id'], host_id, 

760 vnic_type) 

761 

762 # Wait for network segment to be created on destination host. 

763 vlan = self._wait_for_network_segment(share_server, host_id) 

764 for port in active_port_bindings: 

765 port['segmentation_id'] = vlan 

766 

767 return active_port_bindings 

768 

769 def delete_extended_allocations(self, context, share_server): 

770 host_id = self.config.neutron_host_id 

771 ports = self.db.network_allocations_get_for_share_server( 

772 context, share_server['id'], label='user') 

773 for port in ports: 

774 try: 

775 self.neutron_api.delete_port_binding(port['id'], host_id) 

776 except exception.NetworkException as e: 

777 msg = 'Failed to delete port binding on port %{port}s: %{err}s' 

778 LOG.warning(msg, {'port': port['id'], 'err': e}) 

779 

780 def cutover_network_allocations(self, context, src_share_server): 

781 src_host = share_utils.extract_host(src_share_server['host'], 'host') 

782 dest_host = self.config.neutron_host_id 

783 ports = self.db.network_allocations_get_for_share_server( 

784 context, src_share_server['id'], label='user') 

785 for port in ports: 

786 self.neutron_api.activate_port_binding(port['id'], dest_host) 

787 self.neutron_api.delete_port_binding(port['id'], src_host) 

788 return ports 

789 

790 

791class NeutronBindSingleNetworkPlugin(NeutronSingleNetworkPlugin, 

792 NeutronBindNetworkPlugin): 

793 pass