Coverage for manila/share/drivers/container/driver.py: 88%

309 statements  

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

1# Copyright (c) 2016 Mirantis, Inc. 

2# All Rights Reserved. 

3# 

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

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

6# a copy of the License at 

7# 

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

9# 

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

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

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

13# License for the specific language governing permissions and limitations 

14# under the License. 

15 

16"""Container Driver for shares. 

17 

18This driver uses a container as a share server. 

19Current implementation suggests that a container when started by Docker will 

20be plugged into a Linux bridge. Also it is suggested that all interfaces 

21willing to talk to each other reside in an OVS bridge.""" 

22 

23import math 

24 

25from oslo_config import cfg 

26from oslo_log import log 

27from oslo_serialization import jsonutils 

28from oslo_utils import importutils 

29from oslo_utils import uuidutils 

30 

31from manila import exception 

32from manila.i18n import _ 

33from manila.share import driver 

34from manila import utils 

35 

36 

37CONF = cfg.CONF 

38LOG = log.getLogger(__name__) 

39 

40 

41container_opts = [ 

42 cfg.StrOpt("container_linux_bridge_name", 

43 default="docker0", 

44 help="Linux bridge used by container hypervisor to plug " 

45 "host-side veth to. It will be unplugged from here " 

46 "by the driver."), 

47 cfg.StrOpt("container_ovs_bridge_name", 

48 default="br-int", 

49 help="OVS bridge to use to plug a container to."), 

50 cfg.BoolOpt("container_cifs_guest_ok", 

51 default=True, 

52 help="Determines whether to allow guest access to CIFS share " 

53 "or not."), 

54 cfg.StrOpt("container_image_name", 

55 default="manila-docker-container", 

56 help="Image to be used for a container-based share server."), 

57 cfg.StrOpt("container_helper", 

58 default="manila.share.drivers.container.container_helper." 

59 "DockerExecHelper", 

60 help="Container helper which provides container-related " 

61 "operations to the driver."), 

62 cfg.StrOpt("container_protocol_helper", 

63 default="manila.share.drivers.container.protocol_helper." 

64 "DockerCIFSHelper", 

65 help="Helper which facilitates interaction with share server."), 

66 cfg.StrOpt("container_security_service_helper", 

67 default="manila.share.drivers.container.security_service_helper" 

68 ".SecurityServiceHelper", 

69 help="Helper which facilitates interaction with security " 

70 "services."), 

71 cfg.StrOpt("container_storage_helper", 

72 default="manila.share.drivers.container.storage_helper." 

73 "LVMHelper", 

74 help="Helper which facilitates interaction with storage " 

75 "solution used to actually store data. By default LVM " 

76 "is used to provide storage for a share."), 

77 cfg.StrOpt("container_volume_mount_path", 

78 default="/tmp/shares", 

79 help="Folder name in host to which logical volume will be " 

80 "mounted prior to providing access to it from a " 

81 "container."), 

82] 

83 

84 

85class ContainerShareDriver(driver.ShareDriver, driver.ExecuteMixin): 

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

87 super(ContainerShareDriver, self).__init__([True], *args, **kwargs) 

88 self.configuration.append_config_values(container_opts) 

89 self.backend_name = self.configuration.safe_get( 

90 "share_backend_name") or "Docker" 

91 self.container = importutils.import_class( 

92 self.configuration.container_helper)( 

93 configuration=self.configuration) 

94 self.security_service_helper = importutils.import_class( 

95 self.configuration.container_security_service_helper)( 

96 configuration=self.configuration) 

97 self.storage = importutils.import_class( 

98 self.configuration.container_storage_helper)( 

99 configuration=self.configuration) 

100 self._helpers = {} 

101 self.network_allocation_update_support = True 

102 

103 def _get_helper(self, share): 

104 if share["share_proto"].upper() == "CIFS": 

105 helper = self._helpers.get("CIFS") 

106 if helper is not None: 

107 return helper(self.container, 

108 share=share, 

109 config=self.configuration) 

110 self._helpers["CIFS"] = importutils.import_class( 

111 self.configuration.container_protocol_helper) 

112 return self._helpers["CIFS"](self.container, 

113 share=share, 

114 config=self.configuration) 

115 else: 

116 raise exception.InvalidShare( 

117 reason=_("Wrong, unsupported or disabled protocol.")) 

