Coverage for manila/share/drivers/service_instance.py: 96%

587 statements  

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

1# Copyright (c) 2014 NetApp, Inc. 

2# Copyright (c) 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 

17"""Module for managing nova instances for share drivers.""" 

18 

19import abc 

20import os 

21import time 

22 

23import netaddr 

24from oslo_config import cfg 

25from oslo_log import log 

26from oslo_utils import importutils 

27from oslo_utils import netutils 

28 

29from manila.common import constants as const 

30from manila import compute 

31from manila import context 

32from manila import coordination 

33from manila import exception 

34from manila.i18n import _ 

35from manila import image 

36from manila.network.linux import ip_lib 

37from manila.network.neutron import api as neutron 

38from manila import ssh_utils 

39from manila import utils 

40from manila import volume 

41 

42LOG = log.getLogger(__name__) 

43NEUTRON_NAME = "neutron" 

44 

45share_servers_handling_mode_opts = [ 

46 cfg.StrOpt( 

47 "service_image_name", 

48 default="manila-service-image", 

49 help="Name of image in Glance, that will be used for service instance " 

50 "creation. Only used if driver_handles_share_servers=True."), 

51 cfg.StrOpt( 

52 "service_instance_name_template", 

53 default="%s", 

54 help="Name of service instance. " 

55 "Only used if driver_handles_share_servers=True."), 

56 cfg.StrOpt( 

57 "manila_service_keypair_name", 

58 default="manila-service", 

59 help="Keypair name that will be created and used for service " 

60 "instances. Only used if driver_handles_share_servers=True."), 

61 cfg.StrOpt( 

62 "path_to_public_key", 

63 default="~/.ssh/id_rsa.pub", 

64 help="Path to hosts public key. " 

65 "Only used if driver_handles_share_servers=True."), 

66 cfg.StrOpt( 

67 "service_instance_security_group", 

68 default="manila-service", 

69 help="Security group name, that will be used for " 

70 "service instance creation. " 

71 "Only used if driver_handles_share_servers=True."), 

72 cfg.StrOpt( 

73 "service_instance_flavor_id", 

74 default="100", 

75 help="ID of flavor, that will be used for service instance " 

76 "creation. Only used if driver_handles_share_servers=True."), 

77 cfg.StrOpt( 

78 "service_network_name", 

79 default="manila_service_network", 

80 help="Name of manila service network. Used only with Neutron. " 

81 "Only used if driver_handles_share_servers=True."), 

82 cfg.HostAddressOpt( 

83 "service_network_host", 

84 sample_default="<your_network_hostname>", 

85 help="Hostname to be used for service network binding. Used only with " 

86 "Neutron and if driver_handles_share_servers=True."), 

87 cfg.StrOpt( 

88 "service_network_cidr", 

89 default="10.254.0.0/16", 

90 help="CIDR of manila service network. Used only with Neutron and " 

91 "if driver_handles_share_servers=True."), 

92 cfg.IntOpt( 

93 "service_network_division_mask", 

94 default=28, 

95 help="This mask is used for dividing service network into " 

96 "subnets, IP capacity of subnet with this mask directly " 

97 "defines possible amount of created service VMs " 

98 "per tenant's subnet. Used only with Neutron " 

99 "and if driver_handles_share_servers=True."), 

100 cfg.StrOpt( 

101 "interface_driver", 

102 default="manila.network.linux.interface.OVSInterfaceDriver", 

103 help="Module path to the Virtual Interface (VIF) driver class. This " 

104 "option is used only by drivers operating in " 

105 "`driver_handles_share_servers=True` mode that provision " 

106 "OpenStack compute instances as share servers. This option is " 

107 "only supported with Neutron networking. " 

108 "Drivers provided in tree work with Linux Bridge " 

109 "(manila.network.linux.interface.BridgeInterfaceDriver) and OVS " 

110 "(manila.network.linux.interface.OVSInterfaceDriver). If the " 

111 "manila-share service is running on a host that is connected to " 

112 "the administrator network, a no-op driver " 

113 "(manila.network.linux.interface.NoopInterfaceDriver) may " 

114 "be used."), 

115 cfg.BoolOpt( 

116 "connect_share_server_to_tenant_network", 

117 default=False, 

118 help="Attach share server directly to share network. " 

119 "Used only with Neutron and " 

120 "if driver_handles_share_servers=True."), 

121 cfg.StrOpt( 

122 "admin_network_id", 

123 help="ID of neutron network used to communicate with admin network," 

124 " to create additional admin export locations on."), 

125 cfg.StrOpt( 

126 "admin_subnet_id", 

127 help="ID of neutron subnet used to communicate with admin network," 

128 " to create additional admin export locations on. " 

129 "Related to 'admin_network_id'."), 

130 cfg.BoolOpt( 

131 'service_instance_boot_from_volume', 

132 default=False, 

133 help='Boot service instances (share servers) from a Cinder volume. ' 

134 'If False, boot from the image as before. ' 

135 'Only used if driver_handles_share_servers=True.' 

136 ), 

137 cfg.IntOpt( 

138 'service_instance_boot_volume_size', 

139 default=10, 

140 min=1, 

141 help='Size (GiB) of the root volume when booting from volume. ' 

142 'Only used if driver_handles_share_servers=True.' 

143 ), 

144 cfg.StrOpt( 

145 'service_instance_boot_volume_type', 

146 help='Name or id of cinder volume type which will be used ' 

147 'for all boot volumes created by driver.'), 

148 cfg.StrOpt( 

149 'service_instance_base_boot_volume_id', 

150 help="UUID of volume in Cinder, that will be used as base volume " 

151 "that bootable volume clone from during service instance " 

152 "creation. Only used if driver_handles_share_servers=True."), 

153 cfg.BoolOpt( 

154 'service_instance_boot_volume_delete_on_termination', 

155 default=True, 

156 help='Whether the root volume is deleted when the service instance ' 

157 'is terminated. Only used if driver_handles_share_servers=True.' 

158 ), 

159 cfg.StrOpt('service_instance_boot_volume_name_template', 

160 default='manila-share-%s-boot', 

161 help="Boot volume name template."), 

162] 

163 

