Coverage for manila/tests/share/drivers/zfsonlinux/test_driver.py: 100%

1066 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 

16from unittest import mock 

17 

18import ddt 

19from oslo_config import cfg 

20 

21from manila import context 

22from manila import exception 

23from manila.share.drivers.ganesha import utils as ganesha_utils 

24from manila.share.drivers.zfsonlinux import driver as zfs_driver 

25from manila import test 

26from manila.tests import db_utils 

27 

28CONF = cfg.CONF 

29 

30 

31class FakeConfig(object): 

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

33 self.driver_handles_share_servers = False 

34 self.share_driver = 'fake_share_driver_name' 

35 self.share_backend_name = 'FAKE_BACKEND_NAME' 

36 self.zfs_share_export_ip = kwargs.get( 

37 "zfs_share_export_ip", "1.1.1.1") 

38 self.zfs_service_ip = kwargs.get("zfs_service_ip", "2.2.2.2") 

39 self.zfs_zpool_list = kwargs.get( 

40 "zfs_zpool_list", ["foo", "bar/subbar", "quuz"]) 

41 self.zfs_use_ssh = kwargs.get("zfs_use_ssh", False) 

42 self.zfs_share_export_ip = kwargs.get( 

43 "zfs_share_export_ip", "240.241.242.243") 

44 self.zfs_service_ip = kwargs.get("zfs_service_ip", "240.241.242.244") 

45 self.ssh_conn_timeout = kwargs.get("ssh_conn_timeout", 123) 

46 self.zfs_ssh_username = kwargs.get( 

47 "zfs_ssh_username", 'fake_username') 

48 self.zfs_ssh_user_password = kwargs.get( 

49 "zfs_ssh_user_password", 'fake_pass') 

50 self.zfs_ssh_private_key_path = kwargs.get( 

51 "zfs_ssh_private_key_path", '/fake/path') 

52 self.zfs_replica_snapshot_prefix = kwargs.get( 

53 "zfs_replica_snapshot_prefix", "tmp_snapshot_for_replication_") 

54 self.zfs_migration_snapshot_prefix = kwargs.get( 

55 "zfs_migration_snapshot_prefix", "tmp_snapshot_for_migration_") 

56 self.zfs_dataset_creation_options = kwargs.get( 

57 "zfs_dataset_creation_options", ["fook=foov", "bark=barv"]) 

58 self.network_config_group = kwargs.get( 

59 "network_config_group", "fake_network_config_group") 

60 self.admin_network_config_group = kwargs.get( 

61 "admin_network_config_group", "fake_admin_network_config_group") 

62 self.config_group = kwargs.get("config_group", "fake_config_group") 

63 self.reserved_share_percentage = kwargs.get( 

64 "reserved_share_percentage", 0) 

65 self.reserved_share_from_snapshot_percentage = kwargs.get( 

66 "reserved_share_from_snapshot_percentage", 0) 

67 self.reserved_share_extend_percentage = kwargs.get( 

68 "reserved_share_extend_percentage", 0) 

69 self.max_over_subscription_ratio = kwargs.get( 

70 "max_over_subscription_ratio", 15.0) 

71 self.filter_function = kwargs.get("filter_function", None) 

72 self.goodness_function = kwargs.get("goodness_function", None) 

73 

74 def safe_get(self, key): 

75 return getattr(self, key) 

76 

77 def append_config_values(self, *args, **kwargs): 

78 pass 

79 

80 

81class FakeDriverPrivateStorage(object): 

82 

83 def __init__(self): 

84 self.storage = {} 

85 

86 def update(self, entity_id, data): 

87 if entity_id not in self.storage: 

88 self.storage[entity_id] = {} 

89 self.storage[entity_id].update(data) 

90 

91 def get(self, entity_id, key): 

92 return self.storage.get(entity_id, {}).get(key) 

93 

94 def delete(self, entity_id): 

95 self.storage.pop(entity_id, None) 

96 

97 

98class FakeTempDir(object): 

99 

100 def __enter__(self, *args, **kwargs): 

101 return '/foo/path' 

102 

103 def __exit__(self, *args, **kwargs): 

104 pass 

105 

106 

107class GetBackendConfigurationTestCase(test.TestCase): 

108 

109 def test_get_backend_configuration_success(self): 

110 backend_name = 'fake_backend_name' 

111 self.mock_object( 

112 zfs_driver.CONF, 'list_all_sections', 

113 mock.Mock(return_value=['fake1', backend_name, 'fake2'])) 

114 mock_config = self.mock_object( 

115 zfs_driver.configuration, 'Configuration') 

116 

117 result = zfs_driver.get_backend_configuration(backend_name) 

118 

119 self.assertEqual(mock_config.return_value, result) 

120 mock_config.assert_called_once_with( 

121 zfs_driver.driver.share_opts, config_group=backend_name) 

122 mock_config.return_value.append_config_values.assert_has_calls([ 

123 mock.call(zfs_driver.zfsonlinux_opts), 

124 mock.call(zfs_driver.share_manager_opts), 

125 mock.call(zfs_driver.driver.ssh_opts), 

126 ]) 

127 

128 def test_get_backend_configuration_error(self): 

129 backend_name = 'fake_backend_name' 

130 self.mock_object( 

131 zfs_driver.CONF, 'list_all_sections', 

132 mock.Mock(return_value=['fake1', 'fake2'])) 

133 mock_config = self.mock_object( 

134 zfs_driver.configuration, 'Configuration') 

135 

136 self.assertRaises( 

137 exception.BadConfigurationException, 

138 zfs_driver.get_backend_configuration, 

139 backend_name, 

140 ) 

141 

142 self.assertFalse(mock_config.called) 

143 self.assertFalse(mock_config.return_value.append_config_values.called) 

144 

145 

146@ddt.ddt 

147class ZFSonLinuxShareDriverTestCase(test.TestCase): 

148 

149 def setUp(self): 

150 self.mock_object(zfs_driver.CONF, '_check_required_opts') 

151 super(ZFSonLinuxShareDriverTestCase, self).setUp() 

152 self._context = context.get_admin_context() 

153 self.ssh_executor = self.mock_object(ganesha_utils, 'SSHExecutor') 

154 self.configuration = FakeConfig() 

155 self.private_storage = FakeDriverPrivateStorage() 

156 self.driver = zfs_driver.ZFSonLinuxShareDriver( 

157 configuration=self.configuration, 

158 private_storage=self.private_storage) 

159 self.mock_object(zfs_driver.time, 'sleep') 

160 

161 def test_init(self): 

162 self.assertTrue(hasattr(self.driver, 'replica_snapshot_prefix')) 

163 self.assertEqual( 

164 self.driver.replica_snapshot_prefix, 

165 self.configuration.zfs_replica_snapshot_prefix) 

166 self.assertEqual( 

167 self.driver.backend_name, 

168 self.configuration.share_backend_name) 

169 self.assertEqual( 

170 self.driver.zpool_list, ['foo', 'bar', 'quuz']) 

171 self.assertEqual( 

172 self.driver.dataset_creation_options, 

173 self.configuration.zfs_dataset_creation_options) 

174 self.assertEqual( 

175 self.driver.share_export_ip, 

176 self.configuration.zfs_share_export_ip) 

177 self.assertEqual( 

178 self.driver.service_ip, 

179 self.configuration.zfs_service_ip) 

180 self.assertEqual( 

181 self.driver.private_storage, 

182 self.private_storage) 

183 self.assertTrue(hasattr(self.driver, '_helpers')) 

184 self.assertEqual(self.driver._helpers, {}) 

185 for attr_name in ('execute', 'execute_with_retry', 'parse_zfs_answer', 

186 'get_zpool_option', 'get_zfs_option', 'zfs'): 

187 self.assertTrue(hasattr(self.driver, attr_name)) 

188 

189 def test_init_error_with_duplicated_zpools(self): 

190 configuration = FakeConfig( 

191 zfs_zpool_list=['foo', 'bar', 'foo/quuz']) 

192 self.assertRaises( 

193 exception.BadConfigurationException, 

194 zfs_driver.ZFSonLinuxShareDriver, 

195 configuration=configuration, 

196 private_storage=self.private_storage 

197 ) 

198 

199 def test__setup_helpers(self): 

200 mock_import_class = self.mock_object( 

201 zfs_driver.importutils, 'import_class') 

202 self.configuration.zfs_share_helpers = ['FOO=foo.module.WithHelper'] 

203 

204 result = self.driver._setup_helpers() 

205 

206 self.assertIsNone(result) 

207 mock_import_class.assert_called_once_with('foo.module.WithHelper') 

208 mock_import_class.return_value.assert_called_once_with( 

209 self.configuration) 

210 self.assertEqual( 

211 self.driver._helpers, 

212 {'FOO': mock_import_class.return_value.return_value}) 

213 

214 def test__setup_helpers_error(self): 

215 self.configuration.zfs_share_helpers = [] 

216 self.assertRaises( 

217 exception.BadConfigurationException, self.driver._setup_helpers) 

218 

219 def test__get_share_helper(self): 

220 self.driver._helpers = {'FOO': 'BAR'} 

221 

222 result = self.driver._get_share_helper('FOO') 

223 

224 self.assertEqual('BAR', result) 

225 

226 @ddt.data({}, {'foo': 'bar'}) 

227 def test__get_share_helper_error(self, share_proto): 

228 self.assertRaises( 

229 exception.InvalidShare, self.driver._get_share_helper, 'NFS') 

230 

231 @ddt.data(True, False) 

232 def test_do_setup(self, use_ssh): 

233 self.mock_object(self.driver, '_setup_helpers') 

234 self.mock_object(self.driver, 'ssh_executor') 

235 self.configuration.zfs_use_ssh = use_ssh 

236 

237 self.driver.do_setup('fake_context') 

238 

239 self.driver._setup_helpers.assert_called_once_with() 

240 if use_ssh: 

241 self.assertEqual(4, self.driver.ssh_executor.call_count) 

242 else: 

243 self.assertEqual(3, self.driver.ssh_executor.call_count) 

244 

245 @ddt.data( 

246 ('foo', '127.0.0.1'), 

247 ('127.0.0.1', 'foo'), 

248 ('256.0.0.1', '127.0.0.1'), 

249 ('::1/128', '127.0.0.1'), 

250 ('127.0.0.1', '::1/128'), 

251 ) 

252 @ddt.unpack 

253 def test_do_setup_error_on_ip_addresses_configuration( 

254 self, share_export_ip, service_ip): 

255 self.mock_object(self.driver, '_setup_helpers') 

256 self.driver.share_export_ip = share_export_ip 

257 self.driver.service_ip = service_ip 

258 

259 self.assertRaises( 

260 exception.BadConfigurationException, 

261 self.driver.do_setup, 'fake_context') 

262 

263 self.driver._setup_helpers.assert_called_once_with() 

264 

265 @ddt.data([], '', None) 

266 def test_do_setup_no_zpools_configured(self, zpool_list): 

267 self.mock_object(self.driver, '_setup_helpers') 

268 self.driver.zpool_list = zpool_list 

269 

270 self.assertRaises( 

271 exception.BadConfigurationException, 

272 self.driver.do_setup, 'fake_context') 

273 

274 self.driver._setup_helpers.assert_called_once_with() 

275 

276 @ddt.data(None, '', 'foo_replication_domain') 

277 def test__get_pools_info(self, replication_domain): 

278 self.mock_object( 

279 self.driver, 'get_zpool_option', 

280 mock.Mock(side_effect=['2G', '3G', '5G', '4G'])) 

281 self.configuration.replication_domain = replication_domain 

282 self.driver.zpool_list = ['foo', 'bar'] 

283 expected = [ 

284 {'pool_name': 'foo', 'total_capacity_gb': 3.0, 

285 'free_capacity_gb': 2.0, 'reserved_percentage': 0, 

286 'reserved_snapshot_percentage': 0, 

287 'reserved_share_extend_percentage': 0, 

288 'compression': [True, False], 

289 'dedupe': [True, False], 

290 'thin_provisioning': [True], 

291 'max_over_subscription_ratio': ( 

292 self.driver.configuration.max_over_subscription_ratio), 

293 'qos': [False]}, 

294 {'pool_name': 'bar', 'total_capacity_gb': 4.0, 

295 'free_capacity_gb': 5.0, 'reserved_percentage': 0, 

296 'reserved_snapshot_percentage': 0, 

297 'reserved_share_extend_percentage': 0, 

298 'compression': [True, False], 

299 'dedupe': [True, False], 

300 'thin_provisioning': [True], 

301 'max_over_subscription_ratio': ( 

302 self.driver.configuration.max_over_subscription_ratio), 

303 'qos': [False]}, 

304 ] 

305 if replication_domain: 

306 for pool in expected: 

307 pool['replication_type'] = 'readable' 

308 

309 result = self.driver._get_pools_info() 

310 

311 self.assertEqual(expected, result) 

312 self.driver.get_zpool_option.assert_has_calls([ 

313 mock.call('foo', 'free'), 

314 mock.call('foo', 'size'), 

315 mock.call('bar', 'free'), 

316 mock.call('bar', 'size'), 

317 ]) 

318 

319 @ddt.data( 

320 ([], {'compression': [True, False], 'dedupe': [True, False]}), 

321 (['dedup=off'], {'compression': [True, False], 'dedupe': [False]}), 

322 (['dedup=on'], {'compression': [True, False], 'dedupe': [True]}), 

323 (['compression=on'], {'compression': [True], 'dedupe': [True, False]}), 

324 (['compression=off'], 

325 {'compression': [False], 'dedupe': [True, False]}), 

326 (['compression=fake'], 

327 {'compression': [True], 'dedupe': [True, False]}), 

328 (['compression=fake', 'dedup=off'], 

329 {'compression': [True], 'dedupe': [False]}), 

330 (['compression=off', 'dedup=on'], 

331 {'compression': [False], 'dedupe': [True]}), 

332 ) 

333 @ddt.unpack 

334 def test__init_common_capabilities( 

335 self, dataset_creation_options, expected_part): 

336 self.driver.dataset_creation_options = ( 

337 dataset_creation_options) 

338 expected = { 

339 'thin_provisioning': [True], 

340 'qos': [False], 

341 'max_over_subscription_ratio': ( 

342 self.driver.configuration.max_over_subscription_ratio), 

343 } 

344 expected.update(expected_part) 

345 

346 self.driver._init_common_capabilities() 

347 

348 self.assertEqual(expected, self.driver.common_capabilities) 

349 

350 @ddt.data(None, '', 'foo_replication_domain') 

351 def test__update_share_stats(self, replication_domain): 

352 self.configuration.replication_domain = replication_domain 

353 self.configuration.max_shares_per_share_server = -1 

354 self.configuration.max_share_server_size = -1 

355 self.mock_object(self.driver, '_get_pools_info') 

356 self.assertEqual({}, self.driver._stats) 