118 

119 def _update_share_stats(self): 

120 data = { 

121 'share_backend_name': self.backend_name, 

122 'storage_protocol': 'CIFS', 

123 'reserved_percentage': 

124 self.configuration.reserved_share_percentage, 

125 'reserved_snapshot_percentage': 

126 self.configuration.reserved_share_from_snapshot_percentage or 

127 self.configuration.reserved_share_percentage, 

128 'reserved_share_extend_percentage': 

129 self.configuration.reserved_share_extend_percentage or 

130 self.configuration.reserved_share_percentage, 

131 'consistency_group_support': None, 

132 'snapshot_support': False, 

133 'create_share_from_snapshot_support': False, 

134 'driver_name': 'ContainerShareDriver', 

135 'pools': self.storage.get_share_server_pools(), 

136 'security_service_update_support': True, 

137 'share_server_multiple_subnet_support': True, 

138 'mount_point_name_support': False, 

139 } 

140 super(ContainerShareDriver, self)._update_share_stats(data) 

141 

142 def create_share(self, context, share, share_server=None): 

143 LOG.debug("Create share on server '%s'.", share_server["id"]) 

144 server_id = self._get_container_name(share_server["id"]) 

145 share_name = share.share_id 

146 self.storage.provide_storage(share_name, share['size']) 

147 

148 location = self._create_export_and_mount_storage( 

149 share, server_id, share_name) 

150 

151 return location 

152 

153 @utils.synchronized('container_driver_delete_share_lock', external=True) 

154 def delete_share(self, context, share, share_server=None): 

155 LOG.debug("Deleting share %(share)s on server '%(server)s'.", 

156 {"server": share_server["id"], 

157 "share": self._get_share_name(share)}) 

158 server_id = self._get_container_name(share_server["id"]) 

159 share_name = self._get_share_name(share) 

160 

161 self._delete_export_and_umount_storage(share, server_id, share_name, 

162 ignore_errors=True) 

163 

164 self.storage.remove_storage(share_name) 

165 LOG.debug("Deleted share %s successfully.", share_name) 

166 

167 def _get_share_name(self, share): 

168 if share.get('export_location'): 

169 return share['export_location'].split('/')[-1] 

170 else: 

171 return share.share_id 

172 

173 def extend_share(self, share, new_size, share_server=None): 

174 server_id = self._get_container_name(share_server["id"]) 

175 share_name = self._get_share_name(share) 

176 self.container.execute( 

177 server_id, 

178 ["umount", "/shares/%s" % share_name] 

179 ) 

180 self.storage.extend_share(share_name, new_size, share_server) 

181 lv_device = self.storage._get_lv_device(share_name) 

182 self.container.execute( 

183 server_id, 

184 ["mount", lv_device, "/shares/%s" % share_name] 

185 ) 

186 

187 def ensure_share(self, context, share, share_server=None): 

188 pass 

189 

190 def update_access(self, context, share, access_rules, add_rules, 

191 delete_rules, update_rules, share_server=None): 

192 server_id = self._get_container_name(share_server["id"]) 

193 share_name = self._get_share_name(share) 

194 LOG.debug("Updating access to share %(share)s at " 

195 "share server %(share_server)s.", 

196 {"share_server": share_server["id"], 

197 "share": share_name}) 

198 self._get_helper(share).update_access(server_id, share_name, 

199 access_rules, add_rules, 

200 delete_rules) 

201 

202 def get_network_allocations_number(self): 

203 return 1 

204 

205 def _get_container_name(self, server_id): 

206 return "manila_%s" % server_id.replace("-", "_") 

207 

208 def do_setup(self, *args, **kwargs): 

209 pass 

210 

211 def check_for_setup_error(self, *args, **kwargs): 

212 host_id = self.configuration.safe_get("neutron_host_id") 

213 neutron_class = importutils.import_class( 

214 'manila.network.neutron.neutron_network_plugin.' 

215 'NeutronNetworkPlugin' 

216 ) 

217 actual_class = importutils.import_class( 

218 self.configuration.safe_get("network_api_class")) 

219 if host_id is None and issubclass(actual_class, neutron_class): 

220 msg = _("%s requires neutron_host_id to be " 

221 "specified.") % neutron_class 

222 raise exception.ManilaException(msg) 