164no_share_servers_handling_mode_opts = [ 

165 cfg.StrOpt( 

166 "service_instance_name_or_id", 

167 help="Name or ID of service instance in Nova to use for share " 

168 "exports. Used only when share servers handling is disabled."), 

169 cfg.HostAddressOpt( 

170 "service_net_name_or_ip", 

171 help="Can be either name of network that is used by service " 

172 "instance within Nova to get IP address or IP address itself " 

173 "(either IPv4 or IPv6) for managing shares there. " 

174 "Used only when share servers handling is disabled."), 

175 cfg.HostAddressOpt( 

176 "tenant_net_name_or_ip", 

177 help="Can be either name of network that is used by service " 

178 "instance within Nova to get IP address or IP address itself " 

179 "(either IPv4 or IPv6) for exporting shares. " 

180 "Used only when share servers handling is disabled."), 

181] 

182 

183common_opts = [ 

184 cfg.StrOpt( 

185 "service_instance_user", 

186 help="User in service instance that will be used for authentication."), 

187 cfg.StrOpt( 

188 "service_instance_password", 

189 secret=True, 

190 help="Password for service instance user."), 

191 cfg.StrOpt( 

192 "path_to_private_key", 

193 help="Path to host's private key."), 

194 cfg.IntOpt( 

195 "max_time_to_build_instance", 

196 default=300, 

197 help="Maximum time in seconds to wait for creating service instance."), 

198 cfg.BoolOpt( 

199 "limit_ssh_access", 

200 default=False, 

201 help="Block SSH connection to the service instance from other " 

202 "networks than service network."), 

203] 

204 

205CONF = cfg.CONF 

206 

207 

208class ServiceInstanceManager(object): 

209 """Manages nova instances for various share drivers. 

210 

211 This class provides following external methods: 

212 

213 1. set_up_service_instance: creates instance and sets up share 

214 infrastructure. 

215 2. ensure_service_instance: ensure service instance is available. 

216 3. delete_service_instance: removes service instance and network 

217 infrastructure. 

218 """ 

219 _INSTANCE_CONNECTION_PROTO = "SSH" 

220 

221 def get_config_option(self, key): 

222 """Returns value of config option. 

223 

224 :param key: key of config' option. 

225 :returns: str -- value of config's option. 

226 first priority is driver's config, 

227 second priority is global config. 

228 """ 

229 if self.driver_config: 

230 return self.driver_config.safe_get(key) 

231 return CONF.get(key) 

232 

233 def _get_network_helper(self): 

234 # Historically, there were multiple types of network helper, 

235 # but currently the only network helper type is Neutron. 

236 return NeutronNetworkHelper(self) 

237 

238 def __init__(self, driver_config=None): 

239 

240 super(ServiceInstanceManager, self).__init__() 

241 self.driver_config = driver_config 

242 

243 if self.driver_config: 

244 self.driver_config.append_config_values(common_opts) 

245 if self.get_config_option("driver_handles_share_servers"): 

246 self.driver_config.append_config_values( 

247 share_servers_handling_mode_opts) 

248 else: 

249 self.driver_config.append_config_values( 

250 no_share_servers_handling_mode_opts) 

251 else: 

252 CONF.register_opts(common_opts) 

253 if self.get_config_option("driver_handles_share_servers"): 

254 CONF.register_opts(share_servers_handling_mode_opts) 

255 else: 

256 CONF.register_opts(no_share_servers_handling_mode_opts) 

257 

258 if not self.get_config_option("service_instance_user"): 

259 raise exception.ServiceInstanceException( 

260 _('Service instance user is not specified.')) 

261 self.admin_context = context.get_admin_context() 

262 

263 self.image_api = image.API() 

264 self.compute_api = compute.API() 

265 self.volume_api = volume.API() 

266 

267 self.path_to_private_key = self.get_config_option( 

268 "path_to_private_key") 

269 self.max_time_to_build_instance = self.get_config_option( 

270 "max_time_to_build_instance") 

271 

272 self.availability_zone = self.get_config_option( 

273 'backend_availability_zone') or CONF.storage_availability_zone 

274 

275 if self.get_config_option("driver_handles_share_servers"): 

276 self.path_to_public_key = self.get_config_option( 

277 "path_to_public_key") 

278 self._network_helper = None 

279 

280 @property 

281 @utils.synchronized("instantiate_network_helper") 

282 def network_helper(self): 

283 if not self._network_helper: 

284 self._network_helper = self._get_network_helper() 

285 self._network_helper.setup_connectivity_with_service_instances() 

286 return self._network_helper 

287 

288 def get_common_server(self): 

289 data = { 

290 'public_address': None, 

291 'private_address': None, 

292 'service_net_name_or_ip': self.get_config_option( 

293 'service_net_name_or_ip'), 

294 'tenant_net_name_or_ip': self.get_config_option( 

295 'tenant_net_name_or_ip'), 

296 } 

297 

298 data['instance'] = self.compute_api.server_get_by_name_or_id( 

299 self.admin_context, 

300 self.get_config_option('service_instance_name_or_id')) 

301 

302 if netutils.is_valid_ip(data['service_net_name_or_ip']): 

303 data['private_address'] = [data['service_net_name_or_ip']] 

304 else: 

305 data['private_address'] = self._get_addresses_by_network_name( 

306 data['service_net_name_or_ip'], data['instance']) 

307 

308 if netutils.is_valid_ip(data['tenant_net_name_or_ip']): 

309 data['public_address'] = [data['tenant_net_name_or_ip']] 

310 else: 

311 data['public_address'] = self._get_addresses_by_network_name( 

312 data['tenant_net_name_or_ip'], data['instance']) 

313 

314 if not (data['public_address'] and data['private_address']): 

315 raise exception.ManilaException( 

316 "Can not find one of net addresses for service instance. " 

317 "Instance: %(instance)s, " 

318 "private_address: %(private_address)s, " 

319 "public_address: %(public_address)s." % data) 

320 

321 share_server = { 

322 'username': self.get_config_option('service_instance_user'), 

323 'password': self.get_config_option('service_instance_password'), 

324 'pk_path': self.path_to_private_key, 

325 'instance_id': data['instance']['id'], 

326 } 

327 for key in ('private_address', 'public_address'): 

328 data[key + '_first'] = None 

329 for address in data[key]: 329 ↛ 327line 329 didn't jump to line 327 because the loop on line 329 didn't complete

330 if netutils.is_valid_ip(address): 

331 data[key + '_first'] = address 

332 break 

333 share_server['ip'] = data['private_address_first'] 

334 share_server['public_address'] = data['public_address_first'] 

335 return {'backend_details': share_server} 

336 

337 def _get_addresses_by_network_name(self, net_name, server): 

338 net_ips = [] 

339 if 'networks' in server and net_name in server['networks']: 

340 net_ips = server['networks'][net_name] 

341 elif 'addresses' in server and net_name in server['addresses']: 