357 expected = { 

358 'driver_handles_share_servers': False, 

359 'driver_name': 'ZFS', 

360 'driver_version': '1.0', 

361 'free_capacity_gb': 'unknown', 

362 'pools': self.driver._get_pools_info.return_value, 

363 'qos': False, 

364 'replication_domain': replication_domain, 

365 'reserved_percentage': 0, 

366 'reserved_snapshot_percentage': 0, 

367 'reserved_share_extend_percentage': 0, 

368 'share_backend_name': self.driver.backend_name, 

369 'share_group_stats': {'consistent_snapshot_support': None}, 

370 'snapshot_support': True, 

371 'create_share_from_snapshot_support': True, 

372 'revert_to_snapshot_support': False, 

373 'mount_snapshot_support': False, 

374 'storage_protocol': 'NFS', 

375 'total_capacity_gb': 'unknown', 

376 'vendor_name': 'Open Source', 

377 'filter_function': None, 

378 'goodness_function': None, 

379 'mount_point_name_support': False, 

380 'ipv4_support': True, 

381 'ipv6_support': False, 

382 'security_service_update_support': False, 

383 'share_server_multiple_subnet_support': False, 

384 'network_allocation_update_support': False, 

385 'share_replicas_migration_support': False, 

386 'encryption_support': None, 

387 } 

388 if replication_domain: 

389 expected['replication_type'] = 'readable' 

390 

391 self.driver._update_share_stats() 

392 

393 self.assertEqual(expected, self.driver._stats) 

394 self.driver._get_pools_info.assert_called_once_with() 

395 

396 @ddt.data('', 'foo', 'foo-bar', 'foo_bar', 'foo-bar_quuz') 

397 def test__get_share_name(self, share_id): 

398 prefix = 'fake_prefix_' 

399 self.configuration.zfs_dataset_name_prefix = prefix 

400 self.configuration.zfs_dataset_snapshot_name_prefix = 'quuz' 

401 expected = prefix + share_id.replace('-', '_') 

402 

403 result = self.driver._get_share_name(share_id) 

404 

405 self.assertEqual(expected, result) 

406 

407 @ddt.data('', 'foo', 'foo-bar', 'foo_bar', 'foo-bar_quuz') 

408 def test__get_snapshot_name(self, snapshot_id): 

409 prefix = 'fake_prefix_' 

410 self.configuration.zfs_dataset_name_prefix = 'quuz' 

411 self.configuration.zfs_dataset_snapshot_name_prefix = prefix 

412 expected = prefix + snapshot_id.replace('-', '_') 

413 

414 result = self.driver._get_snapshot_name(snapshot_id) 

415 

416 self.assertEqual(expected, result) 

417 

418 def test__get_dataset_creation_options_not_set(self): 

419 self.driver.dataset_creation_options = [] 

420 mock_get_extra_specs_from_share = self.mock_object( 

421 zfs_driver.share_types, 

422 'get_extra_specs_from_share', 

423 mock.Mock(return_value={})) 

424 share = {'size': '5'} 

425 

426 result = self.driver._get_dataset_creation_options(share=share) 

427 

428 self.assertIsInstance(result, list) 

429 self.assertEqual(2, len(result)) 

430 for v in ('quota=5G', 'readonly=off'): 

431 self.assertIn(v, result) 

432 mock_get_extra_specs_from_share.assert_called_once_with(share) 

433 

434 @ddt.data(True, False) 

435 def test__get_dataset_creation_options(self, is_readonly): 

436 mock_get_extra_specs_from_share = self.mock_object( 

437 zfs_driver.share_types, 

438 'get_extra_specs_from_share', 

439 mock.Mock(return_value={})) 

440 self.driver.dataset_creation_options = [ 

441 'readonly=quuz', 'sharenfs=foo', 'sharesmb=bar', 'k=v', 'q=w', 

442 ] 

443 share = {'size': 5} 

444 readonly = 'readonly=%s' % ('on' if is_readonly else 'off') 

445 expected = [readonly, 'k=v', 'q=w', 'quota=5G'] 

446 

447 result = self.driver._get_dataset_creation_options( 

448 share=share, is_readonly=is_readonly) 

449 

450 self.assertEqual(sorted(expected), sorted(result)) 

451 mock_get_extra_specs_from_share.assert_called_once_with(share) 

452 

453 @ddt.data( 

454 ('<is> True', [True, False], ['dedup=off'], 'dedup=on'), 

455 ('True', [True, False], ['dedup=off'], 'dedup=on'), 

456 ('on', [True, False], ['dedup=off'], 'dedup=on'), 

457 ('yes', [True, False], ['dedup=off'], 'dedup=on'), 

458 ('1', [True, False], ['dedup=off'], 'dedup=on'), 

459 ('True', [True], [], 'dedup=on'), 

460 ('<is> False', [True, False], [], 'dedup=off'), 

461 ('False', [True, False], [], 'dedup=off'), 

462 ('False', [False], ['dedup=on'], 'dedup=off'), 

463 ('off', [False], ['dedup=on'], 'dedup=off'), 

464 ('no', [False], ['dedup=on'], 'dedup=off'), 

465 ('0', [False], ['dedup=on'], 'dedup=off'), 

466 ) 

467 @ddt.unpack 

468 def test__get_dataset_creation_options_with_updated_dedupe( 

469 self, dedupe_extra_spec, dedupe_capability, driver_options, 

470 expected): 

471 mock_get_extra_specs_from_share = self.mock_object( 

472 zfs_driver.share_types, 

473 'get_extra_specs_from_share', 

474 mock.Mock(return_value={'dedupe': dedupe_extra_spec})) 

475 self.driver.dataset_creation_options = driver_options 

476 self.driver.common_capabilities['dedupe'] = dedupe_capability 

477 share = {'size': 5} 

478 expected_options = ['quota=5G', 'readonly=off'] 

479 expected_options.append(expected) 

480 

481 result = self.driver._get_dataset_creation_options(share=share) 

482 

483 self.assertEqual(sorted(expected_options), sorted(result)) 

484 mock_get_extra_specs_from_share.assert_called_once_with(share) 

485 

486 @ddt.data( 

487 ('on', [True, False], ['compression=off'], 'compression=on'), 

488 ('on', [True], [], 'compression=on'), 

489 ('off', [False], ['compression=on'], 'compression=off'), 

490 ('off', [True, False], [], 'compression=off'), 

491 ('foo', [True, False], [], 'compression=foo'), 

492 ('bar', [True], [], 'compression=bar'), 

493 ) 

494 @ddt.unpack 

495 def test__get_dataset_creation_options_with_updated_compression( 

496 self, extra_spec, capability, driver_options, expected_option): 

497 mock_get_extra_specs_from_share = self.mock_object( 

498 zfs_driver.share_types, 

499 'get_extra_specs_from_share', 

500 mock.Mock(return_value={'zfsonlinux:compression': extra_spec})) 

501 self.driver.dataset_creation_options = driver_options 

502 self.driver.common_capabilities['compression'] = capability 

503 share = {'size': 5} 

504 expected_options = ['quota=5G', 'readonly=off'] 

505 expected_options.append(expected_option) 

506 

507 result = self.driver._get_dataset_creation_options(share=share) 

508 

509 self.assertEqual(sorted(expected_options), sorted(result)) 

510 mock_get_extra_specs_from_share.assert_called_once_with(share) 

511 

512 @ddt.data( 

513 ({'dedupe': 'fake'}, {'dedupe': [True, False]}), 

514 ({'dedupe': 'on'}, {'dedupe': [False]}), 

515 ({'dedupe': 'off'}, {'dedupe': [True]}), 

516 ({'zfsonlinux:compression': 'fake'}, {'compression': [False]}), 

517 ({'zfsonlinux:compression': 'on'}, {'compression': [False]}), 

518 ({'zfsonlinux:compression': 'off'}, {'compression': [True]}), 

519 ) 

520 @ddt.unpack 

521 def test__get_dataset_creation_options_error( 

522 self, extra_specs, common_capabilities): 

523 mock_get_extra_specs_from_share = self.mock_object( 

524 zfs_driver.share_types, 

525 'get_extra_specs_from_share', 

526 mock.Mock(return_value=extra_specs)) 

527 share = {'size': 5} 

528 self.driver.common_capabilities.update(common_capabilities) 

529 

530 self.assertRaises( 

531 exception.ZFSonLinuxException, 

532 self.driver._get_dataset_creation_options, 

533 share=share 

534 ) 

535 

536 mock_get_extra_specs_from_share.assert_called_once_with(share) 

537 

538 @ddt.data('bar/quuz', 'bar/quuz/', 'bar') 

539 def test__get_dataset_name(self, second_zpool): 

540 self.configuration.zfs_zpool_list = ['foo', second_zpool] 

541 prefix = 'fake_prefix_' 

542 self.configuration.zfs_dataset_name_prefix = prefix 

543 share = {'id': 'abc-def_ghi', 'host': 'hostname@backend_name#bar'} 

544 

545 result = self.driver._get_dataset_name(share) 

546 

547 if second_zpool[-1] == '/': 

548 second_zpool = second_zpool[0:-1] 

549 expected = '%s/%sabc_def_ghi' % (second_zpool, prefix) 

550 self.assertEqual(expected, result) 

551 

552 def test_create_share(self): 

553 mock_get_helper = self.mock_object(self.driver, '_get_share_helper') 

554 self.mock_object(self.driver, 'zfs') 

555 mock_get_extra_specs_from_share = self.mock_object( 

556 zfs_driver.share_types, 

557 'get_extra_specs_from_share', 

558 mock.Mock(return_value={})) 

559 context = 'fake_context' 

560 share = { 

561 'id': 'fake_share_id', 

562 'host': 'hostname@backend_name#bar', 

563 'share_proto': 'NFS', 

564 'size': 4, 

565 } 

566 self.configuration.zfs_dataset_name_prefix = 'some_prefix_' 

567 self.configuration.zfs_ssh_username = 'someuser' 

568 self.driver.share_export_ip = '1.1.1.1' 

569 self.driver.service_ip = '2.2.2.2' 

570 dataset_name = 'bar/subbar/some_prefix_fake_share_id' 

571 

572 result = self.driver.create_share(context, share, share_server=None) 

573 

574 self.assertEqual( 

575 mock_get_helper.return_value.create_exports.return_value, 

576 result, 

577 ) 

578 self.assertEqual( 

579 'share', 

580 self.driver.private_storage.get(share['id'], 'entity_type')) 

581 self.assertEqual( 

582 dataset_name, 

583 self.driver.private_storage.get(share['id'], 'dataset_name')) 

584 self.assertEqual( 

585 'someuser@2.2.2.2', 

586 self.driver.private_storage.get(share['id'], 'ssh_cmd')) 

587 self.assertEqual( 

588 'bar', 

589 self.driver.private_storage.get(share['id'], 'pool_name')) 

590 self.driver.zfs.assert_called_once_with( 

591 'create', '-o', 'quota=4G', '-o', 'fook=foov', '-o', 'bark=barv', 

592 '-o', 'readonly=off', 'bar/subbar/some_prefix_fake_share_id') 

593 mock_get_helper.assert_has_calls([ 

594 mock.call('NFS'), mock.call().create_exports(dataset_name) 

595 ]) 

596 mock_get_extra_specs_from_share.assert_called_once_with(share) 

597 

598 def test_create_share_with_share_server(self): 

599 self.assertRaises( 

600 exception.InvalidInput, 

601 self.driver.create_share, 

602 'fake_context', 'fake_share', share_server={'id': 'fake_server'}, 

603 ) 

604 

605 def test_delete_share(self): 

606 dataset_name = 'bar/subbar/some_prefix_fake_share_id' 

607 mock_delete = self.mock_object( 

608 self.driver, '_delete_dataset_or_snapshot_with_retry') 

609 self.mock_object(self.driver, '_get_share_helper') 

610 self.mock_object(zfs_driver.LOG, 'warning') 

611 self.mock_object( 

612 self.driver, 'zfs', mock.Mock(return_value=('a', 'b'))) 

613 snap_name = '%s@%s' % ( 

614 dataset_name, self.driver.replica_snapshot_prefix) 

615 self.mock_object( 

616 self.driver, 'parse_zfs_answer', 

617 mock.Mock( 

618 side_effect=[ 

619 [{'NAME': 'fake_dataset_name'}, {'NAME': dataset_name}], 

620 [{'NAME': 'snap_name'}, 

621 {'NAME': '%s@foo' % dataset_name}, 

622 {'NAME': snap_name}], 

623 ])) 

624 context = 'fake_context' 

625 share = { 

626 'id': 'fake_share_id', 

627 'host': 'hostname@backend_name#bar', 

628 'share_proto': 'NFS', 

629 'size': 4, 

630 } 

631 self.configuration.zfs_dataset_name_prefix = 'some_prefix_' 

632 self.configuration.zfs_ssh_username = 'someuser' 

633 self.driver.share_export_ip = '1.1.1.1' 

634 self.driver.service_ip = '2.2.2.2' 

635 self.driver.private_storage.update( 

636 share['id'], 

637 {'pool_name': 'bar', 'dataset_name': dataset_name} 

638 ) 

639 

640 self.driver.delete_share(context, share, share_server=None) 

641 

642 self.driver.zfs.assert_has_calls([ 

643 mock.call('list', '-r', 'bar'), 

644 mock.call('list', '-r', '-t', 'snapshot', 'bar'), 

645 ]) 

646 self.driver._get_share_helper.assert_has_calls([ 

647 mock.call('NFS'), mock.call().remove_exports(dataset_name)]) 

648 self.driver.parse_zfs_answer.assert_has_calls([ 

649 mock.call('a'), mock.call('a')]) 

650 mock_delete.assert_has_calls([ 

651 mock.call(snap_name), 

652 mock.call(dataset_name), 

653 ]) 

654 self.assertEqual(0, zfs_driver.LOG.warning.call_count) 

655 

656 def test_delete_share_absent(self): 

657 dataset_name = 'bar/subbar/some_prefix_fake_share_id' 

658 mock_delete = self.mock_object( 

659 self.driver, '_delete_dataset_or_snapshot_with_retry') 

660 self.mock_object(self.driver, '_get_share_helper') 

661 self.mock_object(zfs_driver.LOG, 'warning') 

662 self.mock_object( 

663 self.driver, 'zfs', mock.Mock(return_value=('a', 'b'))) 

664 snap_name = '%s@%s' % ( 

665 dataset_name, self.driver.replica_snapshot_prefix) 

666 self.mock_object( 

667 self.driver, 'parse_zfs_answer', 

668 mock.Mock(side_effect=[[], [{'NAME': snap_name}]])) 

669 context = 'fake_context' 

670 share = { 

671 'id': 'fake_share_id', 

672 'host': 'hostname@backend_name#bar', 

673 'size': 4, 

674 } 

675 self.configuration.zfs_dataset_name_prefix = 'some_prefix_' 

676 self.configuration.zfs_ssh_username = 'someuser' 

677 self.driver.share_export_ip = '1.1.1.1' 

678 self.driver.service_ip = '2.2.2.2' 

679 self.driver.private_storage.update(share['id'], {'pool_name': 'bar'}) 

680 

681 self.driver.delete_share(context, share, share_server=None) 

682 

683 self.assertEqual(0, self.driver._get_share_helper.call_count) 

684 self.assertEqual(0, mock_delete.call_count) 

685 self.driver.zfs.assert_called_once_with('list', '-r', 'bar') 

686 self.driver.parse_zfs_answer.assert_called_once_with('a') 

687 zfs_driver.LOG.warning.assert_called_once_with( 

688 mock.ANY, {'id': share['id'], 'name': dataset_name}) 

689 

690 def test_delete_share_with_share_server(self): 

691 self.assertRaises( 

692 exception.InvalidInput, 

693 self.driver.delete_share, 

694 'fake_context', 'fake_share', share_server={'id': 'fake_server'}, 

695 ) 

696 

697 def test_create_snapshot(self): 