223 elif host_id is None: 223 ↛ exitline 223 didn't return from function 'check_for_setup_error' because the condition on line 223 was always true

224 LOG.warning("neutron_host_id is not specified. This driver " 

225 "might not work as expected without it.") 

226 

227 def _connect_to_network(self, server_id, network_info, host_veth, 

228 host_bridge, iface): 

229 LOG.debug("Attempting to connect container to neutron network.") 

230 network_allocation = network_info["network_allocations"][0] 

231 port_address = network_allocation.ip_address 

232 port_mac = network_allocation.mac_address 

233 port_id = network_allocation.id 

234 self.container.execute( 

235 server_id, 

236 ["ifconfig", iface, port_address, "up"] 

237 ) 

238 self.container.execute( 

239 server_id, 

240 ["ip", "link", "set", "dev", iface, "address", port_mac] 

241 ) 

242 msg_helper = { 

243 'id': server_id, 

244 'veth': host_veth, 

245 'lb': host_bridge, 

246 'ovsb': self.configuration.container_ovs_bridge_name, 

247 'ip': port_address, 

248 'network': network_info['neutron_net_id'], 

249 'subnet': network_info['neutron_subnet_id'], 

250 } 

251 LOG.debug("Container %(id)s veth is %(veth)s.", msg_helper) 

252 LOG.debug("Removing %(veth)s from %(lb)s.", msg_helper) 

253 self._execute("ip", "link", "set", "dev", host_veth, "nomaster", 

254 run_as_root=True) 

255 

256 LOG.debug("Plugging %(veth)s into %(ovsb)s.", msg_helper) 

257 set_if = ['--', 'set', 'interface', host_veth] 

258 e_mac = set_if + ['external-ids:attached-mac="%s"' % port_mac] 

259 e_id = set_if + ['external-ids:iface-id="%s"' % port_id] 

260 e_status = set_if + ['external-ids:iface-status=active'] 

261 e_mcid = set_if + ['external-ids:manila-container=%s' % server_id] 

262 self._execute("ovs-vsctl", "--", "add-port", 

263 self.configuration.container_ovs_bridge_name, host_veth, 

264 *(e_mac + e_id + e_status + e_mcid), run_as_root=True) 

265 LOG.debug("Now container %(id)s should be accessible from network " 

266 "%(network)s and subnet %(subnet)s by address %(ip)s.", 

267 msg_helper) 

268 

269 @utils.synchronized("container_driver_teardown_lock", external=True) 

270 def _teardown_server(self, *args, **kwargs): 

271 server_id = self._get_container_name(kwargs["server_details"]["id"]) 

272 veths = self.container.get_container_veths(server_id) 

273 networks = self.container.get_container_networks(server_id) 

274 

275 for veth, network in zip(veths, networks): 

276 LOG.debug("Deleting veth %s.", veth) 

277 try: 

278 self._execute("ovs-vsctl", "--", "del-port", 

279 self.configuration.container_ovs_bridge_name, 

280 veth, run_as_root=True) 

281 except exception.ProcessExecutionError as e: 

282 LOG.warning("Failed to delete port %s: port vanished.", veth) 

283 LOG.error(e) 

284 self.container.disconnect_network(network, server_id) 

285 

286 if network != "bridge": 286 ↛ 275line 286 didn't jump to line 275 because the condition on line 286 was always true

287 self.container.remove_network(network) 

288 

289 self.container.stop_container(server_id) 

290 

291 def _setup_server_network(self, server_id, network_info): 

292 existing_interfaces = self.container.fetch_container_interfaces( 

293 server_id) 

294 new_interfaces = [] 

295 

296 # If the share server network allocations are being updated, create 

297 # interfaces starting with ethX + 1. 

298 if existing_interfaces: 

299 ifnum_offset = len(existing_interfaces) 

300 for ifnum, subnet in enumerate(network_info): 

301 # TODO(ecsantos): Newer Ubuntu images (systemd >= 197) use 

302 # predictable network interface names (e.g., enp3s0) instead of 

303 # the classical kernel naming scheme (e.g., eth0). The 

304 # Container driver currently uses an Ubuntu Xenial Docker 

305 # image, so if it's updated in the future, these "eth" strings 

306 # should also be updated. 

307 new_interfaces.append("eth" + str(ifnum + ifnum_offset)) 