342 net_ips = [addr['addr'] for addr in server['addresses'][net_name]] 

343 return net_ips 

344 

345 def _get_service_instance_name(self, share_server_id): 

346 """Returns service vms name.""" 

347 if self.driver_config and self.driver_config.config_group: 

348 # Make service instance name unique for multibackend installation 

349 name = "%s_%s" % (self.driver_config.config_group, share_server_id) 

350 else: 

351 name = share_server_id 

352 return self.get_config_option("service_instance_name_template") % name 

353 

354 def _get_server_ip(self, server, net_name): 

355 """Returns service IP address of service instance.""" 

356 net_ips = self._get_addresses_by_network_name(net_name, server) 

357 if not net_ips: 

358 msg = _("Failed to get service instance IP address. " 

359 "Service network name is '%(net_name)s' " 

360 "and provided data are '%(data)s'.") 

361 msg = msg % {'net_name': net_name, 'data': str(server)} 

362 raise exception.ServiceInstanceException(msg) 

363 return net_ips[0] 

364 

365 def _get_or_create_security_groups(self, context, name=None, 

366 description=None, 

367 allow_ssh_subnet=False): 

368 """Get or create security group for service_instance. 

369 

370 :param context: context, that should be used 

371 :param name: this is used for selection/creation of sec.group 

372 :param description: this is used on sec.group creation step only 

373 :param allow_ssh_subnet: subnet details to allow ssh connection from, 

374 if not supplied ssh will be allowed from any host 

375 :returns: SecurityGroup -- security group instance from Nova 

376 :raises: exception.ServiceInstanceException. 

377 """ 

378 

379 sgs = [] 

380 # Common security group 

381 name = name or self.get_config_option( 

382 "service_instance_security_group") 

383 if not name: 

384 LOG.warning("Name for service instance security group is not " 

385 "provided. Skipping security group step.") 

386 return None 

387 if not description: 

388 description = ("This security group is intended " 

389 "to be used by share service.") 

390 sec_group_data = const.SERVICE_INSTANCE_SECGROUP_DATA 

391 if not allow_ssh_subnet: 

392 sec_group_data += const.SSH_PORTS 

393 

394 sgs.append(self._get_or_create_security_group(name, description, 

395 sec_group_data)) 

396 if allow_ssh_subnet: 

397 if "cidr" not in allow_ssh_subnet or 'id' not in allow_ssh_subnet: 

398 raise exception.ManilaException( 

399 "Unable to limit SSH access") 

400 ssh_sg_name = "manila-service-subnet-{}".format( 

401 allow_ssh_subnet["id"]) 

402 sgs.append(self._get_or_create_security_group( 

403 ssh_sg_name, description, 

404 const.SSH_PORTS, allow_ssh_subnet["cidr"])) 

405 return sgs 

406 

407 @utils.synchronized( 

408 "service_instance_get_or_create_security_group", external=True) 

409 def _get_or_create_security_group(self, name, 

410 description, sec_group_data, 

411 cidr="0.0.0.0/0"): 

412 s_groups = self.network_helper.neutron_api.security_group_list({ 

413 "name": name, 

414 })['security_groups'] 

415 s_groups = [s for s in s_groups if s['name'] == name] 

416 if not s_groups: 

417 LOG.debug("Creating security group with name '%s'.", name) 

418 sg = self.network_helper.neutron_api.security_group_create( 

419 name, description)['security_group'] 

420 for protocol, ports in sec_group_data: 

421 self.network_helper.neutron_api.security_group_rule_create( 

422 parent_group_id=sg['id'], 

423 ip_protocol=protocol, 

424 from_port=ports[0], 

425 to_port=ports[1], 

426 cidr=cidr, 

427 ) 

428 elif len(s_groups) > 1: 

429 msg = _("Ambiguous security_groups.") 

430 raise exception.ServiceInstanceException(msg) 

431 else: 

432 sg = s_groups[0] 

433 return sg 

434 

435 def ensure_service_instance(self, context, server): 

436 """Ensures that server exists and active.""" 

437 if 'instance_id' not in server: 

438 LOG.warning("Unable to check server existence since " 

439 "'instance_id' key is not set in share server " 

440 "backend details.") 

441 return False 

442 try: 

443 inst = self.compute_api.server_get(self.admin_context, 

444 server['instance_id']) 

445 except exception.InstanceNotFound: 

446 LOG.warning("Service instance %s does not exist.", 

447 server['instance_id']) 

448 return False 

449 if inst['status'] == 'ACTIVE': 

450 return self._check_server_availability(server) 

451 return False 

452 

453 def _delete_server(self, context, server_id): 

454 """Deletes the server.""" 

455 try: 

456 self.compute_api.server_get(context, server_id) 

457 except exception.InstanceNotFound: 

458 LOG.debug("Service instance '%s' was not found. " 

459 "Nothing to delete, skipping.", server_id) 

460 return 

461 

462 self.compute_api.server_delete(context, server_id) 

463 

464 t = time.time() 

465 while time.time() - t < self.max_time_to_build_instance: 

466 try: 

467 inst = self.compute_api.server_get(context, server_id) 

468 if inst.get("status").lower() == "soft_deleted": 

469 LOG.debug("Service instance '%s' was soft-deleted " 

470 "successfully.", server_id) 

471 break 

472 except exception.InstanceNotFound: 

473 LOG.debug("Service instance '%s' was deleted " 

474 "successfully.", server_id) 

475 break 

476 time.sleep(2) 

477 else: 

478 raise exception.ServiceInstanceException( 

479 _("Instance '%(id)s' has not been deleted in %(s)ss. " 

480 "Giving up.") % { 

481 'id': server_id, 's': self.max_time_to_build_instance}) 

482 

483 def set_up_service_instance(self, context, network_info): 

484 """Finds or creates and sets up service vm. 

485 

486 :param context: defines context, that should be used 

487 :param network_info: network info for getting allocations 

488 :returns: dict with service instance details 

489 :raises: exception.ServiceInstanceException 

490 """ 

491 instance_name = self._get_service_instance_name( 

492 network_info['server_id']) 

493 server = self._create_service_instance( 

494 context, instance_name, network_info) 

495 instance_details = self._get_new_instance_details(server) 

496 

497 if not self._check_server_availability(instance_details): 

498 e = exception.ServiceInstanceException( 

499 _('%(conn_proto)s connection has not been ' 

500 'established to %(server)s in %(time)ss. Giving up.') % { 

501 'conn_proto': self._INSTANCE_CONNECTION_PROTO, 

502 'server': server['ip'], 

503 'time': self.max_time_to_build_instance}) 