698 self.configuration.zfs_dataset_snapshot_name_prefix = 'prefx_' 

699 self.mock_object(self.driver, 'zfs') 

700 snapshot = { 

701 'id': 'fake_snapshot_instance_id', 

702 'snapshot_id': 'fake_snapshot_id', 

703 'host': 'hostname@backend_name#bar', 

704 'size': 4, 

705 'share_instance_id': 'fake_share_id' 

706 } 

707 snapshot_name = 'foo_data_set_name@prefx_%s' % snapshot['id'] 

708 self.driver.private_storage.update( 

709 snapshot['share_instance_id'], 

710 {'dataset_name': 'foo_data_set_name'}) 

711 

712 result = self.driver.create_snapshot('fake_context', snapshot) 

713 

714 self.driver.zfs.assert_called_once_with( 

715 'snapshot', snapshot_name) 

716 self.assertEqual( 

717 snapshot_name.split('@')[-1], 

718 self.driver.private_storage.get( 

719 snapshot['snapshot_id'], 'snapshot_tag')) 

720 self.assertEqual({"provider_location": snapshot_name}, result) 

721 

722 def test_delete_snapshot(self): 

723 snapshot = { 

724 'id': 'fake_snapshot_instance_id', 

725 'snapshot_id': 'fake_snapshot_id', 

726 'host': 'hostname@backend_name#bar', 

727 'size': 4, 

728 'share_instance_id': 'fake_share_id', 

729 } 

730 dataset_name = 'foo_zpool/bar_dataset_name' 

731 snap_tag = 'prefix_%s' % snapshot['id'] 

732 snap_name = '%(dataset)s@%(tag)s' % { 

733 'dataset': dataset_name, 'tag': snap_tag} 

734 mock_delete = self.mock_object( 

735 self.driver, '_delete_dataset_or_snapshot_with_retry') 

736 self.mock_object(zfs_driver.LOG, 'warning') 

737 self.mock_object( 

738 self.driver, 'zfs', mock.Mock(return_value=('a', 'b'))) 

739 self.mock_object( 

740 self.driver, 'parse_zfs_answer', 

741 mock.Mock(side_effect=[ 

742 [{'NAME': 'some_other_dataset@snapshot_name'}, 

743 {'NAME': snap_name}], 

744 []])) 

745 context = 'fake_context' 

746 self.driver.private_storage.update( 

747 snapshot['id'], {'snapshot_name': snap_name}) 

748 self.driver.private_storage.update( 

749 snapshot['snapshot_id'], {'snapshot_tag': snap_tag}) 

750 self.driver.private_storage.update( 

751 snapshot['share_instance_id'], {'dataset_name': dataset_name}) 

752 

753 self.assertEqual( 

754 snap_tag, 

755 self.driver.private_storage.get( 

756 snapshot['snapshot_id'], 'snapshot_tag')) 

757 

758 self.driver.delete_snapshot(context, snapshot, share_server=None) 

759 

760 self.assertIsNone( 

761 self.driver.private_storage.get( 

762 snapshot['snapshot_id'], 'snapshot_tag')) 

763 

764 self.assertEqual(0, zfs_driver.LOG.warning.call_count) 

765 self.driver.zfs.assert_called_once_with( 

766 'list', '-r', '-t', 'snapshot', snap_name) 

767 self.driver.parse_zfs_answer.assert_called_once_with('a') 

768 mock_delete.assert_called_once_with(snap_name) 

769 

770 def test_delete_snapshot_absent(self): 

771 snapshot = { 

772 'id': 'fake_snapshot_instance_id', 

773 'snapshot_id': 'fake_snapshot_id', 

774 'host': 'hostname@backend_name#bar', 

775 'size': 4, 

776 'share_instance_id': 'fake_share_id', 

777 } 

778 dataset_name = 'foo_zpool/bar_dataset_name' 

779 snap_tag = 'prefix_%s' % snapshot['id'] 

780 snap_name = '%(dataset)s@%(tag)s' % { 

781 'dataset': dataset_name, 'tag': snap_tag} 

782 mock_delete = self.mock_object( 

783 self.driver, '_delete_dataset_or_snapshot_with_retry') 

784 self.mock_object(zfs_driver.LOG, 'warning') 

785 self.mock_object( 

786 self.driver, 'zfs', mock.Mock(return_value=('a', 'b'))) 

787 self.mock_object( 

788 self.driver, 'parse_zfs_answer', 

789 mock.Mock(side_effect=[[], [{'NAME': snap_name}]])) 

790 context = 'fake_context' 

791 self.driver.private_storage.update( 

792 snapshot['id'], {'snapshot_name': snap_name}) 

793 self.driver.private_storage.update( 

794 snapshot['snapshot_id'], {'snapshot_tag': snap_tag}) 

795 self.driver.private_storage.update( 

796 snapshot['share_instance_id'], {'dataset_name': dataset_name}) 

797 

798 self.driver.delete_snapshot(context, snapshot, share_server=None) 

799 

800 self.assertEqual(0, mock_delete.call_count) 

801 self.driver.zfs.assert_called_once_with( 

802 'list', '-r', '-t', 'snapshot', snap_name) 

803 self.driver.parse_zfs_answer.assert_called_once_with('a') 

804 zfs_driver.LOG.warning.assert_called_once_with( 

805 mock.ANY, {'id': snapshot['id'], 'name': snap_name}) 

806 

807 def test_delete_snapshot_with_share_server(self): 

808 self.assertRaises( 

809 exception.InvalidInput, 

810 self.driver.delete_snapshot, 

811 'fake_context', 'fake_snapshot', 

812 share_server={'id': 'fake_server'}, 

813 ) 

814 

815 @ddt.data({'src_backend_name': 'backend_a', 'src_user': 'someuser', 

816 'src_ip': '2.2.2.2'}, 

817 {'src_backend_name': 'backend_b', 'src_user': 'someuser2', 

818 'src_ip': '3.3.3.3'}) 

819 @ddt.unpack 

820 def test_create_share_from_snapshot(self, src_backend_name, src_user, 

821 src_ip): 

822 mock_get_helper = self.mock_object(self.driver, '_get_share_helper') 

823 self.mock_object(self.driver, 'zfs') 

824 self.mock_object(self.driver, 'execute') 

825 mock_get_extra_specs_from_share = self.mock_object( 

826 zfs_driver.share_types, 

827 'get_extra_specs_from_share', 

828 mock.Mock(return_value={})) 

829 context = 'fake_context' 

830 dst_backend_name = 'backend_a' 

831 parent_share = db_utils.create_share_without_instance( 

832 id='fake_share_id_1', 

833 size=4 

834 ) 

835 parent_instance = db_utils.create_share_instance( 

836 id='fake_parent_instance', 

837 share_id=parent_share['id'], 

838 host='hostname@%s#bar' % src_backend_name 

839 ) 

840 share = db_utils.create_share( 

841 id='fake_share_id_2', 

842 host='hostname@%s#bar' % dst_backend_name, 

843 size=4 

844 ) 

845 snapshot = db_utils.create_snapshot( 

846 id='fake_snap_id_1', 

847 share_id='fake_share_id_1' 

848 ) 

849 snap_instance = db_utils.create_snapshot_instance( 

850 id='fake_snap_instance', 

851 snapshot_id=snapshot['id'], 

852 share_instance_id=parent_instance['id'] 

853 ) 

854 

855 dataset_name = 'bar/subbar/some_prefix_%s' % share['id'] 

856 snap_tag = 'prefix_%s' % snapshot['id'] 

857 snap_name = '%(dataset)s@%(tag)s' % { 

858 'dataset': dataset_name, 'tag': snap_tag} 

859 self.configuration.zfs_dataset_name_prefix = 'some_prefix_' 

860 self.configuration.zfs_ssh_username = 'someuser' 

861 self.driver.share_export_ip = '1.1.1.1' 

862 self.driver.service_ip = '2.2.2.2' 

863 self.driver.private_storage.update( 

864 snap_instance['id'], {'snapshot_name': snap_name}) 

865 self.driver.private_storage.update( 

866 snap_instance['snapshot_id'], {'snapshot_tag': snap_tag}) 

867 self.driver.private_storage.update( 

868 snap_instance['share_instance_id'], 

869 {'dataset_name': dataset_name}) 

870 

871 self.mock_object( 

872 zfs_driver, 'get_backend_configuration', 

873 mock.Mock(return_value=type( 

874 'FakeConfig', (object,), { 

875 'zfs_ssh_username': src_user, 

876 'zfs_service_ip': src_ip 

877 }))) 

878 

879 result = self.driver.create_share_from_snapshot( 

880 context, share, snap_instance, share_server=None) 

881 

882 self.assertEqual( 

883 mock_get_helper.return_value.create_exports.return_value, 

884 result, 

885 ) 

886 

887 dst_ssh_host = (self.configuration.zfs_ssh_username + 

888 '@' + self.driver.service_ip) 

889 src_ssh_host = src_user + '@' + src_ip 

890 self.assertEqual( 

891 'share', 

892 self.driver.private_storage.get(share['id'], 'entity_type')) 

893 self.assertEqual( 

894 dataset_name, 

895 self.driver.private_storage.get( 

896 snap_instance['share_instance_id'], 'dataset_name')) 

897 self.assertEqual( 

898 dst_ssh_host, 

899 self.driver.private_storage.get(share['id'], 'ssh_cmd')) 

900 self.assertEqual( 

901 'bar', 

902 self.driver.private_storage.get(share['id'], 'pool_name')) 

903 

904 self.driver.execute.assert_has_calls([ 

905 mock.call( 

906 'ssh', src_ssh_host, 

907 'sudo', 'zfs', 'send', '-vD', snap_name, '|', 

908 'ssh', dst_ssh_host, 

909 'sudo', 'zfs', 'receive', '-v', 

910 '%s' % dataset_name), 

911 mock.call( 

912 'sudo', 'zfs', 'destroy', 

913 '%s@%s' % (dataset_name, snap_tag)), 

914 ]) 

915 

916 self.driver.zfs.assert_has_calls([ 

917 mock.call('set', opt, '%s' % dataset_name) 

918 for opt in ('quota=4G', 'bark=barv', 'readonly=off', 'fook=foov') 

919 ], any_order=True) 

920 mock_get_helper.assert_has_calls([ 

921 mock.call('NFS'), mock.call().create_exports(dataset_name) 

922 ]) 

923 mock_get_extra_specs_from_share.assert_called_once_with(share) 

924 

925 def test_create_share_from_snapshot_with_share_server(self): 

926 self.assertRaises( 

927 exception.InvalidInput, 

928 self.driver.create_share_from_snapshot, 

929 'fake_context', 'fake_share', 'fake_snapshot', 

930 share_server={'id': 'fake_server'}, 

931 ) 

932 

933 def test_get_pool(self): 

934 share = {'host': 'hostname@backend_name#bar'} 

935 

936 result = self.driver.get_pool(share) 

937 

938 self.assertEqual('bar', result) 

939 

940 @ddt.data('on', 'off', 'rw=1.1.1.1') 

941 def test_ensure_share(self, get_zfs_option_answer): 

942 share = { 

943 'id': 'fake_share_id', 

944 'host': 'hostname@backend_name#bar', 

945 'share_proto': 'NFS', 

946 } 

947 dataset_name = 'foo_zpool/foo_fs' 

948 self.mock_object( 

949 self.driver, '_get_dataset_name', 

950 mock.Mock(return_value=dataset_name)) 

951 self.mock_object( 

952 self.driver, 'get_zfs_option', 

953 mock.Mock(return_value=get_zfs_option_answer)) 

954 mock_helper = self.mock_object(self.driver, '_get_share_helper') 

955 self.mock_object( 

956 self.driver, 'zfs', mock.Mock(return_value=('a', 'b'))) 

957 self.mock_object( 

958 self.driver, 'parse_zfs_answer', 

959 mock.Mock(side_effect=[[{'NAME': 'fake1'}, 

960 {'NAME': dataset_name}, 

961 {'NAME': 'fake2'}]] * 2)) 

962 

963 for s in ('1', '2'): 

964 self.driver.zfs.reset_mock() 

965 self.driver.get_zfs_option.reset_mock() 

966 mock_helper.reset_mock() 

967 self.driver.parse_zfs_answer.reset_mock() 

968 self.driver._get_dataset_name.reset_mock() 

969 

970 self.driver.share_export_ip = '1.1.1.%s' % s 

971 self.driver.service_ip = '2.2.2.%s' % s 

972 self.configuration.zfs_ssh_username = 'user%s' % s 

973 

974 result = self.driver.ensure_share('fake_context', share) 

975 

976 self.assertEqual( 

977 'user%(s)s@2.2.2.%(s)s' % {'s': s}, 

978 self.driver.private_storage.get(share['id'], 'ssh_cmd')) 

979 self.driver.get_zfs_option.assert_called_once_with( 

980 dataset_name, 'sharenfs') 

981 mock_helper.assert_called_once_with( 

982 share['share_proto']) 

983 mock_helper.return_value.get_exports.assert_called_once_with( 

984 dataset_name) 

985 expected_calls = [mock.call('list', '-r', 'bar')] 

986 if get_zfs_option_answer != 'off': 

987 expected_calls.append(mock.call('share', dataset_name)) 

988 self.driver.zfs.assert_has_calls(expected_calls) 

989 self.driver.parse_zfs_answer.assert_called_once_with('a') 

990 self.driver._get_dataset_name.assert_called_once_with(share) 

991 self.assertEqual( 

992 mock_helper.return_value.get_exports.return_value, 

993 result, 

994 ) 

995 

996 def test_ensure_share_absent(self): 

997 share = {'id': 'fake_share_id', 'host': 'hostname@backend_name#bar'} 

998 dataset_name = 'foo_zpool/foo_fs' 

999 self.driver.private_storage.update( 

1000 share['id'], {'dataset_name': dataset_name}) 

1001 self.mock_object(self.driver, 'get_zfs_option') 

1002 self.mock_object(self.driver, '_get_share_helper') 

1003 self.mock_object( 

1004 self.driver, 'zfs', mock.Mock(return_value=('a', 'b'))) 

1005 self.mock_object( 

1006 self.driver, 'parse_zfs_answer', 

1007 mock.Mock(side_effect=[[], [{'NAME': dataset_name}]])) 

1008 

1009 self.assertRaises( 

1010 exception.ShareResourceNotFound, 

1011 self.driver.ensure_share, 

1012 'fake_context', share, 

1013 ) 

1014 

1015 self.assertEqual(0, self.driver.get_zfs_option.call_count) 

1016 self.assertEqual(0, self.driver._get_share_helper.call_count) 

1017 self.driver.zfs.assert_called_once_with('list', '-r', 'bar') 

1018 self.driver.parse_zfs_answer.assert_called_once_with('a') 

1019 

1020 def test_ensure_share_with_share_server(self): 

1021 self.assertRaises( 

1022 exception.InvalidInput, 

1023 self.driver.ensure_share, 

1024 'fake_context', 'fake_share', share_server={'id': 'fake_server'}, 

1025 ) 

1026 

1027 def test_get_network_allocations_number(self): 

1028 self.assertEqual(0, self.driver.get_network_allocations_number()) 

1029 

1030 def test_extend_share(self): 

1031 dataset_name = 'foo_zpool/foo_fs' 

1032 self.mock_object( 

1033 self.driver, '_get_dataset_name', 

1034 mock.Mock(return_value=dataset_name)) 