308 # Otherwise (the share server was just created), create interfaces 

309 # starting with eth0. 

310 else: 

311 for ifnum, subnet in enumerate(network_info): 

312 new_interfaces.append("eth" + str(ifnum)) 

313 

314 for new_interface, subnet in zip(new_interfaces, network_info): 

315 network_name = "manila-docker-network-" + uuidutils.generate_uuid() 

316 self.container.create_network(network_name) 

317 self.container.connect_network(network_name, server_id) 

318 

319 bridge = self.container.get_network_bridge(network_name) 

320 veth = self.container.get_veth_from_bridge(bridge) 

321 self._connect_to_network(server_id, subnet, veth, bridge, 

322 new_interface) 

323 

324 @utils.synchronized("veth-lock", external=True) 

325 def _setup_server(self, network_info, metadata=None): 

326 msg = "Creating share server '%s'." 

327 common_net_info = network_info[0] 

328 server_id = self._get_container_name(common_net_info["server_id"]) 

329 LOG.debug(msg, server_id) 

330 

331 try: 

332 self.container.create_container(server_id) 

333 self.container.start_container(server_id) 

334 except Exception as e: 

335 raise exception.ManilaException(_("Cannot create container: %s") % 

336 e) 

337 

338 self._setup_server_network(server_id, network_info) 

339 security_services = common_net_info.get('security_services') 

340 

341 if security_services: 

342 self.setup_security_services(server_id, security_services) 

343 

344 LOG.info("Container %s was created.", server_id) 

345 return {"id": common_net_info["server_id"]} 

346 

347 def _delete_export_and_umount_storage( 

348 self, share, server_id, share_name, ignore_errors=False): 

349 

350 self._umount_storage( 

351 share, server_id, share_name, ignore_errors=ignore_errors) 

352 

353 # (aovchinnikov): bug 1621784 manifests itself here as well as in 

354 # storage helper. There is a chance that we won't be able to remove 

355 # this directory, despite the fact that it is not shared anymore and 

356 # already contains nothing. In such case the driver should not fail 

357 # share deletion, but issue a warning. 

358 self.container.execute( 

359 server_id, 

360 ["rm", "-fR", "/shares/%s" % share_name], 

361 ignore_errors=True 

362 ) 

363 

364 def _umount_storage( 

365 self, share, server_id, share_name, ignore_errors=False): 

366 

367 self._get_helper(share).delete_share(server_id, share_name, 

368 ignore_errors=ignore_errors) 

369 self.container.execute( 

370 server_id, 

371 ["umount", "/shares/%s" % share_name], 

372 ignore_errors=ignore_errors 

373 ) 

374 

375 def _create_export_and_mount_storage(self, share, server_id, share_name): 

376 self.container.execute( 

377 server_id, 

378 ["mkdir", "-m", "750", "/shares/%s" % share_name] 

379 ) 

380 return self._mount_storage(share, server_id, share_name) 

381 

382 def _mount_storage(self, share, server_id, share_name): 

383 lv_device = self.storage._get_lv_device(share_name) 

384 self.container.execute( 

385 server_id, 

386 ["mount", lv_device, "/shares/%s" % share_name] 

387 ) 

388 location = self._get_helper(share).create_share(server_id) 

389 return location 

390 

391 def manage_existing_with_server( 

392 self, share, driver_options, share_server=None): 

393 if not share_server and self.driver_handles_share_servers: 

394 raise exception.ShareBackendException( 

395 "A share server object is needed to manage a share in this " 

396 "driver mode of operation.") 

397 server_id = self._get_container_name(share_server["id"]) 

398 share_name = self._get_share_name(share) 

399 size = int(math.ceil(float(self.storage.get_size(share_name)))) 

400 

401 self._delete_export_and_umount_storage(share, server_id, share_name) 

402 

403 new_share_name = share.share_id 

404 self.storage.rename_storage(share_name, new_share_name) 

405 

406 location = self._create_export_and_mount_storage( 

407 share, server_id, new_share_name) 

408 

409 result = {'size': size, 'export_locations': location} 

410 LOG.info("Successfully managed share %(share)s, returning %(data)s", 

411 {'share': share.id, 'data': result}) 

412 return result 

413 

414 def unmanage_with_server(self, share, share_server=None): 