504 e.detail_data = {'server_details': instance_details} 

505 raise e 

506 

507 return instance_details 

508 

509 def _get_new_instance_details(self, server): 

510 instance_details = { 

511 'instance_id': server['id'], 

512 'ip': server['ip'], 

513 'pk_path': server.get('pk_path'), 

514 'subnet_id': server.get('subnet_id'), 

515 'password': self.get_config_option('service_instance_password'), 

516 'username': self.get_config_option('service_instance_user'), 

517 'public_address': server['public_address'], 

518 } 

519 if server.get('admin_ip'): 519 ↛ 521line 519 didn't jump to line 521 because the condition on line 519 was always true

520 instance_details['admin_ip'] = server['admin_ip'] 

521 if server.get('router_id'): 521 ↛ 523line 521 didn't jump to line 523 because the condition on line 521 was always true

522 instance_details['router_id'] = server['router_id'] 

523 if server.get('service_port_id'): 

524 instance_details['service_port_id'] = server['service_port_id'] 

525 if server.get('public_port_id'): 

526 instance_details['public_port_id'] = server['public_port_id'] 

527 if server.get('admin_port_id'): 527 ↛ 528line 527 didn't jump to line 528 because the condition on line 527 was never true

528 instance_details['admin_port_id'] = server['admin_port_id'] 

529 

530 for key in ('password', 'pk_path', 'subnet_id'): 

531 if not instance_details[key]: 

532 instance_details.pop(key) 

533 return instance_details 

534 

535 def _load_public_key(self, path): 

536 with open(path, 'r') as f: 

537 public_key = f.read() 

538 return public_key 

539 

540 @utils.synchronized("service_instance_get_key", external=True) 

541 def _get_key(self, context): 

542 """Get ssh key. 

543 

544 :param context: defines context, that should be used 

545 :returns: tuple with keypair name and path to private key. 

546 """ 

547 if not (self.path_to_public_key and self.path_to_private_key): 

548 return (None, None) 

549 path_to_public_key = os.path.expanduser(self.path_to_public_key) 

550 path_to_private_key = os.path.expanduser(self.path_to_private_key) 

551 if (not os.path.exists(path_to_public_key) or 

552 not os.path.exists(path_to_private_key)): 

553 return (None, None) 

554 keypair_name = self.get_config_option("manila_service_keypair_name") 

555 keypairs = [k for k in self.compute_api.keypair_list(context) 

556 if k.name == keypair_name] 

557 if len(keypairs) > 1: 

558 raise exception.ServiceInstanceException(_('Ambiguous keypairs.')) 

559 

560 public_key = self._load_public_key(path_to_public_key) 

561 if not keypairs: 

562 keypair = self.compute_api.keypair_import( 

563 context, keypair_name, public_key) 

564 else: 

565 keypair = keypairs[0] 

566 if keypair.public_key != public_key: 

567 LOG.debug('Public key differs from existing keypair. ' 

568 'Creating new keypair.') 

569 self.compute_api.keypair_delete(context, keypair.id) 

570 keypair = self.compute_api.keypair_import( 

571 context, keypair_name, public_key) 

572 

573 return keypair.name, path_to_private_key 

574 

575 def _get_service_image(self, context): 

576 """Returns ID of service image for service vm creating.""" 

577 service_image_name = self.get_config_option("service_image_name") 

578 images = [image.id for image in self.image_api.image_list(context) 

579 if image.name == service_image_name 

580 and image.status == 'active'] 

581 if not images: 

582 raise exception.ServiceInstanceException( 

583 _("Image with name '%s' was not found or is not in " 

584 "'active' state.") % service_image_name) 

585 if len(images) != 1: 

586 raise exception.ServiceInstanceException( 

587 _("Multiple 'active' state images found with name '%s'!") % 

588 service_image_name) 

589 return images[0] 

590 

591 def _build_bdm_from_volume(self, volume_id, delete_on_termination=True): 

592 return [{ 

593 'boot_index': 0, 

594 'uuid': volume_id, 

595 'source_type': 'volume', 

596 'destination_type': 'volume', 

597 'delete_on_termination': bool(delete_on_termination), 

598 }] 

599 

600 def _build_bdm_from_image(self, image_id, size_gb, 

601 delete_on_termination=True): 

602 # Nova will create the volume in Cinder and attach as root 

603 return [{ 

604 'boot_index': 0, 

605 'uuid': image_id, 

606 'source_type': 'image', 

607 'destination_type': 'volume', 

608 'volume_size': int(size_gb), 

609 'delete_on_termination': bool(delete_on_termination), 

610 }] 

611 

612 def _create_service_instance(self, context, instance_name, network_info): 

613 """Creates service vm and sets up networking for it.""" 

614 boot_from_volume = self.get_config_option( 

615 'service_instance_boot_from_volume') 

616 block_device_mapping_v2 = None 

617 boot_volume_id = None 

618 service_image_id = self._get_service_image(context) 

619 if boot_from_volume: 

620 del_root = self.get_config_option( 

621 'service_instance_boot_volume_delete_on_termination') 

622 base_vol_id = self.get_config_option( 

623 "service_instance_base_boot_volume_id") 

624 root_size = self.get_config_option( 

625 'service_instance_boot_volume_size') 

626 if base_vol_id: 

627 msg = "Creating boot volume for share server '%s'." 

628 LOG.debug(msg, network_info['server_id']) 

629 name = self.get_config_option( 

630 'service_instance_boot_volume_name_template' 

631 ) % network_info[ 

632 'server_id'] 

633 

634 volume_info = { 

635 'size': root_size, 

636 'name': name, 

637 'description': '', 

638 'availability_zone': ( 

639 self.availability_zone 

640 ), 

641 'source_volid': base_vol_id 

642 } 

643 vol_type = self.get_config_option( 

644 "service_instance_boot_volume_type") 

645 volume_info['volume_type'] = vol_type 

646 volume = self.volume_api.create(context, **volume_info) 

647 msg_error = _('Failed to create bootable volume') 

648 timeout = self.get_config_option('max_time_to_create_volume') 

649 msg_timeout = ( 

650 _('Volume has not been created in %ss. Giving up') % 

651 timeout 

652 ) 

653 

654 volume = self.volume_api.wait_for_available_volume( 

655 volume, timeout, 

656 msg_error=msg_error, msg_timeout=msg_timeout 

657 ) 

658 boot_volume_id = volume['id'] 

659 block_device_mapping_v2 = self._build_bdm_from_volume( 

660 boot_volume_id, del_root 

661 ) 