1035 self.mock_object(self.driver, 'zfs') 

1036 

1037 self.driver.extend_share('fake_share', 5) 

1038 

1039 self.driver._get_dataset_name.assert_called_once_with('fake_share') 

1040 self.driver.zfs.assert_called_once_with( 

1041 'set', 'quota=5G', dataset_name) 

1042 

1043 def test_extend_share_with_share_server(self): 

1044 self.assertRaises( 

1045 exception.InvalidInput, 

1046 self.driver.extend_share, 

1047 'fake_context', 'fake_share', 5, 

1048 share_server={'id': 'fake_server'}, 

1049 ) 

1050 

1051 def test_shrink_share(self): 

1052 dataset_name = 'foo_zpool/foo_fs' 

1053 self.mock_object( 

1054 self.driver, '_get_dataset_name', 

1055 mock.Mock(return_value=dataset_name)) 

1056 self.mock_object(self.driver, 'zfs') 

1057 self.mock_object( 

1058 self.driver, 'get_zfs_option', mock.Mock(return_value='4G')) 

1059 share = {'id': 'fake_share_id'} 

1060 

1061 self.driver.shrink_share(share, 5) 

1062 

1063 self.driver._get_dataset_name.assert_called_once_with(share) 

1064 self.driver.get_zfs_option.assert_called_once_with( 

1065 dataset_name, 'used') 

1066 self.driver.zfs.assert_called_once_with( 

1067 'set', 'quota=5G', dataset_name) 

1068 

1069 def test_shrink_share_data_loss(self): 

1070 dataset_name = 'foo_zpool/foo_fs' 

1071 self.mock_object( 

1072 self.driver, '_get_dataset_name', 

1073 mock.Mock(return_value=dataset_name)) 

1074 self.mock_object(self.driver, 'zfs') 

1075 self.mock_object( 

1076 self.driver, 'get_zfs_option', mock.Mock(return_value='6G')) 

1077 share = {'id': 'fake_share_id'} 

1078 

1079 self.assertRaises( 

1080 exception.ShareShrinkingPossibleDataLoss, 

1081 self.driver.shrink_share, share, 5) 

1082 

1083 self.driver._get_dataset_name.assert_called_once_with(share) 

1084 self.driver.get_zfs_option.assert_called_once_with( 

1085 dataset_name, 'used') 

1086 self.assertEqual(0, self.driver.zfs.call_count) 

1087 

1088 def test_shrink_share_with_share_server(self): 

1089 self.assertRaises( 

1090 exception.InvalidInput, 

1091 self.driver.shrink_share, 

1092 'fake_context', 'fake_share', 5, 

1093 share_server={'id': 'fake_server'}, 

1094 ) 

1095 

1096 def test__get_replication_snapshot_prefix(self): 

1097 replica = {'id': 'foo-_bar-_id'} 

1098 self.driver.replica_snapshot_prefix = 'PrEfIx' 

1099 

1100 result = self.driver._get_replication_snapshot_prefix(replica) 

1101 

1102 self.assertEqual('PrEfIx_foo__bar__id', result) 

1103 

1104 def test__get_replication_snapshot_tag(self): 

1105 replica = {'id': 'foo-_bar-_id'} 

1106 self.driver.replica_snapshot_prefix = 'PrEfIx' 

1107 mock_utcnow = self.mock_object(zfs_driver.timeutils, 'utcnow') 

1108 

1109 result = self.driver._get_replication_snapshot_tag(replica) 

1110 

1111 self.assertEqual( 

1112 ('PrEfIx_foo__bar__id_time_' 

1113 '%s' % mock_utcnow.return_value.isoformat.return_value), 

1114 result) 

1115 mock_utcnow.assert_called_once_with() 

1116 mock_utcnow.return_value.isoformat.assert_called_once_with() 

1117 

1118 def test__get_active_replica(self): 

1119 replica_list = [ 

1120 {'replica_state': zfs_driver.constants.REPLICA_STATE_IN_SYNC, 

1121 'id': '1'}, 

1122 {'replica_state': zfs_driver.constants.REPLICA_STATE_ACTIVE, 

1123 'id': '2'}, 

1124 {'replica_state': zfs_driver.constants.REPLICA_STATE_OUT_OF_SYNC, 

1125 'id': '3'}, 

1126 ] 

1127 

1128 result = self.driver._get_active_replica(replica_list) 

1129 

1130 self.assertEqual(replica_list[1], result) 

1131 

1132 def test__get_active_replica_not_found(self): 

1133 replica_list = [ 

1134 {'replica_state': zfs_driver.constants.REPLICA_STATE_IN_SYNC, 

1135 'id': '1'}, 

1136 {'replica_state': zfs_driver.constants.REPLICA_STATE_OUT_OF_SYNC, 

1137 'id': '3'}, 

1138 ] 

1139 

1140 self.assertRaises( 

1141 exception.ReplicationException, 

1142 self.driver._get_active_replica, 

1143 replica_list, 

1144 ) 

1145 

1146 def test_update_access(self): 

1147 self.mock_object(self.driver, '_get_dataset_name') 

1148 mock_helper = self.mock_object(self.driver, '_get_share_helper') 

1149 mock_shell_executor = self.mock_object( 

1150 self.driver, '_get_shell_executor_by_host') 

1151 share = { 

1152 'share_proto': 'NFS', 

1153 'host': 'foo_host@bar_backend@quuz_pool', 

1154 } 

1155 

1156 result = self.driver.update_access( 

1157 'fake_context', share, [1], [2], [3], []) 

1158 

1159 self.driver._get_dataset_name.assert_called_once_with(share) 

1160 mock_shell_executor.assert_called_once_with(share['host']) 

1161 self.assertEqual( 

1162 mock_helper.return_value.update_access.return_value, 

1163 result, 

1164 ) 

1165 

1166 def test_update_access_with_share_server(self): 

1167 self.assertRaises( 

1168 exception.InvalidInput, 

1169 self.driver.update_access, 

1170 'fake_context', 'fake_share', [], [], [], 

1171 share_server={'id': 'fake_server'}, 

1172 ) 

1173 

1174 @ddt.data( 

1175 ({}, True), 

1176 ({"size": 5}, True), 

1177 ({"size": 5, "foo": "bar"}, False), 

1178 ({"size": "5", "foo": "bar"}, True), 

1179 ) 

1180 @ddt.unpack 

1181 def test_manage_share_success_expected(self, driver_options, mount_exists): 

1182 old_dataset_name = "foopool/path/to/old/dataset/name" 

1183 new_dataset_name = "foopool/path/to/new/dataset/name" 

1184 share = { 

1185 "id": "fake_share_instance_id", 

1186 "share_id": "fake_share_id", 

1187 "export_locations": [{"path": "1.1.1.1:/%s" % old_dataset_name}], 

1188 "host": "foobackend@foohost#foopool", 

1189 "share_proto": "NFS", 

1190 } 

1191 

1192 mock_get_extra_specs_from_share = self.mock_object( 

1193 zfs_driver.share_types, 

1194 'get_extra_specs_from_share', 

1195 mock.Mock(return_value={})) 

1196 mock__get_dataset_name = self.mock_object( 

1197 self.driver, "_get_dataset_name", 

1198 mock.Mock(return_value=new_dataset_name)) 

1199 mock_helper = self.mock_object(self.driver, "_get_share_helper") 

1200 mock_zfs = self.mock_object( 

1201 self.driver, "zfs", 

1202 mock.Mock(return_value=("fake_out", "fake_error"))) 

1203 mock_zfs_with_retry = self.mock_object(self.driver, "zfs_with_retry") 

1204 

1205 mock_execute_side_effects = [ 

1206 ("%s " % old_dataset_name, "fake_err") 

1207 if mount_exists else ("foo", "bar") 

1208 ] * 3 

1209 if mount_exists: 

1210 # After three retries, assume the mount goes away 

1211 mock_execute_side_effects.append((("foo", "bar"))) 

1212 mock_execute = self.mock_object( 

1213 self.driver, "execute", 

1214 mock.Mock(side_effect=iter(mock_execute_side_effects))) 

1215 

1216 mock_parse_zfs_answer = self.mock_object( 

1217 self.driver, 

1218 "parse_zfs_answer", 

1219 mock.Mock(return_value=[ 

1220 {"NAME": "some_other_dataset_1"}, 

1221 {"NAME": old_dataset_name}, 

1222 {"NAME": "some_other_dataset_2"}, 

1223 ])) 

1224 mock_get_zfs_option = self.mock_object( 

1225 self.driver, 'get_zfs_option', mock.Mock(return_value="4G")) 

1226 

1227 result = self.driver.manage_existing(share, driver_options) 

1228 

1229 self.assertTrue(mock_helper.return_value.get_exports.called) 

1230 self.assertTrue(mock_zfs_with_retry.called) 

1231 self.assertEqual(2, len(result)) 

1232 self.assertIn("size", result) 

1233 self.assertIn("export_locations", result) 

1234 self.assertEqual(5, result["size"]) 

1235 self.assertEqual( 

1236 mock_helper.return_value.get_exports.return_value, 

1237 result["export_locations"]) 

1238 mock_execute.assert_called_with("sudo", "mount") 

1239 if mount_exists: 

1240 self.assertEqual(4, mock_execute.call_count) 

1241 else: 

1242 self.assertEqual(1, mock_execute.call_count) 

1243 mock_parse_zfs_answer.assert_called_once_with(mock_zfs.return_value[0]) 

1244 if driver_options.get("size"): 

1245 self.assertFalse(mock_get_zfs_option.called) 

1246 else: 

1247 mock_get_zfs_option.assert_called_once_with( 

1248 old_dataset_name, "used") 

1249 mock__get_dataset_name.assert_called_once_with(share) 

1250 mock_get_extra_specs_from_share.assert_called_once_with(share) 

1251 

1252 def test_manage_share_wrong_pool(self): 

1253 old_dataset_name = "foopool/path/to/old/dataset/name" 

1254 new_dataset_name = "foopool/path/to/new/dataset/name" 

1255 share = { 

1256 "id": "fake_share_instance_id", 

1257 "share_id": "fake_share_id", 

1258 "export_locations": [{"path": "1.1.1.1:/%s" % old_dataset_name}], 

1259 "host": "foobackend@foohost#barpool", 

1260 "share_proto": "NFS", 

1261 } 

1262 

1263 mock_get_extra_specs_from_share = self.mock_object( 

1264 zfs_driver.share_types, 

1265 'get_extra_specs_from_share', 

1266 mock.Mock(return_value={})) 

1267 mock__get_dataset_name = self.mock_object( 

1268 self.driver, "_get_dataset_name", 

1269 mock.Mock(return_value=new_dataset_name)) 

1270 mock_get_zfs_option = self.mock_object( 

1271 self.driver, 'get_zfs_option', mock.Mock(return_value="4G")) 

1272 

1273 self.assertRaises( 

1274 exception.ZFSonLinuxException, 

1275 self.driver.manage_existing, 

1276 share, {} 

1277 ) 

1278 

1279 mock__get_dataset_name.assert_called_once_with(share) 

1280 mock_get_zfs_option.assert_called_once_with(old_dataset_name, "used") 

1281 mock_get_extra_specs_from_share.assert_called_once_with(share) 

1282 

1283 def test_manage_share_dataset_not_found(self): 

1284 old_dataset_name = "foopool/path/to/old/dataset/name" 

1285 new_dataset_name = "foopool/path/to/new/dataset/name" 

1286 share = { 

1287 "id": "fake_share_instance_id", 

1288 "share_id": "fake_share_id", 

1289 "export_locations": [{"path": "1.1.1.1:/%s" % old_dataset_name}], 

1290 "host": "foobackend@foohost#foopool", 

1291 "share_proto": "NFS", 

1292 } 

1293 

1294 mock_get_extra_specs_from_share = self.mock_object( 

1295 zfs_driver.share_types, 

1296 'get_extra_specs_from_share', 

1297 mock.Mock(return_value={})) 

1298 mock__get_dataset_name = self.mock_object( 

1299 self.driver, "_get_dataset_name", 

1300 mock.Mock(return_value=new_dataset_name)) 

1301 mock_get_zfs_option = self.mock_object( 

1302 self.driver, 'get_zfs_option', mock.Mock(return_value="4G")) 

1303 mock_zfs = self.mock_object( 

1304 self.driver, "zfs", 

1305 mock.Mock(return_value=("fake_out", "fake_error"))) 

1306 mock_parse_zfs_answer = self.mock_object( 

1307 self.driver, 

1308 "parse_zfs_answer", 

1309 mock.Mock(return_value=[{"NAME": "some_other_dataset_1"}])) 

1310 

1311 self.assertRaises( 

1312 exception.ZFSonLinuxException, 

1313 self.driver.manage_existing, 

1314 share, {} 

1315 ) 

1316 

1317 mock__get_dataset_name.assert_called_once_with(share) 

1318 mock_get_zfs_option.assert_called_once_with(old_dataset_name, "used") 

1319 mock_zfs.assert_called_once_with( 

1320 "list", "-r", old_dataset_name.split("/")[0]) 

1321 mock_parse_zfs_answer.assert_called_once_with(mock_zfs.return_value[0]) 

1322 mock_get_extra_specs_from_share.assert_called_once_with(share) 

1323 

1324 def test_manage_unmount_exception(self): 

1325 old_ds_name = "foopool/path/to/old/dataset/name" 

1326 new_ds_name = "foopool/path/to/new/dataset/name" 

1327 share = { 

1328 "id": "fake_share_instance_id", 

1329 "share_id": "fake_share_id", 

1330 "export_locations": [{"path": "1.1.1.1:/%s" % old_ds_name}], 

1331 "host": "foobackend@foohost#foopool", 

1332 "share_proto": "NFS", 

1333 } 

1334 

1335 mock_get_extra_specs_from_share = self.mock_object( 

1336 zfs_driver.share_types, 

1337 'get_extra_specs_from_share', 

1338 mock.Mock(return_value={})) 

1339 mock__get_dataset_name = self.mock_object( 

1340 self.driver, "_get_dataset_name", 

1341 mock.Mock(return_value=new_ds_name)) 

1342 mock_helper = self.mock_object(self.driver, "_get_share_helper") 

1343 mock_zfs = self.mock_object( 

1344 self.driver, "zfs", 

1345 mock.Mock(return_value=("fake_out", "fake_error"))) 

1346 mock_zfs_with_retry = self.mock_object(self.driver, "zfs_with_retry") 

1347 

1348 # 10 Retries, would mean 20 calls to check the mount still exists 

1349 mock_execute_side_effects = [("%s " % old_ds_name, "fake_err")] * 21 

1350 mock_execute = self.mock_object( 

1351 self.driver, "execute", 

1352 mock.Mock(side_effect=mock_execute_side_effects)) 

1353 

1354 mock_parse_zfs_answer = self.mock_object( 

1355 self.driver, 

1356 "parse_zfs_answer", 

1357 mock.Mock(return_value=[ 

1358 {"NAME": "some_other_dataset_1"}, 

1359 {"NAME": old_ds_name}, 

1360 {"NAME": "some_other_dataset_2"}, 

1361 ])) 

1362 mock_get_zfs_option = self.mock_object( 

1363 self.driver, 'get_zfs_option', mock.Mock(return_value="4G")) 

1364 

1365 self.assertRaises(exception.ZFSonLinuxException, 

1366 self.driver.manage_existing, 

1367 share, {'size': 10}) 