415 pass 

416 

417 def get_share_server_network_info( 

418 self, context, share_server, identifier, driver_options): 

419 name = self._get_correct_container_old_name(identifier) 

420 return self.container.fetch_container_addresses(name, "inet") 

421 

422 def manage_server(self, context, share_server, identifier, driver_options): 

423 new_name = self._get_container_name(share_server['id']) 

424 old_name = self._get_correct_container_old_name(identifier) 

425 self.container.rename_container(old_name, new_name) 

426 return new_name, {'id': share_server['id']} 

427 

428 def unmanage_server(self, server_details, security_services=None): 

429 pass 

430 

431 def _get_correct_container_old_name(self, name): 

432 # Check if the container with the given name exists, else return 

433 # the name based on the driver template 

434 if not self.container.container_exists(name): 

435 return self._get_container_name(name) 

436 return name 

437 

438 def migration_check_compatibility(self, context, source_share, 

439 destination_share, share_server=None, 

440 destination_share_server=None): 

441 return self.storage.migration_check_compatibility( 

442 context, source_share, destination_share, 

443 share_server=share_server, 

444 destination_share_server=destination_share_server) 

445 

446 def migration_start(self, context, source_share, destination_share, 

447 source_snapshots, snapshot_mappings, 

448 share_server=None, destination_share_server=None): 

449 self.storage.migration_start( 

450 context, source_share, destination_share, 

451 source_snapshots, snapshot_mappings, 

452 share_server=share_server, 

453 destination_share_server=destination_share_server) 

454 

455 def migration_continue(self, context, source_share, destination_share, 

456 source_snapshots, snapshot_mappings, 

457 share_server=None, destination_share_server=None): 

458 return self.storage.migration_continue( 

459 context, source_share, destination_share, 

460 source_snapshots, snapshot_mappings, share_server=share_server, 

461 destination_share_server=destination_share_server) 

462 

463 def migration_get_progress(self, context, source_share, 

464 destination_share, source_snapshots, 

465 snapshot_mappings, share_server=None, 

466 destination_share_server=None): 

467 return self.storage.migration_get_progress( 

468 context, source_share, destination_share, 

469 source_snapshots, snapshot_mappings, share_server=share_server, 

470 destination_share_server=destination_share_server) 

471 

472 def migration_cancel(self, context, source_share, destination_share, 

473 source_snapshots, snapshot_mappings, 

474 share_server=None, destination_share_server=None): 

475 self.storage.migration_cancel( 

476 context, source_share, destination_share, 

477 source_snapshots, snapshot_mappings, share_server=share_server, 

478 destination_share_server=destination_share_server) 

479 

480 def migration_complete(self, context, source_share, destination_share, 

481 source_snapshots, snapshot_mappings, 

482 share_server=None, destination_share_server=None): 

483 # Removes the source share reference from the source container 

484 source_server_id = self._get_container_name(share_server["id"]) 

485 self._umount_storage( 

486 source_share, source_server_id, source_share.share_id) 

487 

488 # storage removes source share 

489 self.storage.migration_complete( 

490 context, source_share, destination_share, 

491 source_snapshots, snapshot_mappings, share_server=share_server, 

492 destination_share_server=destination_share_server) 

493 

494 # Enables the access on the destination container 

495 destination_server_id = self._get_container_name( 

496 destination_share_server["id"]) 

497 new_export_locations = self._mount_storage( 

498 destination_share, destination_server_id, 

499 destination_share.share_id) 

500 

501 msg = ("Volume move operation for share %(shr)s was completed " 

502 "successfully. Share has been moved from %(src)s to " 

503 "%(dest)s.") 

504 msg_args = { 

505 'shr': source_share['id'], 

506 'src': source_share['host'], 

507 'dest': destination_share['host'], 

508 } 

509 LOG.info(msg, msg_args) 

510 

511 return { 

512 'export_locations': new_export_locations, 

513 } 

514 

515 def share_server_migration_check_compatibility( 

516 self, context, share_server, dest_host, old_share_network, 

517 new_share_network, shares_request_spec): 

518 """Is called to check migration compatibility for a share server.""" 

519 return self.storage.share_server_migration_check_compatibility( 

520 context, share_server, dest_host, old_share_network, 

521 new_share_network, shares_request_spec) 

522 