662 else: 

663 block_device_mapping_v2 = self._build_bdm_from_image( 

664 service_image_id, 

665 root_size, 

666 delete_on_termination=del_root 

667 ) 

668 

669 key_name, key_path = self._get_key(context) 

670 if not (self.get_config_option("service_instance_password") or 

671 key_name): 

672 raise exception.ServiceInstanceException( 

673 _('Neither service instance password nor key are available.')) 

674 if not key_path: 674 ↛ 675line 674 didn't jump to line 675 because the condition on line 674 was never true

675 LOG.warning( 

676 'No key path is available. May be non-existent key path is ' 

677 'provided. Check path_to_private_key (current value ' 

678 '%(private_path)s) and path_to_public_key (current value ' 

679 '%(public_path)s) in manila configuration file.', dict( 

680 private_path=self.path_to_private_key, 

681 public_path=self.path_to_public_key)) 

682 network_data = self.network_helper.setup_network(network_info) 

683 fail_safe_data = dict( 

684 router_id=network_data.get('router_id'), 

685 subnet_id=network_data.get('subnet_id')) 

686 if network_data.get('service_port'): 

687 fail_safe_data['service_port_id'] = ( 

688 network_data['service_port']['id']) 

689 if network_data.get('public_port'): 

690 fail_safe_data['public_port_id'] = ( 

691 network_data['public_port']['id']) 

692 if network_data.get('admin_port'): 

693 fail_safe_data['admin_port_id'] = ( 

694 network_data['admin_port']['id']) 

695 try: 

696 create_kwargs = self._get_service_instance_create_kwargs() 

697 if boot_from_volume: 

698 create_kwargs[ 

699 'block_device_mapping_v2' 

700 ] = block_device_mapping_v2 

701 create_kwargs['image'] = None 

702 else: 

703 create_kwargs['image'] = service_image_id 

704 service_instance = self.compute_api.server_create( 

705 context, 

706 name=instance_name, 

707 flavor=self.get_config_option("service_instance_flavor_id"), 

708 key_name=key_name, 

709 nics=network_data['nics'], 

710 availability_zone=self.availability_zone, 

711 **create_kwargs) 

712 

713 fail_safe_data['instance_id'] = service_instance['id'] 

714 

715 service_instance = self.wait_for_instance_to_be_active( 

716 service_instance['id'], 

717 self.max_time_to_build_instance) 

718 

719 if self.get_config_option("limit_ssh_access"): 719 ↛ 730line 719 didn't jump to line 730 because the condition on line 719 was always true

720 try: 

721 service_subnet = network_data['service_subnet'] 

722 except KeyError: 

723 LOG.error( 

724 "Unable to limit ssh access to instance id: '%s'!", 

725 fail_safe_data['instance_id']) 

726 raise exception.ManilaException( 

727 "Unable to limit SSH access - " 

728 "invalid service subnet details provided") 

729 else: 

730 service_subnet = False 

731 

732 sec_groups = self._get_or_create_security_groups( 

733 context, allow_ssh_subnet=service_subnet) 

734 

735 for sg in sec_groups: 

736 sg_id = sg['id'] 

737 LOG.debug( 

738 "Adding security group '%(sg)s' to server '%(si)s'.", 

739 dict(sg=sg_id, si=service_instance["id"])) 

740 self.compute_api.add_security_group_to_server( 

741 context, service_instance["id"], sg_id) 

742 

743 ip = (network_data.get('service_port', 

744 network_data.get( 

745 'admin_port'))['fixed_ips']) 

746 service_instance['ip'] = ip[0]['ip_address'] 

747 public_ip = (network_data.get('public_port', network_data.get( 

748 'service_port'))['fixed_ips']) 

749 service_instance['public_address'] = public_ip[0]['ip_address'] 

750 

751 except Exception as e: 

752 e.detail_data = {'server_details': fail_safe_data} 

753 # Clean up boot volume if we created one and instance creation 

754 # failed. If instance was created successfully, the volume is 

755 # attached and will be cleaned up when the instance is deleted. 

756 if boot_volume_id and 'instance_id' not in fail_safe_data: 756 ↛ 757line 756 didn't jump to line 757 because the condition on line 756 was never true

757 LOG.warning("Cleaning up orphaned boot volume %s after " 

758 "service instance creation failure.", 

759 boot_volume_id) 

760 try: 

761 self.volume_api.delete(context, boot_volume_id) 

762 except Exception: 

763 LOG.exception("Failed to delete orphaned boot volume %s.", 

764 boot_volume_id) 

765 raise 

766 

767 service_instance.update(fail_safe_data) 

768 service_instance['pk_path'] = key_path 

769 for pair in [('router', 'router_id'), ('service_subnet', 'subnet_id')]: 

770 if pair[0] in network_data and 'id' in network_data[pair[0]]: 770 ↛ 769line 770 didn't jump to line 769 because the condition on line 770 was always true

771 service_instance[pair[1]] = network_data[pair[0]]['id'] 

772 

773 admin_port = network_data.get('admin_port') 

774 if admin_port: 774 ↛ 783line 774 didn't jump to line 783 because the condition on line 774 was always true

775 try: 

776 service_instance['admin_ip'] = ( 

777 admin_port['fixed_ips'][0]['ip_address']) 

778 except Exception: 

779 msg = _("Admin port is being used but Admin IP was not found.") 

780 LOG.exception(msg) 

781 raise exception.AdminIPNotFound(reason=msg) 

782 

783 return service_instance 

784 

785 def _get_service_instance_create_kwargs(self): 

786 """Specify extra arguments used when creating the service instance. 

787 

788 Classes inheriting the service instance manager can use this to easily 

789 pass extra arguments such as user data or metadata. 

790 """ 

791 return {} 

792 

793 def _check_server_availability(self, instance_details, interval=5): 

794 t = time.time() 

795 ssh_pool = ssh_utils.SSHPool(instance_details['ip'], 

796 22, 

797 interval, 

798 instance_details['username'], 

799 instance_details.get('password'), 

800 instance_details.get('pk_path'), 

801 max_size=1) 

802 while time.time() - t < self.max_time_to_build_instance: 

803 LOG.debug('Checking server availability.') 

804 if not self._test_server_connection(instance_details, ssh_pool): 

805 time.sleep(interval) 

806 else: 

807 return True 

808 return False 

809 

810 def _test_server_connection(self, server, ssh_pool): 

811 conn = None 

812 try: 

813 conn = ssh_pool.create(quiet=True) 

814 return True 