1368 

1369 self.assertFalse(mock_helper.return_value.get_exports.called) 

1370 mock_zfs_with_retry.assert_called_with("umount", "-f", old_ds_name) 

1371 mock_execute.assert_called_with("sudo", "mount") 

1372 self.assertEqual(10, mock_zfs_with_retry.call_count) 

1373 self.assertEqual(20, mock_execute.call_count) 

1374 mock_parse_zfs_answer.assert_called_once_with(mock_zfs.return_value[0]) 

1375 self.assertFalse(mock_get_zfs_option.called) 

1376 mock__get_dataset_name.assert_called_once_with(share) 

1377 mock_get_extra_specs_from_share.assert_called_once_with(share) 

1378 

1379 def test_unmanage(self): 

1380 share = {'id': 'fake_share_id'} 

1381 self.mock_object(self.driver.private_storage, 'delete') 

1382 

1383 self.driver.unmanage(share) 

1384 

1385 self.driver.private_storage.delete.assert_called_once_with(share['id']) 

1386 

1387 @ddt.data( 

1388 {}, 

1389 {"size": 5}, 

1390 {"size": "5"}, 

1391 ) 

1392 def test_manage_existing_snapshot(self, driver_options): 

1393 dataset_name = "path/to/dataset" 

1394 old_provider_location = dataset_name + "@original_snapshot_tag" 

1395 snapshot_instance = { 

1396 "id": "fake_snapshot_instance_id", 

1397 "share_instance_id": "fake_share_instance_id", 

1398 "snapshot_id": "fake_snapshot_id", 

1399 "provider_location": old_provider_location, 

1400 } 

1401 new_snapshot_tag = "fake_new_snapshot_tag" 

1402 new_provider_location = ( 

1403 old_provider_location.split("@")[0] + "@" + new_snapshot_tag) 

1404 

1405 self.mock_object(self.driver, "zfs") 

1406 self.mock_object( 

1407 self.driver, "get_zfs_option", mock.Mock(return_value="5G")) 

1408 self.mock_object( 

1409 self.driver, 

1410 '_get_snapshot_name', 

1411 mock.Mock(return_value=new_snapshot_tag)) 

1412 self.driver.private_storage.update( 

1413 snapshot_instance["share_instance_id"], 

1414 {"dataset_name": dataset_name}) 

1415 

1416 result = self.driver.manage_existing_snapshot( 

1417 snapshot_instance, driver_options) 

1418 

1419 expected_result = { 

1420 "size": 5, 

1421 "provider_location": new_provider_location, 

1422 } 

1423 self.assertEqual(expected_result, result) 

1424 self.driver._get_snapshot_name.assert_called_once_with( 

1425 snapshot_instance["id"]) 

1426 self.driver.zfs.assert_has_calls([ 

1427 mock.call("list", "-r", "-t", "snapshot", old_provider_location), 

1428 mock.call("rename", old_provider_location, new_provider_location), 

1429 ]) 

1430 

1431 def test_manage_existing_snapshot_not_found(self): 

1432 dataset_name = "path/to/dataset" 

1433 old_provider_location = dataset_name + "@original_snapshot_tag" 

1434 new_snapshot_tag = "fake_new_snapshot_tag" 

1435 snapshot_instance = { 

1436 "id": "fake_snapshot_instance_id", 

1437 "snapshot_id": "fake_snapshot_id", 

1438 "provider_location": old_provider_location, 

1439 } 

1440 self.mock_object( 

1441 self.driver, "_get_snapshot_name", 

1442 mock.Mock(return_value=new_snapshot_tag)) 

1443 self.mock_object( 

1444 self.driver, "zfs", 

1445 mock.Mock(side_effect=exception.ProcessExecutionError("FAKE"))) 

1446 

1447 self.assertRaises( 

1448 exception.ManageInvalidShareSnapshot, 

1449 self.driver.manage_existing_snapshot, 

1450 snapshot_instance, {}, 

1451 ) 

1452 

1453 self.driver.zfs.assert_called_once_with( 

1454 "list", "-r", "-t", "snapshot", old_provider_location) 

1455 self.driver._get_snapshot_name.assert_called_once_with( 

1456 snapshot_instance["id"]) 

1457 

1458 def test_unmanage_snapshot(self): 

1459 snapshot_instance = { 

1460 "id": "fake_snapshot_instance_id", 

1461 "snapshot_id": "fake_snapshot_id", 

1462 } 

1463 self.mock_object(self.driver.private_storage, "delete") 

1464 

1465 self.driver.unmanage_snapshot(snapshot_instance) 

1466 

1467 self.driver.private_storage.delete.assert_called_once_with( 

1468 snapshot_instance["snapshot_id"]) 

1469 

1470 def test__delete_dataset_or_snapshot_with_retry_snapshot(self): 

1471 umount_sideeffects = (exception.ProcessExecutionError, 

1472 exception.ProcessExecutionError, 

1473 exception.ProcessExecutionError) 

1474 zfs_destroy_sideeffects = (exception.ProcessExecutionError, 

1475 exception.ProcessExecutionError, 

1476 None) 

1477 self.mock_object( 

1478 self.driver, 'get_zfs_option', mock.Mock(return_value='/foo@bar')) 

1479 self.mock_object( 

1480 self.driver, 'execute', mock.Mock(side_effect=umount_sideeffects)) 

1481 self.mock_object( 

1482 self.driver, 'zfs', mock.Mock(side_effect=zfs_destroy_sideeffects)) 

1483 

1484 self.driver._delete_dataset_or_snapshot_with_retry('foo@bar') 

1485 

1486 self.driver.get_zfs_option.assert_called_once_with( 

1487 'foo@bar', 'mountpoint') 

1488 self.driver.execute.assert_has_calls( 

1489 [mock.call('sudo', 'umount', '/foo@bar')] * 3) 

1490 self.driver.zfs.assert_has_calls( 

1491 [mock.call('destroy', '-f', 'foo@bar')] * 3) 

1492 self.assertEqual(3, self.driver.execute.call_count) 

1493 self.assertEqual(3, self.driver.zfs.call_count) 

1494 

1495 def test__delete_dataset_or_snapshot_with_retry_dataset_busy_fail(self): 

1496 # simulating local processes that have open files on the mount 

1497 lsof_sideeffects = ([('1001, 1002', '0')] * 30) 

1498 self.mock_object(self.driver, 'get_zfs_option', 

1499 mock.Mock(return_value='/fake/dataset/name')) 

1500 self.mock_object( 

1501 self.driver, 'execute', mock.Mock(side_effect=lsof_sideeffects)) 

1502 self.mock_object(zfs_driver.LOG, 'debug') 

1503 self.mock_object( 

1504 zfs_driver.time, 'time', mock.Mock(side_effect=range(1, 70, 2))) 

1505 self.mock_object(self.driver, 'zfs') 

1506 dataset_name = 'fake/dataset/name' 

1507 

1508 self.assertRaises( 

1509 exception.ZFSonLinuxException, 

1510 self.driver._delete_dataset_or_snapshot_with_retry, 

1511 dataset_name, 

1512 ) 

1513 

1514 self.driver.get_zfs_option.assert_called_once_with( 

1515 dataset_name, 'mountpoint') 

1516 self.assertEqual(29, zfs_driver.LOG.debug.call_count) 

1517 # We should've bailed out before executing "zfs destroy" 

1518 self.driver.zfs.assert_not_called() 

1519 

1520 def test__delete_dataset_or_snapshot_with_retry_dataset(self): 

1521 lsof_sideeffects = (('1001', '0'), exception.ProcessExecutionError) 

1522 umount_sideeffects = (exception.ProcessExecutionError, 

1523 exception.ProcessExecutionError, 

1524 exception.ProcessExecutionError) 

1525 zfs_destroy_sideeffects = (exception.ProcessExecutionError, 

1526 exception.ProcessExecutionError, 

1527 None) 

1528 dataset_name = 'fake/dataset/name' 

1529 self.mock_object(self.driver, 'get_zfs_option', mock.Mock( 

1530 return_value='/%s' % dataset_name)) 

1531 self.mock_object( 

1532 self.driver, 'execute', mock.Mock( 

1533 side_effect=(lsof_sideeffects + umount_sideeffects))) 

1534 self.mock_object( 

1535 self.driver, 'zfs', mock.Mock(side_effect=zfs_destroy_sideeffects)) 

1536 self.mock_object(zfs_driver.LOG, 'info') 

1537 

1538 self.driver._delete_dataset_or_snapshot_with_retry(dataset_name) 

1539 

1540 self.driver.get_zfs_option.assert_called_once_with( 

1541 dataset_name, 'mountpoint') 

1542 self.driver.execute.assert_has_calls( 

1543 [mock.call('lsof', '-w', '/%s' % dataset_name)] * 2 + 

1544 [mock.call('sudo', 'umount', '/%s' % dataset_name)] * 3) 

1545 self.driver.zfs.assert_has_calls( 

1546 [mock.call('destroy', '-f', dataset_name)] * 3) 

1547 self.assertEqual(6, zfs_driver.time.sleep.call_count) 

1548 self.assertEqual(5, self.driver.execute.call_count) 

1549 self.assertEqual(3, self.driver.zfs.call_count) 

1550 

1551 def test_create_replica(self): 

1552 active_replica = { 

1553 'id': 'fake_active_replica_id', 

1554 'host': 'hostname1@backend_name1#foo', 

1555 'size': 5, 

1556 'replica_state': zfs_driver.constants.REPLICA_STATE_ACTIVE, 

1557 } 

1558 replica_list = [active_replica] 

1559 new_replica = { 

1560 'id': 'fake_new_replica_id', 

1561 'host': 'hostname2@backend_name2#bar', 

1562 'share_proto': 'NFS', 

1563 'replica_state': None, 

1564 } 

1565 dst_dataset_name = ( 

1566 'bar/subbar/fake_dataset_name_prefix%s' % new_replica['id']) 

1567 access_rules = ['foo_rule', 'bar_rule'] 

1568 self.driver.private_storage.update( 

1569 active_replica['id'], 

1570 {'dataset_name': 'fake/active/dataset/name', 

1571 'ssh_cmd': 'fake_ssh_cmd'} 

1572 ) 

1573 self.mock_object( 

1574 self.driver, 'execute', 

1575 mock.Mock(side_effect=[('a', 'b'), ('c', 'd')])) 

1576 self.mock_object(self.driver, 'zfs') 

1577 mock_helper = self.mock_object(self.driver, '_get_share_helper') 

1578 self.configuration.zfs_dataset_name_prefix = 'fake_dataset_name_prefix' 

1579 mock_utcnow = self.mock_object(zfs_driver.timeutils, 'utcnow') 

1580 mock_utcnow.return_value.isoformat.return_value = 'some_time' 

1581 

1582 result = self.driver.create_replica( 

1583 'fake_context', replica_list, new_replica, access_rules, []) 

1584 

1585 expected = { 

1586 'export_locations': ( 

1587 mock_helper.return_value.create_exports.return_value), 

1588 'replica_state': zfs_driver.constants.REPLICA_STATE_IN_SYNC, 

1589 'access_rules_status': zfs_driver.constants.STATUS_ACTIVE, 

1590 } 

1591 self.assertEqual(expected, result) 

1592 mock_helper.assert_has_calls([ 

1593 mock.call('NFS'), 

1594 mock.call().update_access( 

1595 dst_dataset_name, access_rules, add_rules=[], 

1596 delete_rules=[], make_all_ro=True), 

1597 mock.call('NFS'), 

1598 mock.call().create_exports(dst_dataset_name), 

1599 ]) 

1600 self.driver.zfs.assert_has_calls([ 

1601 mock.call('set', 'readonly=on', dst_dataset_name), 

1602 mock.call('set', 'quota=%sG' % active_replica['size'], 

1603 dst_dataset_name), 

1604 ]) 

1605 src_snapshot_name = ( 

1606 'fake/active/dataset/name@' 

1607 'tmp_snapshot_for_replication__fake_new_replica_id_time_some_time') 

1608 self.driver.execute.assert_has_calls([ 

1609 mock.call('ssh', 'fake_ssh_cmd', 'sudo', 'zfs', 'snapshot', 

1610 src_snapshot_name), 

1611 mock.call( 

1612 'ssh', 'fake_ssh_cmd', 

1613 'sudo', 'zfs', 'send', '-vDR', src_snapshot_name, '|', 

1614 'ssh', 'fake_username@240.241.242.244', 

1615 'sudo', 'zfs', 'receive', '-v', dst_dataset_name 

1616 ), 

1617 ]) 

1618 mock_utcnow.assert_called_once_with() 

1619 mock_utcnow.return_value.isoformat.assert_called_once_with() 

1620 

1621 def test_delete_replica_not_found(self): 

1622 dataset_name = 'foo/dataset/name' 

1623 pool_name = 'foo_pool' 

1624 replica = {'id': 'fake_replica_id'} 

1625 replica_list = [replica] 

1626 replica_snapshots = [] 

1627 self.mock_object( 

1628 self.driver, '_get_dataset_name', 

1629 mock.Mock(return_value=dataset_name)) 

1630 self.mock_object( 

1631 self.driver, 'zfs', 

1632 mock.Mock(side_effect=[('a', 'b'), ('c', 'd')])) 

1633 self.mock_object( 

1634 self.driver, 'parse_zfs_answer', mock.Mock(side_effect=[[], []])) 

1635 self.mock_object(self.driver, '_delete_dataset_or_snapshot_with_retry') 

1636 self.mock_object(zfs_driver.LOG, 'warning') 

1637 self.mock_object(self.driver, '_get_share_helper') 

1638 self.driver.private_storage.update( 

1639 replica['id'], {'pool_name': pool_name}) 

1640 

1641 self.driver.delete_replica('fake_context', replica_list, 

1642 replica_snapshots, replica) 

1643 

1644 zfs_driver.LOG.warning.assert_called_once_with( 

1645 mock.ANY, {'id': replica['id'], 'name': dataset_name}) 

1646 self.assertEqual(0, self.driver._get_share_helper.call_count) 

1647 self.assertEqual( 

1648 0, self.driver._delete_dataset_or_snapshot_with_retry.call_count) 

1649 self.driver._get_dataset_name.assert_called_once_with(replica) 

1650 self.driver.zfs.assert_has_calls([ 

1651 mock.call('list', '-r', '-t', 'snapshot', pool_name), 

1652 mock.call('list', '-r', pool_name), 

1653 ]) 

1654 self.driver.parse_zfs_answer.assert_has_calls([ 

1655 mock.call('a'), mock.call('c'), 

1656 ]) 

1657 

1658 def test_delete_replica(self): 

1659 dataset_name = 'foo/dataset/name' 

1660 pool_name = 'foo_pool' 

1661 replica = {'id': 'fake_replica_id', 'share_proto': 'NFS'} 

1662 replica_list = [replica] 

1663 self.mock_object( 

1664 self.driver, '_get_dataset_name', 

1665 mock.Mock(return_value=dataset_name)) 

1666 self.mock_object( 

1667 self.driver, 'zfs', 

1668 mock.Mock(side_effect=[('a', 'b'), ('c', 'd')])) 

1669 self.mock_object( 

1670 self.driver, 'parse_zfs_answer', mock.Mock(side_effect=[ 

1671 [{'NAME': 'some_other_dataset@snapshot'}, 

1672 {'NAME': dataset_name + '@foo_snap'}], 

1673 [{'NAME': 'some_other_dataset'}, 

1674 {'NAME': dataset_name}], 

1675 ])) 