523 def share_server_migration_start(self, context, src_share_server, 

524 dest_share_server, shares, snapshots): 

525 """Is called to perform 1st phase of migration of a share server.""" 

526 LOG.debug( 

527 "Migration of share server with ID '%s' has been started.", 

528 src_share_server["id"]) 

529 self.storage.share_server_migration_start( 

530 context, src_share_server, dest_share_server, shares, snapshots) 

531 

532 def share_server_migration_continue(self, context, src_share_server, 

533 dest_share_server, shares, snapshots): 

534 

535 return self.storage.share_server_migration_continue( 

536 context, src_share_server, dest_share_server, shares, snapshots) 

537 

538 def share_server_migration_cancel(self, context, src_share_server, 

539 dest_share_server, shares, snapshots): 

540 """Is called to cancel a share server migration.""" 

541 self.storage.share_server_migration_cancel( 

542 context, src_share_server, dest_share_server, shares, snapshots) 

543 LOG.debug( 

544 "Migration of share server with ID '%s' has been canceled.", 

545 src_share_server["id"]) 

546 return 

547 

548 def share_server_migration_get_progress(self, context, src_share_server, 

549 dest_share_server, shares, 

550 snapshots): 

551 """Is called to get share server migration progress.""" 

552 return self.storage.share_server_migration_get_progress( 

553 context, src_share_server, dest_share_server, shares, snapshots) 

554 

555 def share_server_migration_complete(self, context, source_share_server, 

556 dest_share_server, shares, snapshots, 

557 new_network_allocations): 

558 # Removes the source shares reference from the source container 

559 source_server_id = self._get_container_name(source_share_server["id"]) 

560 for source_share in shares: 

561 self._umount_storage( 

562 source_share, source_server_id, source_share.share_id) 

563 

564 # storage removes source share 

565 self.storage.share_server_migration_complete( 

566 context, source_share_server, dest_share_server, shares, snapshots, 

567 new_network_allocations) 

568 

569 destination_server_id = self._get_container_name( 

570 dest_share_server["id"]) 

571 shares_updates = {} 

572 for destination_share in shares: 

573 share_id = destination_share.share_id 

574 new_export_locations = self._mount_storage( 

575 destination_share, destination_server_id, share_id) 

576 

577 shares_updates[destination_share['id']] = { 

578 'export_locations': new_export_locations, 

579 'pool_name': self.storage.get_share_pool_name(share_id), 

580 } 

581 

582 msg = ("Volumes move operation from server %(server)s were completed " 

583 "successfully. Share server has been moved from %(src)s to " 

584 "%(dest)s.") 

585 msg_args = { 

586 'serv': source_share_server['id'], 

587 'src': source_share_server['host'], 

588 'dest': dest_share_server['host'], 

589 } 

590 LOG.info(msg, msg_args) 

591 

592 return { 

593 'share_updates': shares_updates, 

594 } 

595 

596 def setup_security_services(self, share_server_id, security_services): 

597 """Is called to setup a security service in the share server.""" 

598 

599 for security_service in security_services: 

600 if security_service['type'].lower() != 'ldap': 

601 raise exception.ShareBackendException(_( 

602 "The container driver does not support security services " 

603 "other than LDAP.")) 

604 

605 self.security_service_helper.setup_security_service( 

606 share_server_id, security_service) 

607 

608 def _get_different_security_service_keys( 

609 self, current_security_service, new_security_service): 

610 valid_keys = ['dns_ip', 'server', 'domain', 'user', 'password', 'ou'] 

611 different_keys = [] 

612 for key, value in current_security_service.items(): 

613 if (current_security_service[key] != new_security_service[key] 613 ↛ 612line 613 didn't jump to line 612 because the condition on line 613 was always true

614 and key in valid_keys): 

615 different_keys.append(key) 

616 return different_keys 

617 

618 def _check_if_all_fields_are_updatable(self, current_security_service, 

619 new_security_service): 

620 # NOTE(carloss): We only support updating user and password at 

621 # the moment 

622 updatable_fields = ['user', 'password'] 

623 different_keys = self._get_different_security_service_keys( 

624 current_security_service, new_security_service) 

625 for key in different_keys: 

626 if key not in updatable_fields: 

627 return False 

628 return True 

629 