815 except Exception as e: 

816 LOG.debug(e) 

817 LOG.debug("Could not login to server %s over SSH. Waiting...", 

818 server["ip"]) 

819 return False 

820 finally: 

821 if conn: 

822 conn.close() 

823 

824 def delete_service_instance(self, context, server_details): 

825 """Removes share infrastructure. 

826 

827 Deletes service vm and subnet, associated to share network. 

828 """ 

829 instance_id = server_details.get("instance_id") 

830 self._delete_server(context, instance_id) 

831 self.network_helper.teardown_network(server_details) 

832 

833 def wait_for_instance_to_be_active(self, instance_id, timeout): 

834 t = time.time() 

835 while time.time() - t < timeout: 

836 try: 

837 service_instance = self.compute_api.server_get( 

838 self.admin_context, 

839 instance_id) 

840 except exception.InstanceNotFound as e: 

841 LOG.debug(e) 

842 time.sleep(1) 

843 continue 

844 

845 instance_status = service_instance['status'] 

846 # NOTE(vponomaryov): emptiness of 'networks' field checked as 

847 # workaround for nova/neutron bug #1210483. 

848 if (instance_status == 'ACTIVE' and 

849 service_instance.get('networks', {})): 

850 return service_instance 

851 elif service_instance['status'] == 'ERROR': 

852 break 

853 

854 LOG.debug("Waiting for instance %(instance_id)s to be active. " 

855 "Current status: %(instance_status)s.", 

856 dict(instance_id=instance_id, 

857 instance_status=instance_status)) 

858 time.sleep(1) 

859 raise exception.ServiceInstanceException( 

860 _("Instance %(instance_id)s failed to reach active state " 

861 "in %(timeout)s seconds. " 

862 "Current status: %(instance_status)s.") % 

863 dict(instance_id=instance_id, 

864 timeout=timeout, 

865 instance_status=instance_status)) 

866 

867 def reboot_server(self, server, soft_reboot=False): 

868 self.compute_api.server_reboot(self.admin_context, 

869 server['instance_id'], 

870 soft_reboot) 

871 

872 

873class BaseNetworkhelper(metaclass=abc.ABCMeta): 

874 

875 @property 

876 @abc.abstractmethod 

877 def NAME(self): 

878 """Returns code name of network helper.""" 

879 

880 @abc.abstractmethod 

881 def __init__(self, service_instance_manager): 

882 """Instantiates class and its attrs.""" 

883 

884 @abc.abstractmethod 

885 def get_network_name(self, network_info): 

886 """Returns name of network for service instance.""" 

887 

888 @abc.abstractmethod 

889 def setup_connectivity_with_service_instances(self): 

890 """Sets up connectivity between Manila host and service instances.""" 

891 

892 @abc.abstractmethod 

893 def setup_network(self, network_info): 

894 """Sets up network for service instance.""" 

895 

896 @abc.abstractmethod 

897 def teardown_network(self, server_details): 

898 """Teardowns network resources provided for service instance.""" 

899 

900 

901class NeutronNetworkHelper(BaseNetworkhelper): 

902 

903 def __init__(self, service_instance_manager): 

904 self.get_config_option = service_instance_manager.get_config_option 

905 self.vif_driver = importutils.import_class( 

906 self.get_config_option("interface_driver"))() 

907 

908 if service_instance_manager.driver_config: 

909 self._network_config_group = ( 

910 service_instance_manager.driver_config.network_config_group or 

911 service_instance_manager.driver_config.config_group) 

912 else: 

913 self._network_config_group = None 

914 

915 self.use_admin_port = False 

916 self.use_service_network = True 

917 self._neutron_api = None 

918 self._service_network_id = None 

919 self.connect_share_server_to_tenant_network = ( 

920 self.get_config_option('connect_share_server_to_tenant_network')) 

921 

922 self.admin_network_id = self.get_config_option('admin_network_id') 

923 self.admin_subnet_id = self.get_config_option('admin_subnet_id') 

924 

925 if self.admin_network_id and self.admin_subnet_id: 925 ↛ 926line 925 didn't jump to line 926 because the condition on line 925 was never true

926 self.use_admin_port = True 

927 if self.use_admin_port and self.connect_share_server_to_tenant_network: 927 ↛ 928line 927 didn't jump to line 928 because the condition on line 927 was never true

928 self.use_service_network = False 

929 

930 @property 

931 def NAME(self): 

932 return NEUTRON_NAME 

933 

934 @property 

935 def admin_project_id(self): 

936 return self.neutron_api.admin_project_id 

937 

938 @property 

939 @utils.synchronized("instantiate_neutron_api_neutron_net_helper") 

940 def neutron_api(self): 

941 if not self._neutron_api: 

942 self._neutron_api = neutron.API( 

943 config_group_name=self._network_config_group) 

944 return self._neutron_api 

945 

946 @property 

947 @utils.synchronized("service_network_id_neutron_net_helper") 

948 def service_network_id(self): 

949 if not self._service_network_id: 

950 self._service_network_id = self._get_service_network_id() 

951 return self._service_network_id 

952 

953 def get_network_name(self, network_info): 

954 """Returns name of network for service instance.""" 

955 net = self.neutron_api.get_network(network_info['neutron_net_id']) 

956 return net['name'] 

957 

958 @coordination.synchronized("service_instance_get_service_network") 

959 def _get_service_network_id(self): 

960 """Finds existing or creates new service network.""" 

961 service_network_name = self.get_config_option("service_network_name") 

962 networks = [] 

963 for network in self.neutron_api.get_all_admin_project_networks(): 

964 if network['name'] == service_network_name: 964 ↛ 963line 964 didn't jump to line 963 because the condition on line 964 was always true

965 networks.append(network) 

966 if len(networks) > 1: 

967 raise exception.ServiceInstanceException( 

968 _('Ambiguous service networks.')) 

969 elif not networks: 

970 return self.neutron_api.network_create( 

971 self.admin_project_id, service_network_name)['id'] 

972 else: 

973 return networks[0]['id'] 

974 

975 @utils.synchronized( 

976 "service_instance_setup_and_teardown_network_for_instance", 

977 external=True) 

978 def teardown_network(self, server_details): 

979 subnet_id = server_details.get("subnet_id") 

980 router_id = server_details.get("router_id") 

981 

982 service_port_id = server_details.get("service_port_id") 

983 public_port_id = server_details.get("public_port_id") 

984 admin_port_id = server_details.get("admin_port_id") 

985 for port_id in (service_port_id, public_port_id, admin_port_id): 