1676 mock_helper = self.mock_object(self.driver, '_get_share_helper') 

1677 self.mock_object(self.driver, '_delete_dataset_or_snapshot_with_retry') 

1678 self.mock_object(zfs_driver.LOG, 'warning') 

1679 self.driver.private_storage.update( 

1680 replica['id'], 

1681 {'pool_name': pool_name, 'dataset_name': dataset_name}) 

1682 

1683 self.driver.delete_replica('fake_context', replica_list, [], replica) 

1684 

1685 self.assertEqual(0, zfs_driver.LOG.warning.call_count) 

1686 self.assertEqual(0, self.driver._get_dataset_name.call_count) 

1687 self.driver._delete_dataset_or_snapshot_with_retry.assert_has_calls([ 

1688 mock.call(dataset_name + '@foo_snap'), 

1689 mock.call(dataset_name), 

1690 ]) 

1691 self.driver.zfs.assert_has_calls([ 

1692 mock.call('list', '-r', '-t', 'snapshot', pool_name), 

1693 mock.call('list', '-r', pool_name), 

1694 ]) 

1695 self.driver.parse_zfs_answer.assert_has_calls([ 

1696 mock.call('a'), mock.call('c'), 

1697 ]) 

1698 mock_helper.assert_called_once_with(replica['share_proto']) 

1699 mock_helper.return_value.remove_exports.assert_called_once_with( 

1700 dataset_name) 

1701 

1702 def test_update_replica(self): 

1703 active_replica = { 

1704 'id': 'fake_active_replica_id', 

1705 'host': 'hostname1@backend_name1#foo', 

1706 'size': 5, 

1707 'replica_state': zfs_driver.constants.REPLICA_STATE_ACTIVE, 

1708 } 

1709 replica = { 

1710 'id': 'fake_new_replica_id', 

1711 'host': 'hostname2@backend_name2#bar', 

1712 'share_proto': 'NFS', 

1713 'replica_state': None, 

1714 } 

1715 replica_list = [replica, active_replica] 

1716 replica_snapshots = [] 

1717 dst_dataset_name = ( 

1718 'bar/subbar/fake_dataset_name_prefix%s' % replica['id']) 

1719 src_dataset_name = ( 

1720 'bar/subbar/fake_dataset_name_prefix%s' % active_replica['id']) 

1721 access_rules = ['foo_rule', 'bar_rule'] 

1722 old_repl_snapshot_tag = ( 

1723 self.driver._get_replication_snapshot_prefix( 

1724 active_replica) + 'foo') 

1725 snap_tag_prefix = self.driver._get_replication_snapshot_prefix( 

1726 replica) 

1727 self.driver.private_storage.update( 

1728 active_replica['id'], 

1729 {'dataset_name': src_dataset_name, 

1730 'ssh_cmd': 'fake_src_ssh_cmd', 

1731 'repl_snapshot_tag': old_repl_snapshot_tag} 

1732 ) 

1733 self.driver.private_storage.update( 

1734 replica['id'], 

1735 {'dataset_name': dst_dataset_name, 

1736 'ssh_cmd': 'fake_dst_ssh_cmd', 

1737 'repl_snapshot_tag': old_repl_snapshot_tag} 

1738 ) 

1739 self.mock_object( 

1740 self.driver, 'execute', 

1741 mock.Mock(side_effect=[('a', 'b'), ('c', 'd'), ('e', 'f')])) 

1742 self.mock_object(self.driver, 'execute_with_retry', 

1743 mock.Mock(side_effect=[('g', 'h')])) 

1744 self.mock_object(self.driver, 'zfs', 

1745 mock.Mock(side_effect=[('j', 'k'), ('l', 'm')])) 

1746 self.mock_object( 

1747 self.driver, 'parse_zfs_answer', 

1748 mock.Mock(side_effect=[ 

1749 ({'NAME': dst_dataset_name + '@' + old_repl_snapshot_tag}, 

1750 {'NAME': dst_dataset_name + '@%s_time_some_time' % 

1751 snap_tag_prefix}, 

1752 {'NAME': 'other/dataset/name1@' + old_repl_snapshot_tag}), 

1753 ({'NAME': src_dataset_name + '@' + old_repl_snapshot_tag}, 

1754 {'NAME': src_dataset_name + '@' + snap_tag_prefix + 'quuz'}, 

1755 {'NAME': 'other/dataset/name2@' + old_repl_snapshot_tag}), 

1756 ]) 

1757 ) 

1758 mock_helper = self.mock_object(self.driver, '_get_share_helper') 

1759 self.configuration.zfs_dataset_name_prefix = 'fake_dataset_name_prefix' 

1760 mock_utcnow = self.mock_object(zfs_driver.timeutils, 'utcnow') 

1761 mock_utcnow.return_value.isoformat.return_value = 'some_time' 

1762 mock_delete_snapshot = self.mock_object( 

1763 self.driver, '_delete_dataset_or_snapshot_with_retry') 

1764 

1765 result = self.driver.update_replica_state( 

1766 'fake_context', replica_list, replica, access_rules, 

1767 replica_snapshots) 

1768 

1769 self.assertEqual(zfs_driver.constants.REPLICA_STATE_IN_SYNC, result) 

1770 mock_helper.assert_called_once_with('NFS') 

1771 mock_helper.return_value.update_access.assert_called_once_with( 

1772 dst_dataset_name, access_rules, add_rules=[], delete_rules=[], 

1773 make_all_ro=True) 

1774 self.driver.execute_with_retry.assert_called_once_with( 

1775 'ssh', 'fake_src_ssh_cmd', 'sudo', 'zfs', 'destroy', '-f', 

1776 src_dataset_name + '@' + snap_tag_prefix + 'quuz') 

1777 self.driver.execute.assert_has_calls([ 

1778 mock.call( 

1779 'ssh', 'fake_src_ssh_cmd', 'sudo', 'zfs', 'snapshot', 

1780 src_dataset_name + '@' + 

1781 self.driver._get_replication_snapshot_tag(replica)), 

1782 mock.call( 

1783 'ssh', 'fake_src_ssh_cmd', 'sudo', 'zfs', 'send', 

1784 '-vDRI', old_repl_snapshot_tag, 

1785 src_dataset_name + '@%s' % snap_tag_prefix + '_time_some_time', 

1786 '|', 'ssh', 'fake_dst_ssh_cmd', 

1787 'sudo', 'zfs', 'receive', '-vF', dst_dataset_name), 

1788 mock.call( 

1789 'ssh', 'fake_src_ssh_cmd', 

1790 'sudo', 'zfs', 'list', '-r', '-t', 'snapshot', 'bar'), 

1791 ]) 

1792 mock_delete_snapshot.assert_called_once_with( 

1793 dst_dataset_name + '@' + old_repl_snapshot_tag) 

1794 self.driver.parse_zfs_answer.assert_has_calls( 

1795 [mock.call('l'), mock.call('e')]) 

1796 

1797 def test_promote_replica_active_available(self): 

1798 active_replica = { 

1799 'id': 'fake_active_replica_id', 

1800 'host': 'hostname1@backend_name1#foo', 

1801 'size': 5, 

1802 'replica_state': zfs_driver.constants.REPLICA_STATE_ACTIVE, 

1803 } 

1804 replica = { 

1805 'id': 'fake_first_replica_id', 

1806 'host': 'hostname2@backend_name2#bar', 

1807 'share_proto': 'NFS', 

1808 'replica_state': zfs_driver.constants.REPLICA_STATE_IN_SYNC, 

1809 } 

1810 second_replica = { 

1811 'id': 'fake_second_replica_id', 

1812 'host': 'hostname3@backend_name3#quuz', 

1813 'share_proto': 'NFS', 

1814 'replica_state': zfs_driver.constants.REPLICA_STATE_IN_SYNC, 

1815 } 

1816 replica_list = [replica, active_replica, second_replica] 

1817 dst_dataset_name = ( 

1818 'bar/subbar/fake_dataset_name_prefix%s' % replica['id']) 

1819 src_dataset_name = ( 

1820 'bar/subbar/fake_dataset_name_prefix%s' % active_replica['id']) 

1821 access_rules = ['foo_rule', 'bar_rule'] 

1822 old_repl_snapshot_tag = ( 

1823 self.driver._get_replication_snapshot_prefix( 

1824 active_replica) + 'foo') 

1825 snap_tag_prefix = self.driver._get_replication_snapshot_prefix( 

1826 active_replica) + '_time_some_time' 

1827 self.driver.private_storage.update( 

1828 active_replica['id'], 

1829 {'dataset_name': src_dataset_name, 

1830 'ssh_cmd': 'fake_src_ssh_cmd', 

1831 'repl_snapshot_tag': old_repl_snapshot_tag} 

1832 ) 

1833 for repl in (replica, second_replica): 

1834 self.driver.private_storage.update( 

1835 repl['id'], 

1836 {'dataset_name': ( 

1837 'bar/subbar/fake_dataset_name_prefix%s' % repl['id']), 

1838 'ssh_cmd': 'fake_dst_ssh_cmd', 

1839 'repl_snapshot_tag': old_repl_snapshot_tag} 

1840 ) 

1841 self.mock_object( 

1842 self.driver, 'execute', 

1843 mock.Mock(side_effect=[ 

1844 ('a', 'b'), 

1845 ('c', 'd'), 

1846 ('e', 'f'), 

1847 exception.ProcessExecutionError('Second replica sync failure'), 

1848 ])) 

1849 self.mock_object(self.driver, 'zfs', 

1850 mock.Mock(side_effect=[('g', 'h')])) 

1851 mock_helper = self.mock_object(self.driver, '_get_share_helper') 

1852 self.configuration.zfs_dataset_name_prefix = 'fake_dataset_name_prefix' 

1853 mock_utcnow = self.mock_object(zfs_driver.timeutils, 'utcnow') 

1854 mock_utcnow.return_value.isoformat.return_value = 'some_time' 

1855 mock_delete_snapshot = self.mock_object( 

1856 self.driver, '_delete_dataset_or_snapshot_with_retry') 

1857 

1858 result = self.driver.promote_replica( 

1859 'fake_context', replica_list, replica, access_rules) 

1860 

1861 expected = [ 

1862 {'access_rules_status': 

1863 zfs_driver.constants.SHARE_INSTANCE_RULES_SYNCING, 

1864 'id': 'fake_active_replica_id', 

1865 'replica_state': zfs_driver.constants.REPLICA_STATE_IN_SYNC}, 

1866 {'access_rules_status': zfs_driver.constants.STATUS_ACTIVE, 

1867 'id': 'fake_first_replica_id', 

1868 'replica_state': zfs_driver.constants.REPLICA_STATE_ACTIVE}, 

1869 {'access_rules_status': 

1870 zfs_driver.constants.SHARE_INSTANCE_RULES_SYNCING, 

1871 'id': 'fake_second_replica_id', 

1872 'replica_state': zfs_driver.constants.REPLICA_STATE_OUT_OF_SYNC}, 

1873 ] 

1874 for repl in expected: 

1875 self.assertIn(repl, result) 

1876 self.assertEqual(3, len(result)) 

1877 mock_helper.assert_called_once_with('NFS') 

1878 mock_helper.return_value.update_access.assert_called_once_with( 

1879 dst_dataset_name, access_rules, add_rules=[], delete_rules=[]) 

1880 self.driver.zfs.assert_called_once_with( 

1881 'set', 'readonly=off', dst_dataset_name) 

1882 self.assertEqual(0, mock_delete_snapshot.call_count) 

1883 for repl in (active_replica, replica): 

1884 self.assertEqual( 

1885 snap_tag_prefix, 

1886 self.driver.private_storage.get( 

1887 repl['id'], 'repl_snapshot_tag')) 

1888 self.assertEqual( 

1889 old_repl_snapshot_tag, 

1890 self.driver.private_storage.get( 

1891 second_replica['id'], 'repl_snapshot_tag')) 

1892 

1893 def test_promote_replica_active_not_available(self): 

1894 active_replica = { 

1895 'id': 'fake_active_replica_id', 

1896 'host': 'hostname1@backend_name1#foo', 

1897 'size': 5, 

1898 'replica_state': zfs_driver.constants.REPLICA_STATE_ACTIVE, 

1899 } 

1900 replica = { 

1901 'id': 'fake_first_replica_id', 

1902 'host': 'hostname2@backend_name2#bar', 

1903 'share_proto': 'NFS', 

1904 'replica_state': zfs_driver.constants.REPLICA_STATE_IN_SYNC, 

1905 } 

1906 second_replica = { 

1907 'id': 'fake_second_replica_id', 

1908 'host': 'hostname3@backend_name3#quuz', 

1909 'share_proto': 'NFS', 

1910 'replica_state': zfs_driver.constants.REPLICA_STATE_IN_SYNC, 

1911 } 

1912 third_replica = { 

1913 'id': 'fake_third_replica_id', 

1914 'host': 'hostname4@backend_name4#fff', 

1915 'share_proto': 'NFS', 

1916 'replica_state': zfs_driver.constants.REPLICA_STATE_IN_SYNC, 

1917 } 

1918 replica_list = [replica, active_replica, second_replica, third_replica] 

1919 dst_dataset_name = ( 

1920 'bar/subbar/fake_dataset_name_prefix%s' % replica['id']) 

1921 src_dataset_name = ( 

1922 'bar/subbar/fake_dataset_name_prefix%s' % active_replica['id']) 

1923 access_rules = ['foo_rule', 'bar_rule'] 

1924 old_repl_snapshot_tag = ( 

1925 self.driver._get_replication_snapshot_prefix( 

1926 active_replica) + 'foo') 

1927 snap_tag_prefix = self.driver._get_replication_snapshot_prefix( 

1928 replica) + '_time_some_time' 

1929 self.driver.private_storage.update( 

1930 active_replica['id'], 

1931 {'dataset_name': src_dataset_name, 

1932 'ssh_cmd': 'fake_src_ssh_cmd', 

1933 'repl_snapshot_tag': old_repl_snapshot_tag} 

1934 ) 

1935 for repl in (replica, second_replica, third_replica): 

1936 self.driver.private_storage.update( 

1937 repl['id'], 

1938 {'dataset_name': ( 

1939 'bar/subbar/fake_dataset_name_prefix%s' % repl['id']), 

1940 'ssh_cmd': 'fake_dst_ssh_cmd', 

1941 'repl_snapshot_tag': old_repl_snapshot_tag} 

1942 ) 

1943 self.mock_object( 

1944 self.driver, 'execute', 

1945 mock.Mock(side_effect=[ 

1946 exception.ProcessExecutionError('Active replica failure'), 

1947 ('a', 'b'), 

1948 exception.ProcessExecutionError('Second replica sync failure'), 

1949 ('c', 'd'), 

1950 ])) 

1951 self.mock_object(self.driver, 'zfs', 

1952 mock.Mock(side_effect=[('g', 'h'), ('i', 'j')])) 

1953 mock_helper = self.mock_object(self.driver, '_get_share_helper') 

1954 self.configuration.zfs_dataset_name_prefix = 'fake_dataset_name_prefix' 

1955 mock_utcnow = self.mock_object(zfs_driver.timeutils, 'utcnow') 

1956 mock_utcnow.return_value.isoformat.return_value = 'some_time' 