630 def update_share_server_security_service(self, context, share_server, 

631 network_info, 

632 share_instances, 

633 share_instance_rules, 

634 new_security_service, 

635 current_security_service=None): 

636 """Is called to update or add a sec service to a share server.""" 

637 

638 if not self.check_update_share_server_security_service( 

639 context, share_server, network_info, share_instances, 

640 share_instance_rules, new_security_service, 

641 current_security_service=current_security_service): 

642 raise exception.ManilaException(_( 

643 "The requested security service update is not supported by " 

644 "the container driver.")) 

645 

646 server_id = self._get_container_name(share_server['id']) 

647 

648 if not current_security_service: 

649 self.setup_security_services(server_id, [new_security_service]) 

650 else: 

651 self.security_service_helper.update_security_service( 

652 server_id, current_security_service, new_security_service) 

653 

654 msg = ( 

655 "The security service was successfully added to the share " 

656 "server %(server_id)s.") 

657 msg_args = { 

658 'server_id': share_server['id'], 

659 } 

660 LOG.info(msg, msg_args) 

661 

662 def check_update_share_server_security_service( 

663 self, context, share_server, network_info, share_instances, 

664 share_instance_rules, new_security_service, 

665 current_security_service=None): 

666 current_type = ( 

667 current_security_service['type'].lower() 

668 if current_security_service else '') 

669 new_type = new_security_service['type'].lower() 

670 

671 if new_type != 'ldap' or (current_type and current_type != 'ldap'): 

672 LOG.error('Currently only LDAP security services are supported ' 

673 'by the container driver.') 

674 return False 

675 

676 if not current_type: 

677 return True 

678 

679 all_fields_are_updatable = self._check_if_all_fields_are_updatable( 

680 current_security_service, new_security_service) 

681 if not all_fields_are_updatable: 

682 LOG.info( 

683 "The Container driver does not support updating " 

684 "security service parameters other than 'user' and " 

685 "'password'.") 

686 return False 

687 return True 

688 

689 def _form_share_server_update_return(self, share_server, 

690 current_network_allocations, 

691 new_network_allocations, 

692 share_instances): 

693 server_id = self._get_container_name(share_server["id"]) 

694 addresses = self.container.fetch_container_addresses(server_id, "inet") 

695 share_updates = {} 

696 subnet_allocations = {} 

697 

698 for share_instance in share_instances: 

699 export_locations = [] 

700 for address in addresses: 

701 # TODO(ecsantos): The Container driver currently only 

702 # supports CIFS. If NFS support is implemented in the 

703 # future, the path should be adjusted accordingly. 

704 export_location = { 

705 "is_admin_only": False, 

706 "path": "//%(ip_address)s/%(share_id)s" % 

707 { 

708 "ip_address": address, 

709 "share_id": share_instance["share_id"] 

710 }, 

711 "preferred": False 

712 } 

713 export_locations.append(export_location) 

714 share_updates[share_instance["id"]] = export_locations 

715 

716 for subnet in current_network_allocations["subnets"]: 

717 for network_allocation in subnet["network_allocations"]: 

718 subnet_allocations[network_allocation["id"]] = ( 

719 network_allocation["ip_address"]) 

720 

721 for network_allocation in ( 

722 new_network_allocations["network_allocations"]): 

723 subnet_allocations[network_allocation["id"]] = ( 

724 network_allocation["ip_address"]) 

725 

726 server_details = { 

727 "subnet_allocations": jsonutils.dumps(subnet_allocations) 

728 } 

729 return { 

730 "share_updates": share_updates, 

731 "server_details": server_details 

732 } 

733 

734 def check_update_share_server_network_allocations( 

735 self, context, share_server, current_network_allocations, 

736 new_share_network_subnet, security_services, share_instances, 

737 share_instances_rules): 

738 LOG.debug("Share server %(server)s can be updated with allocations " 

739 "from new subnet.", {"server": share_server["id"]}) 

740 return True 

741 

742 def update_share_server_network_allocations( 

743 self, context, share_server, current_network_allocations, 

744 new_network_allocations, security_services, share_instances, 

745 snapshots): 

746 server_id = self._get_container_name(share_server["id"]) 

747 self._setup_server_network(server_id, [new_network_allocations]) 

748 return self._form_share_server_update_return( 

749 share_server, current_network_allocations, new_network_allocations, 

750 share_instances)