986 if port_id: 

987 try: 

988 self.neutron_api.delete_port(port_id) 

989 except exception.NetworkException as e: 

990 if e.kwargs.get('code') != 404: 

991 raise 

992 LOG.debug("Failed to delete port %(port_id)s with error: " 

993 "\n %(exc)s", {"port_id": port_id, "exc": e}) 

994 

995 if subnet_id: 

996 ports = self.neutron_api.list_ports( 

997 fields=['device_id', 'device_owner'], 

998 fixed_ips=['subnet_id=%s' % subnet_id]) 

999 # NOTE(vponomaryov): iterate ports to get to know whether current 

1000 # subnet is used or not. We will not remove it from router if it 

1001 # is used. 

1002 for port in ports: 

1003 # NOTE(vponomaryov): if device_id is present, then we know that 

1004 # this port is used. Also, if device owner is 'compute:*', then 

1005 # we know that it is VM. We continue only if both are 'True'. 

1006 if (port['device_id'] and 

1007 port['device_owner'].startswith('compute:')): 

1008 # NOTE(vponomaryov): There are other share servers 

1009 # exist that use this subnet. So, do not remove it 

1010 # from router. 

1011 return 

1012 if router_id: 

1013 try: 

1014 # NOTE(vponomaryov): there is no other share servers or 

1015 # some VMs that use this subnet. So, remove it from router. 

1016 self.neutron_api.router_remove_interface( 

1017 router_id, subnet_id) 

1018 except exception.NetworkException as e: 

1019 if e.kwargs['code'] != 404: 

1020 raise 

1021 LOG.debug('Subnet %(subnet_id)s is not attached to the ' 

1022 'router %(router_id)s.', 

1023 {'subnet_id': subnet_id, 'router_id': router_id}) 

1024 self.neutron_api.update_subnet(subnet_id, '') 

1025 

1026 @utils.synchronized( 

1027 "service_instance_setup_and_teardown_network_for_instance", 

1028 external=True) 

1029 def setup_network(self, network_info): 

1030 neutron_net_id = network_info['neutron_net_id'] 

1031 neutron_subnet_id = network_info['neutron_subnet_id'] 

1032 network_data = dict() 

1033 subnet_name = ('service_subnet_for_handling_of_share_server_for_' 

1034 'tenant_subnet_%s' % neutron_subnet_id) 

1035 

1036 if self.use_service_network: 

1037 network_data['service_subnet'] = self._get_service_subnet( 

1038 subnet_name) 

1039 if not network_data['service_subnet']: 

1040 network_data['service_subnet'] = ( 

1041 self.neutron_api.subnet_create( 

1042 self.admin_project_id, self.service_network_id, 

1043 subnet_name, self._get_cidr_for_subnet(), 

1044 self.connect_share_server_to_tenant_network)) 

1045 

1046 network_data['ports'] = [] 

1047 

1048 if not self.connect_share_server_to_tenant_network: 

1049 network_data['router'] = self._get_private_router( 

1050 neutron_net_id, neutron_subnet_id) 

1051 try: 

1052 self.neutron_api.router_add_interface( 

1053 network_data['router']['id'], 

1054 network_data['service_subnet']['id']) 

1055 except exception.NetworkException as e: 

1056 if e.kwargs['code'] != 400: 

1057 raise 

1058 LOG.debug('Subnet %(subnet_id)s is already attached to the ' 

1059 'router %(router_id)s.', 

1060 {'subnet_id': network_data['service_subnet']['id'], 

1061 'router_id': network_data['router']['id']}) 

1062 else: 

1063 network_data['public_port'] = self.neutron_api.create_port( 

1064 self.admin_project_id, neutron_net_id, 

1065 subnet_id=neutron_subnet_id, device_owner='manila') 

1066 network_data['ports'].append(network_data['public_port']) 

1067 

1068 if self.use_service_network: 

1069 network_data['service_port'] = self.neutron_api.create_port( 

1070 self.admin_project_id, self.service_network_id, 

1071 subnet_id=network_data['service_subnet']['id'], 

1072 device_owner='manila') 

1073 network_data['ports'].append(network_data['service_port']) 

1074 

1075 if self.use_admin_port: 

1076 network_data['admin_port'] = self.neutron_api.create_port( 

1077 self.admin_project_id, self.admin_network_id, 

1078 subnet_id=self.admin_subnet_id, device_owner='manila') 

1079 network_data['ports'].append(network_data['admin_port']) 

1080 

1081 try: 

1082 self.setup_connectivity_with_service_instances() 

1083 except Exception: 

1084 for port in network_data['ports']: 

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

1086 raise 

1087 

1088 network_data['nics'] = [ 

1089 {'port-id': port['id']} for port in network_data['ports']] 

1090 public_ip = network_data.get( 

1091 'public_port', network_data.get('service_port')) 

1092 network_data['ip_address'] = public_ip['fixed_ips'][0]['ip_address'] 

1093 

1094 return network_data 

1095 

1096 def _get_cidr_for_subnet(self): 

1097 """Returns not used cidr for service subnet creating.""" 

1098 subnets = self._get_all_service_subnets() 

1099 used_cidrs = set(subnet['cidr'] for subnet in subnets) 

1100 serv_cidr = netaddr.IPNetwork( 

1101 self.get_config_option("service_network_cidr")) 

1102 division_mask = self.get_config_option("service_network_division_mask") 

1103 for subnet in serv_cidr.subnet(division_mask): 

1104 cidr = str(subnet.cidr) 

1105 if cidr not in used_cidrs: 

1106 return cidr 

1107 else: 

1108 raise exception.ServiceInstanceException(_('No available cidrs.')) 

1109 

1110 def setup_connectivity_with_service_instances(self): 

1111 """Sets up connectivity with service instances. 

1112 

1113 Creates host port in service network and/or admin network, creating 

1114 and setting up required network devices. 

1115 """ 

1116 if self.use_service_network: 1116 ↛ 1126line 1116 didn't jump to line 1126 because the condition on line 1116 was always true

1117 LOG.debug("Plugging service instance into service network %s.", 

1118 self.service_network_id) 

1119 port = self._get_service_port( 

1120 self.service_network_id, None, 'manila-share') 

1121 port = self._add_fixed_ips_to_service_port(port) 

1122 interface_name = self.vif_driver.get_device_name(port) 

1123 device = ip_lib.IPDevice(interface_name) 

1124 self._plug_interface_in_host(interface_name, device, port) 

1125 

1126 if self.use_admin_port: 1126 ↛ exitline 1126 didn't return from function 'setup_connectivity_with_service_instances' because the condition on line 1126 was always true