1957 mock_delete_snapshot = self.mock_object( 

1958 self.driver, '_delete_dataset_or_snapshot_with_retry') 

1959 

1960 result = self.driver.promote_replica( 

1961 'fake_context', replica_list, replica, access_rules) 

1962 

1963 expected = [ 

1964 {'access_rules_status': 

1965 zfs_driver.constants.SHARE_INSTANCE_RULES_SYNCING, 

1966 'id': 'fake_active_replica_id', 

1967 'replica_state': zfs_driver.constants.REPLICA_STATE_OUT_OF_SYNC}, 

1968 {'access_rules_status': zfs_driver.constants.STATUS_ACTIVE, 

1969 'id': 'fake_first_replica_id', 

1970 'replica_state': zfs_driver.constants.REPLICA_STATE_ACTIVE}, 

1971 {'access_rules_status': 

1972 zfs_driver.constants.SHARE_INSTANCE_RULES_SYNCING, 

1973 'id': 'fake_second_replica_id'}, 

1974 {'access_rules_status': 

1975 zfs_driver.constants.SHARE_INSTANCE_RULES_SYNCING, 

1976 'id': 'fake_third_replica_id', 

1977 'replica_state': zfs_driver.constants.REPLICA_STATE_OUT_OF_SYNC}, 

1978 ] 

1979 for repl in expected: 

1980 self.assertIn(repl, result) 

1981 self.assertEqual(4, len(result)) 

1982 mock_helper.assert_called_once_with('NFS') 

1983 mock_helper.return_value.update_access.assert_called_once_with( 

1984 dst_dataset_name, access_rules, add_rules=[], delete_rules=[]) 

1985 self.driver.zfs.assert_has_calls([ 

1986 mock.call('snapshot', dst_dataset_name + '@' + snap_tag_prefix), 

1987 mock.call('set', 'readonly=off', dst_dataset_name), 

1988 ]) 

1989 self.assertEqual(0, mock_delete_snapshot.call_count) 

1990 for repl in (second_replica, replica): 

1991 self.assertEqual( 

1992 snap_tag_prefix, 

1993 self.driver.private_storage.get( 

1994 repl['id'], 'repl_snapshot_tag')) 

1995 for repl in (active_replica, third_replica): 

1996 self.assertEqual( 

1997 old_repl_snapshot_tag, 

1998 self.driver.private_storage.get( 

1999 repl['id'], 'repl_snapshot_tag')) 

2000 

2001 def test_create_replicated_snapshot(self): 

2002 active_replica = { 

2003 'id': 'fake_active_replica_id', 

2004 'replica_state': zfs_driver.constants.REPLICA_STATE_ACTIVE, 

2005 } 

2006 replica = { 

2007 'id': 'fake_first_replica_id', 

2008 'replica_state': zfs_driver.constants.REPLICA_STATE_IN_SYNC, 

2009 } 

2010 second_replica = { 

2011 'id': 'fake_second_replica_id', 

2012 'replica_state': zfs_driver.constants.REPLICA_STATE_IN_SYNC, 

2013 } 

2014 replica_list = [replica, active_replica, second_replica] 

2015 snapshot_instances = [ 

2016 {'id': 'si_%s' % r['id'], 'share_instance_id': r['id'], 

2017 'snapshot_id': 'some_snapshot_id'} 

2018 for r in replica_list 

2019 ] 

2020 src_dataset_name = ( 

2021 'bar/subbar/fake_dataset_name_prefix%s' % active_replica['id']) 

2022 old_repl_snapshot_tag = ( 

2023 self.driver._get_replication_snapshot_prefix( 

2024 active_replica) + 'foo') 

2025 self.driver.private_storage.update( 

2026 active_replica['id'], 

2027 {'dataset_name': src_dataset_name, 

2028 'ssh_cmd': 'fake_src_ssh_cmd', 

2029 'repl_snapshot_tag': old_repl_snapshot_tag} 

2030 ) 

2031 for repl in (replica, second_replica): 

2032 self.driver.private_storage.update( 

2033 repl['id'], 

2034 {'dataset_name': ( 

2035 'bar/subbar/fake_dataset_name_prefix%s' % repl['id']), 

2036 'ssh_cmd': 'fake_dst_ssh_cmd', 

2037 'repl_snapshot_tag': old_repl_snapshot_tag} 

2038 ) 

2039 self.mock_object( 

2040 self.driver, 'execute', mock.Mock(side_effect=[ 

2041 ('a', 'b'), 

2042 ('c', 'd'), 

2043 ('e', 'f'), 

2044 exception.ProcessExecutionError('Second replica sync failure'), 

2045 ])) 

2046 self.configuration.zfs_dataset_name_prefix = 'fake_dataset_name_prefix' 

2047 self.configuration.zfs_dataset_snapshot_name_prefix = ( 

2048 'fake_dataset_snapshot_name_prefix') 

2049 snap_tag_prefix = ( 

2050 self.configuration.zfs_dataset_snapshot_name_prefix + 

2051 'si_%s' % active_replica['id']) 

2052 repl_snap_tag = 'fake_repl_tag' 

2053 self.mock_object( 

2054 self.driver, '_get_replication_snapshot_tag', 

2055 mock.Mock(return_value=repl_snap_tag)) 

2056 

2057 result = self.driver.create_replicated_snapshot( 

2058 'fake_context', replica_list, snapshot_instances) 

2059 

2060 expected = [ 

2061 {'id': 'si_fake_active_replica_id', 

2062 'status': zfs_driver.constants.STATUS_AVAILABLE}, 

2063 {'id': 'si_fake_first_replica_id', 

2064 'status': zfs_driver.constants.STATUS_AVAILABLE}, 

2065 {'id': 'si_fake_second_replica_id', 

2066 'status': zfs_driver.constants.STATUS_ERROR}, 

2067 ] 

2068 for repl in expected: 

2069 self.assertIn(repl, result) 

2070 self.assertEqual(3, len(result)) 

2071 for repl in (active_replica, replica): 

2072 self.assertEqual( 

2073 repl_snap_tag, 

2074 self.driver.private_storage.get( 

2075 repl['id'], 'repl_snapshot_tag')) 

2076 self.assertEqual( 

2077 old_repl_snapshot_tag, 

2078 self.driver.private_storage.get( 

2079 second_replica['id'], 'repl_snapshot_tag')) 

2080 self.assertEqual( 

2081 snap_tag_prefix, 

2082 self.driver.private_storage.get( 

2083 snapshot_instances[0]['snapshot_id'], 'snapshot_tag')) 

2084 self.driver._get_replication_snapshot_tag.assert_called_once_with( 

2085 active_replica) 

2086 

2087 def test_delete_replicated_snapshot(self): 

2088 active_replica = { 

2089 'id': 'fake_active_replica_id', 

2090 'replica_state': zfs_driver.constants.REPLICA_STATE_ACTIVE, 

2091 } 

2092 replica = { 

2093 'id': 'fake_first_replica_id', 

2094 'replica_state': zfs_driver.constants.REPLICA_STATE_IN_SYNC, 

2095 } 

2096 second_replica = { 

2097 'id': 'fake_second_replica_id', 

2098 'replica_state': zfs_driver.constants.REPLICA_STATE_IN_SYNC, 

2099 } 

2100 replica_list = [replica, active_replica, second_replica] 

2101 active_snapshot_instance = { 

2102 'id': 'si_%s' % active_replica['id'], 

2103 'share_instance_id': active_replica['id'], 

2104 'snapshot_id': 'some_snapshot_id', 

2105 'share_id': 'some_share_id', 

2106 } 

2107 snapshot_instances = [ 

2108 {'id': 'si_%s' % r['id'], 'share_instance_id': r['id'], 

2109 'snapshot_id': active_snapshot_instance['snapshot_id'], 

2110 'share_id': active_snapshot_instance['share_id']} 

2111 for r in (replica, second_replica) 

2112 ] 

2113 snapshot_instances.append(active_snapshot_instance) 

2114 for si in snapshot_instances: 

2115 self.driver.private_storage.update( 

2116 si['id'], {'snapshot_name': 'fake_snap_name_%s' % si['id']}) 

2117 src_dataset_name = ( 

2118 'bar/subbar/fake_dataset_name_prefix%s' % active_replica['id']) 

2119 old_repl_snapshot_tag = ( 

2120 self.driver._get_replication_snapshot_prefix( 

2121 active_replica) + 'foo') 

2122 new_repl_snapshot_tag = 'foo_snapshot_tag' 

2123 dataset_name = 'some_dataset_name' 

2124 self.driver.private_storage.update( 

2125 active_replica['id'], 

2126 {'dataset_name': src_dataset_name, 

2127 'ssh_cmd': 'fake_src_ssh_cmd', 

2128 'repl_snapshot_tag': old_repl_snapshot_tag} 

2129 ) 

2130 for replica in (replica, second_replica): 

2131 self.driver.private_storage.update( 

2132 replica['id'], 

2133 {'dataset_name': dataset_name, 

2134 'ssh_cmd': 'fake_ssh_cmd'} 

2135 ) 

2136 self.driver.private_storage.update( 

2137 snapshot_instances[0]['snapshot_id'], 

2138 {'snapshot_tag': new_repl_snapshot_tag} 

2139 ) 

2140 

2141 snap_name = 'fake_snap_name' 

2142 self.mock_object( 

2143 self.driver, 'zfs', mock.Mock(return_value=['out', 'err'])) 

2144 self.mock_object( 

2145 self.driver, 'execute', mock.Mock(side_effect=[ 

2146 ('a', 'b'), 

2147 ('c', 'd'), 

2148 exception.ProcessExecutionError('Second replica sync failure'), 

2149 ])) 

2150 self.mock_object( 

2151 self.driver, 'parse_zfs_answer', mock.Mock(side_effect=[ 

2152 ({'NAME': 'foo'}, {'NAME': snap_name}), 

2153 ({'NAME': 'bar'}, {'NAME': snap_name}), 

2154 [], 

2155 ])) 

2156 expected = sorted([ 

2157 {'id': si['id'], 'status': 'deleted'} for si in snapshot_instances 

2158 ], key=lambda item: item['id']) 

2159 

2160 self.assertEqual( 

2161 new_repl_snapshot_tag, 

2162 self.driver.private_storage.get( 

2163 snapshot_instances[0]['snapshot_id'], 'snapshot_tag')) 

2164 

2165 result = self.driver.delete_replicated_snapshot( 

2166 'fake_context', replica_list, snapshot_instances) 

2167 

2168 self.assertIsNone( 

2169 self.driver.private_storage.get( 

2170 snapshot_instances[0]['snapshot_id'], 'snapshot_tag')) 

2171 

2172 self.driver.execute.assert_has_calls([ 

2173 mock.call('ssh', 'fake_ssh_cmd', 'sudo', 'zfs', 'list', '-r', '-t', 

2174 'snapshot', dataset_name + '@' + new_repl_snapshot_tag) 

2175 for i in (0, 1) 

2176 ]) 

2177 

2178 self.assertIsInstance(result, list) 

2179 self.assertEqual(3, len(result)) 

2180 self.assertEqual(expected, sorted(result, key=lambda item: item['id'])) 

2181 self.driver.parse_zfs_answer.assert_has_calls([ 

2182 mock.call('out'), 

2183 ]) 

2184 

2185 @ddt.data( 

2186 ({'NAME': 'fake'}, zfs_driver.constants.STATUS_ERROR), 

2187 ({'NAME': 'fake_snap_name'}, zfs_driver.constants.STATUS_AVAILABLE), 

2188 ) 

2189 @ddt.unpack 

2190 def test_update_replicated_snapshot(self, parse_answer, expected_status): 

2191 snap_name = 'fake_snap_name' 

2192 self.mock_object(self.driver, '_update_replica_state') 

2193 self.mock_object( 

2194 self.driver, '_get_saved_snapshot_name', 

2195 mock.Mock(return_value=snap_name)) 

2196 self.mock_object( 

2197 self.driver, 'zfs', mock.Mock(side_effect=[('a', 'b')])) 

2198 self.mock_object( 

2199 self.driver, 'parse_zfs_answer', mock.Mock(side_effect=[ 

2200 [parse_answer] 

2201 ])) 

2202 fake_context = 'fake_context' 

2203 replica_list = ['foo', 'bar'] 

2204 share_replica = 'quuz' 

2205 snapshot_instance = {'id': 'fake_snapshot_instance_id'} 

2206 snapshot_instances = ['q', 'w', 'e', 'r', 't', 'y'] 

2207 

2208 result = self.driver.update_replicated_snapshot( 

2209 fake_context, replica_list, share_replica, snapshot_instances, 

2210 snapshot_instance) 

2211 

2212 self.driver._update_replica_state.assert_called_once_with( 

2213 fake_context, replica_list, share_replica) 

2214 self.driver._get_saved_snapshot_name.assert_called_once_with( 

2215 snapshot_instance) 

2216 self.driver.zfs.assert_called_once_with( 

2217 'list', '-r', '-t', 'snapshot', snap_name) 

2218 self.driver.parse_zfs_answer.assert_called_once_with('a') 

2219 self.assertIsInstance(result, dict) 

2220 self.assertEqual(2, len(result)) 

2221 self.assertIn('status', result) 

2222 self.assertIn('id', result) 

2223 self.assertEqual(expected_status, result['status']) 

2224 self.assertEqual(snapshot_instance['id'], result['id']) 

2225 

2226 def test__get_shell_executor_by_host_local(self): 

2227 backend_name = 'foobackend' 

2228 host = 'foohost@%s#foopool' % backend_name 

2229 CONF.set_default( 

2230 'enabled_share_backends', 'fake1,%s,fake2,fake3' % backend_name) 

2231 

2232 self.assertIsNone(self.driver._shell_executors.get(backend_name)) 

2233 

2234 result = self.driver._get_shell_executor_by_host(host) 

2235 

2236 self.assertEqual(self.driver.execute, result) 

2237 

2238 def test__get_shell_executor_by_host_remote(self): 

2239 backend_name = 'foobackend' 

2240 host = 'foohost@%s#foopool' % backend_name 

2241 CONF.set_default('enabled_share_backends', 'fake1,fake2,fake3') 

2242 mock_get_remote_shell_executor = self.mock_object( 

2243 zfs_driver.zfs_utils, 'get_remote_shell_executor') 

2244 mock_config = self.mock_object(zfs_driver, 'get_backend_configuration') 

2245 self.assertIsNone(self.driver._shell_executors.get(backend_name)) 

2246 

2247 for i in (1, 2): 

2248 result = self.driver._get_shell_executor_by_host(host) 

2249 

2250 self.assertEqual( 

2251 mock_get_remote_shell_executor.return_value, result) 

2252 mock_get_remote_shell_executor.assert_called_once_with( 

2253 ip=mock_config.return_value.zfs_service_ip, 

2254 port=22, 

2255 conn_timeout=mock_config.return_value.ssh_conn_timeout, 

2256 login=mock_config.return_value.zfs_ssh_username, 

2257 password=mock_config.return_value.zfs_ssh_user_password, 

2258 privatekey=mock_config.return_value.zfs_ssh_private_key_path, 

2259 max_size=10, 

2260 ) 

2261 zfs_driver.get_backend_configuration.assert_called_once_with( 

2262 backend_name) 

2263 

2264 def test__get_migration_snapshot_tag(self): 

2265 share_instance = {'id': 'fake-share_instance_id'} 

2266 current_time = 'fake_current_time' 

