diff --git a/tests/api2/test_261_iscsi_cmd.py b/tests/api2/test_261_iscsi_cmd.py index 212e6eafe0d3..5dcb7698dbd4 100644 --- a/tests/api2/test_261_iscsi_cmd.py +++ b/tests/api2/test_261_iscsi_cmd.py @@ -2994,3 +2994,44 @@ def test__target_delete_extents(iscsi_running): # Ensure the extent does not exist extents = call('iscsi.extent.query', [['id', '=', target3_config['extent']['id']]]) assert len(extents) == 0, extents + + +@pytest.mark.parametrize('extent_type', ["FILE", "VOLUME"]) +def test__synchronize_cache(iscsi_running, extent_type): + """ + Test that a SCSI SYNCHRONIZE CACHE command actually ensures data is + written to stable storage. + """ + def test_empty(path, extent_size_mb=5): + # Use dd to ensure we don't end up with stuff from page cache, plus we want + # to check what's actually ON DISK. + output = ssh(f'dd if={path} iflag=nocache oflag=nocache | od -a -', False) + return output.split() == ['0000000', + 'nul', 'nul', 'nul', 'nul', 'nul', 'nul', 'nul', 'nul', + 'nul', 'nul', 'nul', 'nul', 'nul', 'nul', 'nul', 'nul', + '*', oct(extent_size_mb * MB)[2:]] + + name = f'{target_name}{extent_type.lower()}' + deadbeef = bytearray.fromhex('deadbeef') * 128 + zeros = bytearray(512) + with initiator_portal() as config: + with configured_target(config, name, extent_type, extent_size_mb=5) as config: + if extent_type == 'FILE': + test_path = config['extent']['path'] + else: + test_path = os.path.join('/dev', config['extent']['disk']) + iqn = f'{basename}:{name}' + + # Ensure that to start the volume has NULL data + assert test_empty(test_path), 'Initial data is not empty' + + with iscsi_scsi_connection(truenas_server.ip, iqn) as s: + # Write data SYNCHRONIZE and check the volume has non-NULL data + s.write16(0, 1, deadbeef) + s.synchronizecache10(0, 1) + assert test_empty(test_path) is False, 'Expected deadbeef data' + + # Zero data SYNCHRONIZE and check the volume has NULL data again + s.write16(0, 1, zeros) + s.synchronizecache16(0, 1) + assert test_empty(test_path) is True, 'Expected zero data' diff --git a/tests/api2/test_262_iscsi_alua.py b/tests/api2/test_262_iscsi_alua.py index e97df2052566..9d04fdb4408e 100644 --- a/tests/api2/test_262_iscsi_alua.py +++ b/tests/api2/test_262_iscsi_alua.py @@ -329,6 +329,7 @@ def zero_luns(self, ip, config): def zero_lun(s, target_num, lun, lun_config): # Write zeros using WRITE SAME (16) s.writesame16(0, self.BLOCKS, self.ZEROS) + s.synchronizecache10(0, self.BLOCKS) self.visit_luns(ip, config, zero_lun) def check_zero_luns(self, ip, config): @@ -365,6 +366,7 @@ def page_pattern(self, target_num, lun): def write_patterns(self, ip, config): def write_pattern(s, target_num, lun, lun_config): s.writesame16(1, 2, self.page_pattern(target_num, lun)) + s.synchronizecache10(1, 2) self.visit_luns(ip, config, write_pattern) def check_patterns(self, ip, config): @@ -465,12 +467,14 @@ def test_failover_complex_alua_config(self, fix_write_patterns, fix_get_domain): if self.VERBOSE: print('Powering off VM', domain) poweroff_vm(domain) + sleep(3) # Wait for the new MASTER to come up newnode = self.wait_for_new_master(node) # Wait for the failover event to complete self.wait_for_failover_in_progress() + sleep(5) if newnode == 'A': new_ip = truenas_server.nodea_ip @@ -484,9 +488,6 @@ def test_failover_complex_alua_config(self, fix_write_patterns, fix_get_domain): print(f'Validate data pattern seen by Node {newnode}...') self.check_patterns(new_ip, fix_write_patterns) - if self.VERBOSE: - print(f'Validate data pattern seen by Node {newnode}...') - @pytest.mark.timeout(900) def test_boot_complex_alua_config(self, fix_write_patterns, fix_get_domain, fix_orig_active_node): """