1127 LOG.debug("Plugging service instance into admin network %s.", 

1128 self.admin_network_id) 

1129 port = self._get_service_port( 

1130 self.admin_network_id, self.admin_subnet_id, 

1131 'manila-admin-share') 

1132 interface_name = self.vif_driver.get_device_name(port) 

1133 device = ip_lib.IPDevice(interface_name) 

1134 self._plug_interface_in_host(interface_name, device, port, 

1135 clear_outdated_routes=True) 

1136 

1137 @utils.synchronized("service_instance_plug_interface_in_host", 

1138 external=True) 

1139 def _plug_interface_in_host(self, interface_name, device, port, 

1140 clear_outdated_routes=False): 

1141 

1142 LOG.debug("Plug interface into host - interface_name: %s, " 

1143 "device: %s, port: %s", interface_name, device, port) 

1144 self.vif_driver.plug(interface_name, port['id'], port['mac_address']) 

1145 cidrs_to_clear = [] 

1146 ip_cidrs = [] 

1147 for fixed_ip in port['fixed_ips']: 

1148 subnet = self.neutron_api.get_subnet(fixed_ip['subnet_id']) 

1149 if clear_outdated_routes: 

1150 cidrs_to_clear.append(subnet['cidr']) 

1151 

1152 net = netaddr.IPNetwork(subnet['cidr']) 

1153 ip_cidr = '%s/%s' % (fixed_ip['ip_address'], net.prefixlen) 

1154 ip_cidrs.append(ip_cidr) 

1155 

1156 self.vif_driver.init_l3(interface_name, ip_cidrs, 

1157 clear_cidrs=cidrs_to_clear) 

1158 

1159 @utils.synchronized("service_instance_get_service_port", external=True) 

1160 def _get_service_port(self, network_id, subnet_id, device_id): 

1161 """Find or creates service neutron port. 

1162 

1163 This port will be used for connectivity with service instances. 

1164 """ 

1165 host = self.get_config_option("service_network_host") or CONF.host 

1166 search_opts = {'device_id': device_id, 

1167 'binding:host_id': host} 

1168 ports = [port for port in self.neutron_api. 

1169 list_ports(**search_opts)] 

1170 if len(ports) > 1: 

1171 raise exception.ServiceInstanceException( 

1172 _('Error. Ambiguous service ports.')) 

1173 elif not ports: 

1174 port = self.neutron_api.create_port( 

1175 self.admin_project_id, network_id, subnet_id=subnet_id, 

1176 device_id=device_id, device_owner='manila:share', host_id=host, 

1177 port_security_enabled=False) 

1178 else: 

1179 port = ports[0] 

1180 return port 

1181 

1182 @utils.synchronized( 

1183 "service_instance_add_fixed_ips_to_service_port", external=True) 

1184 def _add_fixed_ips_to_service_port(self, port): 

1185 network = self.neutron_api.get_network(self.service_network_id) 

1186 subnets = set(network['subnets']) 

1187 port_fixed_ips = [] 

1188 for fixed_ip in port['fixed_ips']: 

1189 port_fixed_ips.append({'subnet_id': fixed_ip['subnet_id'], 

1190 'ip_address': fixed_ip['ip_address']}) 

1191 if fixed_ip['subnet_id'] in subnets: 1191 ↛ 1188line 1191 didn't jump to line 1188 because the condition on line 1191 was always true

1192 subnets.remove(fixed_ip['subnet_id']) 

1193 

1194 # If there are subnets here that means that 

1195 # we need to add those to the port and call update. 

1196 if subnets: 1196 ↛ 1201line 1196 didn't jump to line 1201 because the condition on line 1196 was always true

1197 port_fixed_ips.extend([dict(subnet_id=s) for s in subnets]) 

1198 port = self.neutron_api.update_port_fixed_ips( 

1199 port['id'], {'fixed_ips': port_fixed_ips}) 

1200 

1201 return port 

1202 

1203 @utils.synchronized("service_instance_get_private_router", external=True) 

1204 def _get_private_router(self, neutron_net_id, neutron_subnet_id): 

1205 """Returns router attached to private subnet gateway.""" 

1206 private_subnet = self.neutron_api.get_subnet(neutron_subnet_id) 

1207 if not private_subnet['gateway_ip']: 

1208 raise exception.ServiceInstanceException( 

1209 _('Subnet must have gateway.')) 

1210 private_network_ports = [p for p in self.neutron_api.list_ports( 

1211 network_id=neutron_net_id)] 

1212 for p in private_network_ports: 

1213 fixed_ip = p['fixed_ips'][0] 

1214 if (fixed_ip['subnet_id'] == private_subnet['id'] and 1214 ↛ 1212line 1214 didn't jump to line 1212 because the condition on line 1214 was always true

1215 fixed_ip['ip_address'] == private_subnet['gateway_ip']): 

1216 private_subnet_gateway_port = p 

1217 break 

1218 else: 

1219 raise exception.ServiceInstanceException( 

1220 _('Subnet gateway is not attached to the router.')) 

1221 private_subnet_router = self.neutron_api.show_router( 

1222 private_subnet_gateway_port['device_id']) 

1223 return private_subnet_router 

1224 

1225 @utils.synchronized("service_instance_get_service_subnet", external=True) 

1226 def _get_service_subnet(self, subnet_name): 

1227 all_service_subnets = self._get_all_service_subnets() 

1228 service_subnets = [subnet for subnet in all_service_subnets 

1229 if subnet['name'] == subnet_name] 

1230 if len(service_subnets) == 1: 

1231 return service_subnets[0] 

1232 elif not service_subnets: 

1233 unused_service_subnets = [subnet for subnet in all_service_subnets 

1234 if subnet['name'] == ''] 

1235 if unused_service_subnets: 

1236 service_subnet = unused_service_subnets[0] 

1237 self.neutron_api.update_subnet( 

1238 service_subnet['id'], subnet_name) 

1239 return service_subnet 

1240 return None 

1241 else: 

1242 raise exception.ServiceInstanceException( 

1243 _('Ambiguous service subnets.')) 

1244 

1245 @utils.synchronized( 

1246 "service_instance_get_all_service_subnets", external=True) 

1247 def _get_all_service_subnets(self): 

1248 service_network = self.neutron_api.get_network(self.service_network_id) 

1249 subnets = [] 

1250 for subnet_id in service_network['subnets']: 

1251 subnets.append(self.neutron_api.get_subnet(subnet_id)) 

1252 return subnets