2267 mock_utcnow = self.mock_object(zfs_driver.timeutils, 'utcnow') 

2268 mock_utcnow.return_value.isoformat.return_value = current_time 

2269 expected_value = ( 

2270 self.driver.migration_snapshot_prefix + 

2271 '_fake_share_instance_id_time_' + current_time) 

2272 

2273 result = self.driver._get_migration_snapshot_tag(share_instance) 

2274 

2275 self.assertEqual(expected_value, result) 

2276 

2277 def test_migration_check_compatibility(self): 

2278 src_share = {'host': 'foohost@foobackend#foopool'} 

2279 dst_backend_name = 'barbackend' 

2280 dst_share = {'host': 'barhost@%s#barpool' % dst_backend_name} 

2281 expected = { 

2282 'compatible': True, 

2283 'writable': False, 

2284 'preserve_metadata': True, 

2285 'nondisruptive': True, 

2286 } 

2287 self.mock_object( 

2288 zfs_driver, 

2289 'get_backend_configuration', 

2290 mock.Mock(return_value=type( 

2291 'FakeConfig', (object,), { 

2292 'share_driver': self.driver.configuration.share_driver}))) 

2293 

2294 actual = self.driver.migration_check_compatibility( 

2295 'fake_context', src_share, dst_share) 

2296 

2297 self.assertEqual(expected, actual) 

2298 zfs_driver.get_backend_configuration.assert_called_once_with( 

2299 dst_backend_name) 

2300 

2301 def test_migration_start(self): 

2302 username = self.driver.configuration.zfs_ssh_username 

2303 hostname = self.driver.configuration.zfs_service_ip 

2304 dst_username = username + '_dst' 

2305 dst_hostname = hostname + '_dst' 

2306 src_share = { 

2307 'id': 'fake_src_share_id', 

2308 'host': 'foohost@foobackend#foopool', 

2309 } 

2310 src_dataset_name = 'foo_dataset_name' 

2311 dst_share = { 

2312 'id': 'fake_dst_share_id', 

2313 'host': 'barhost@barbackend#barpool', 

2314 } 

2315 dst_dataset_name = 'bar_dataset_name' 

2316 snapshot_tag = 'fake_migration_snapshot_tag' 

2317 self.mock_object( 

2318 self.driver, 

2319 '_get_dataset_name', 

2320 mock.Mock(return_value=dst_dataset_name)) 

2321 self.mock_object( 

2322 self.driver, 

2323 '_get_migration_snapshot_tag', 

2324 mock.Mock(return_value=snapshot_tag)) 

2325 self.mock_object( 

2326 zfs_driver, 

2327 'get_backend_configuration', 

2328 mock.Mock(return_value=type( 

2329 'FakeConfig', (object,), { 

2330 'zfs_ssh_username': dst_username, 

2331 'zfs_service_ip': dst_hostname, 

2332 }))) 

2333 self.mock_object(self.driver, 'execute') 

2334 

2335 self.mock_object( 

2336 zfs_driver.utils, 'tempdir', 

2337 mock.MagicMock(side_effect=FakeTempDir)) 

2338 

2339 self.driver.private_storage.update( 

2340 src_share['id'], 

2341 {'dataset_name': src_dataset_name, 

2342 'ssh_cmd': username + '@' + hostname}) 

2343 

2344 src_snapshot_name = ( 

2345 '%(dataset_name)s@%(snapshot_tag)s' % { 

2346 'snapshot_tag': snapshot_tag, 

2347 'dataset_name': src_dataset_name, 

2348 } 

2349 ) 

2350 with mock.patch("builtins.open", 

2351 mock.mock_open(read_data="data")) as mock_file: 

2352 self.driver.migration_start( 

2353 self._context, src_share, dst_share, None, None) 

2354 

2355 expected_file_content = ( 

2356 'ssh %(ssh_cmd)s sudo zfs send -vDR %(snap)s | ' 

2357 'ssh %(dst_ssh_cmd)s sudo zfs receive -v %(dst_dataset)s' 

2358 ) % { 

2359 'ssh_cmd': self.driver.private_storage.get( 

2360 src_share['id'], 'ssh_cmd'), 

2361 'dst_ssh_cmd': self.driver.private_storage.get( 

2362 dst_share['id'], 'ssh_cmd'), 

2363 'snap': src_snapshot_name, 

2364 'dst_dataset': dst_dataset_name, 

2365 } 

2366 mock_file.assert_called_with("/foo/path/bar_dataset_name.sh", "w") 

2367 mock_file.return_value.write.assert_called_once_with( 

2368 expected_file_content) 

2369 

2370 self.driver.execute.assert_has_calls([ 

2371 mock.call('sudo', 'zfs', 'snapshot', src_snapshot_name), 

2372 mock.call('sudo', 'chmod', '755', mock.ANY), 

2373 mock.call('nohup', mock.ANY, '&'), 

2374 ]) 

2375 self.driver._get_migration_snapshot_tag.assert_called_once_with( 

2376 dst_share) 

2377 self.driver._get_dataset_name.assert_called_once_with( 

2378 dst_share) 

2379 for k, v in (('dataset_name', dst_dataset_name), 

2380 ('migr_snapshot_tag', snapshot_tag), 

2381 ('pool_name', 'barpool'), 

2382 ('ssh_cmd', dst_username + '@' + dst_hostname)): 

2383 self.assertEqual( 

2384 v, self.driver.private_storage.get(dst_share['id'], k)) 

2385 

2386 def test_migration_continue_success(self): 

2387 dst_share = { 

2388 'id': 'fake_dst_share_id', 

2389 'host': 'barhost@barbackend#barpool', 

2390 } 

2391 dst_dataset_name = 'bar_dataset_name' 

2392 snapshot_tag = 'fake_migration_snapshot_tag' 

2393 self.driver.private_storage.update( 

2394 dst_share['id'], { 

2395 'migr_snapshot_tag': snapshot_tag, 

2396 'dataset_name': dst_dataset_name, 

2397 }) 

2398 mock_executor = self.mock_object( 

2399 self.driver, '_get_shell_executor_by_host') 

2400 self.mock_object( 

2401 self.driver, 'execute', 

2402 mock.Mock(return_value=('fake_out', 'fake_err'))) 

2403 

2404 result = self.driver.migration_continue( 

2405 self._context, 'fake_src_share', dst_share, None, None) 

2406 

2407 self.assertTrue(result) 

2408 mock_executor.assert_called_once_with(dst_share['host']) 

2409 self.driver.execute.assert_has_calls([ 

2410 mock.call('ps', 'aux'), 

2411 mock.call('sudo', 'zfs', 'get', 'quota', dst_dataset_name, 

2412 executor=mock_executor.return_value), 

2413 ]) 

2414 

2415 def test_migration_continue_pending(self): 

2416 dst_share = { 

2417 'id': 'fake_dst_share_id', 

2418 'host': 'barhost@barbackend#barpool', 

2419 } 

2420 dst_dataset_name = 'bar_dataset_name' 

2421 snapshot_tag = 'fake_migration_snapshot_tag' 

2422 self.driver.private_storage.update( 

2423 dst_share['id'], { 

2424 'migr_snapshot_tag': snapshot_tag, 

2425 'dataset_name': dst_dataset_name, 

2426 }) 

2427 mock_executor = self.mock_object( 

2428 self.driver, '_get_shell_executor_by_host') 

2429 self.mock_object( 

2430 self.driver, 'execute', 

2431 mock.Mock(return_value=('foo@%s' % snapshot_tag, 'fake_err'))) 

2432 

2433 result = self.driver.migration_continue( 

2434 self._context, 'fake_src_share', dst_share, None, None) 

2435 

2436 self.assertIsNone(result) 

2437 self.assertFalse(mock_executor.called) 

2438 self.driver.execute.assert_called_once_with('ps', 'aux') 

2439 

2440 def test_migration_continue_exception(self): 

2441 dst_share = { 

2442 'id': 'fake_dst_share_id', 

2443 'host': 'barhost@barbackend#barpool', 

2444 } 

2445 dst_dataset_name = 'bar_dataset_name' 

2446 snapshot_tag = 'fake_migration_snapshot_tag' 

2447 self.driver.private_storage.update( 

2448 dst_share['id'], { 

2449 'migr_snapshot_tag': snapshot_tag, 

2450 'dataset_name': dst_dataset_name, 

2451 }) 

2452 mock_executor = self.mock_object( 

2453 self.driver, '_get_shell_executor_by_host') 

2454 self.mock_object( 

2455 self.driver, 'execute', 

2456 mock.Mock(side_effect=[ 

2457 ('fake_out', 'fake_err'), 

2458 exception.ProcessExecutionError('fake'), 

2459 ])) 

2460 

2461 self.assertRaises( 

2462 exception.ZFSonLinuxException, 

2463 self.driver.migration_continue, 

2464 self._context, 'fake_src_share', dst_share, None, None 

2465 ) 

2466 

2467 mock_executor.assert_called_once_with(dst_share['host']) 

2468 self.driver.execute.assert_has_calls([ 

2469 mock.call('ps', 'aux'), 

2470 mock.call('sudo', 'zfs', 'get', 'quota', dst_dataset_name, 

2471 executor=mock_executor.return_value), 

2472 ]) 

2473 

2474 def test_migration_complete(self): 

2475 src_share = {'id': 'fake_src_share_id'} 

2476 dst_share = { 

2477 'id': 'fake_dst_share_id', 

2478 'host': 'barhost@barbackend#barpool', 

2479 'share_proto': 'fake_share_proto', 

2480 } 

2481 dst_dataset_name = 'bar_dataset_name' 

2482 snapshot_tag = 'fake_migration_snapshot_tag' 

2483 self.driver.private_storage.update( 

2484 dst_share['id'], { 

2485 'migr_snapshot_tag': snapshot_tag, 

2486 'dataset_name': dst_dataset_name, 

2487 }) 

2488 dst_snapshot_name = ( 

2489 '%(dataset_name)s@%(snapshot_tag)s' % { 

2490 'snapshot_tag': snapshot_tag, 

2491 'dataset_name': dst_dataset_name, 

2492 } 

2493 ) 

2494 mock_helper = self.mock_object(self.driver, '_get_share_helper') 

2495 mock_executor = self.mock_object( 

2496 self.driver, '_get_shell_executor_by_host') 

2497 self.mock_object( 

2498 self.driver, 'execute', 

2499 mock.Mock(return_value=('fake_out', 'fake_err'))) 

2500 self.mock_object(self.driver, 'delete_share') 

2501 

2502 result = self.driver.migration_complete( 

2503 self._context, src_share, dst_share, None, None) 

2504 

2505 expected_result = { 

2506 'export_locations': (mock_helper.return_value. 

2507 create_exports.return_value) 

2508 } 

2509 

2510 self.assertEqual(expected_result, result) 

2511 mock_executor.assert_called_once_with(dst_share['host']) 

2512 self.driver.execute.assert_called_once_with( 

2513 'sudo', 'zfs', 'destroy', dst_snapshot_name, 

2514 executor=mock_executor.return_value, 

2515 ) 

2516 self.driver.delete_share.assert_called_once_with( 

2517 self._context, src_share) 

2518 mock_helper.assert_called_once_with(dst_share['share_proto']) 

2519 mock_helper.return_value.create_exports.assert_called_once_with( 

2520 dst_dataset_name, 

2521 executor=self.driver._get_shell_executor_by_host.return_value) 

2522 

2523 def test_migration_cancel_success(self): 

2524 src_dataset_name = 'fake_src_dataset_name' 

2525 src_share = { 

2526 'id': 'fake_src_share_id', 

2527 'dataset_name': src_dataset_name, 

2528 } 

2529 dst_share = { 

2530 'id': 'fake_dst_share_id', 

2531 'host': 'barhost@barbackend#barpool', 

2532 'share_proto': 'fake_share_proto', 

2533 } 

2534 dst_dataset_name = 'fake_dst_dataset_name' 

2535 snapshot_tag = 'fake_migration_snapshot_tag' 

2536 dst_ssh_cmd = 'fake_dst_ssh_cmd' 

2537 self.driver.private_storage.update( 

2538 src_share['id'], {'dataset_name': src_dataset_name}) 

2539 self.driver.private_storage.update( 

2540 dst_share['id'], { 

2541 'migr_snapshot_tag': snapshot_tag, 

2542 'dataset_name': dst_dataset_name, 

2543 'ssh_cmd': dst_ssh_cmd, 

2544 }) 

2545 mock_delete_dataset = self.mock_object( 

2546 self.driver, '_delete_dataset_or_snapshot_with_retry') 

2547 ps_output = ( 

2548 "fake_line1\nfoo_user 12345 foo_dataset_name@%s\n" 

2549 "fake_line2") % snapshot_tag 

2550 self.mock_object( 

2551 self.driver, 'execute', 

2552 mock.Mock(return_value=(ps_output, 'fake_err')) 

2553 ) 

2554 

2555 self.driver.migration_cancel( 

2556 self._context, src_share, dst_share, [], {}) 

2557 

2558 self.driver.execute.assert_has_calls([ 

2559 mock.call('ps', 'aux'), 

2560 mock.call('sudo', 'kill', '-9', '12345'), 

2561 mock.call('ssh', dst_ssh_cmd, 'sudo', 'zfs', 'destroy', '-r', 

2562 dst_dataset_name), 

2563 ]) 

2564 zfs_driver.time.sleep.assert_called_once_with(2) 

2565 mock_delete_dataset.assert_called_once_with( 

2566 src_dataset_name + '@' + snapshot_tag) 

2567 

2568 def test_migration_cancel_error(self): 

2569 src_dataset_name = 'fake_src_dataset_name' 

2570 src_share = { 

2571 'id': 'fake_src_share_id', 

2572 'dataset_name': src_dataset_name, 

2573 } 

2574 dst_share = { 

2575 'id': 'fake_dst_share_id', 

2576 'host': 'barhost@barbackend#barpool', 

2577 'share_proto': 'fake_share_proto', 

2578 } 

2579 dst_dataset_name = 'fake_dst_dataset_name' 

2580 snapshot_tag = 'fake_migration_snapshot_tag' 

2581 dst_ssh_cmd = 'fake_dst_ssh_cmd' 

2582 self.driver.private_storage.update( 

2583 src_share['id'], {'dataset_name': src_dataset_name}) 

2584 self.driver.private_storage.update( 

2585 dst_share['id'], { 

2586 'migr_snapshot_tag': snapshot_tag, 

2587 'dataset_name': dst_dataset_name, 

2588 'ssh_cmd': dst_ssh_cmd, 

2589 }) 

2590 mock_delete_dataset = self.mock_object( 

2591 self.driver, '_delete_dataset_or_snapshot_with_retry') 

2592 self.mock_object( 

2593 self.driver, 'execute', 

2594 mock.Mock(side_effect=exception.ProcessExecutionError), 

2595 ) 

2596 

2597 self.driver.migration_cancel( 

2598 self._context, src_share, dst_share, [], {}) 

2599 

2600 self.driver.execute.assert_has_calls([ 

2601 mock.call('ps', 'aux'), 

2602 mock.call('ssh', dst_ssh_cmd, 'sudo', 'zfs', 'destroy', '-r', 

2603 dst_dataset_name), 

2604 ]) 

2605 zfs_driver.time.sleep.assert_called_once_with(2) 

2606 mock_delete_dataset.assert_called_once_with( 

2607 src_dataset_name + '@' + snapshot_tag)