Skip to content

Commit 0ca915f

Browse files
committed
Added/Updated tests\bugs\gh_7917_test.py: Checked on 4.0.5.3053, 5.0.1.1327, 6.0.0.230 (intermediate snapshots; all in CS/SS).
1 parent e49f253 commit 0ca915f

File tree

1 file changed

+111
-98
lines changed

1 file changed

+111
-98
lines changed

tests/bugs/gh_7917_test.py

Lines changed: 111 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,13 @@
3838
Great thanks to Alex for suggestions (discussion started 13.12.2023 13:18).
3939
4040
Confirmed bug on 6.0.0.173.
41-
Checked on 6.0.0.175, 5.0.0.1305, 4.0.5.3049 - but currenlyt only SuperServer works fine.
42-
Classic has the same problem. Sent report to Alex, 28.12.2023 13:10.
43-
44-
On 3.0.12.33726 error raises:
45-
unsuccessful metadata update / -ALTER DATABASE failed / -Missing correct crypt key / -Plugin fbSampleDbCrypt: / -Crypt key not set
46-
This problem not [yet] investogated.
41+
42+
[30.01.2024] pzotov
43+
Checked on 4.0.5.3053, 5.0.1.1327, 6.0.0.230 (intermediate snapshots; all in CS/SS).
44+
Commits:
45+
6.x: https://github.com/FirebirdSQL/firebird/commit/8295aeb26ccee4f9a644c6928e598abbe06c31c0
46+
5.x: https://github.com/FirebirdSQL/firebird/commit/6f393ba762f390f69f895acc091583a3e486f4d0
47+
4.x: https://github.com/FirebirdSQL/firebird/commit/4c21cae77886461e68c2cab68ec063b416492e61
4748
"""
4849
import os
4950
import time
@@ -78,19 +79,23 @@
7879
SWP_INTERVAL_TO_CHECK = 100
7980
TX_NUMBER_BEFORE_HANG = SWP_INTERVAL_TO_CHECK + 10
8081

82+
MAX_WAIT_FOR_ISQL_TERMINATE=11
83+
8184
db = db_factory(filename = '#' + REQUIRED_ALIAS)
8285

83-
act = python_act('db', substitutions = [('TCPv(4|6)$', 'TCP')])
86+
act = python_act('db', substitutions = [('^((?!(ISQL|Attributes)).)*$', ''), ('[ \t]+', ' '), ('TCPv(4|6)$', 'TCP')])
87+
#act = python_act('db')
8488

8589
tmp_sql_file = temp_file('tmp_7917.sql')
86-
tmp_log_file = temp_file('tmp_7917.log')
90+
tmp_log_file = temp_file('tmp_7917_isql.log')
91+
tmp_gstat_log = temp_file('tmp_7917_gstat.log')
8792

8893
#-----------------------------------------------------------------------
8994

9095
def run_encr_decr(act: Action, mode, max_wait_encr_thread_finish, capsys):
9196
if mode == 'encrypt':
9297
# See letter from Alex, 15.12.2023 16:16 demo-plugin can not transfer named key over network.
93-
# Because of that, we have to use following command WITHOUT adding 'key "{ENCRYPTION_KEY}"':
98+
# Because of that, we have to use 'ALTER DATABASE ENCRYPT WITH <plugin>' _WITHOUT_ adding 'key "{ENCRYPTION_KEY}"'.
9499
# ::: NB ::: One need to be sure that $FB_HOME/plugins.conf contains following lines:
95100
# Plugin = KH2 {
96101
# Module = $(dir_plugins)/fbSampleKeyHolder
@@ -107,66 +112,61 @@ def run_encr_decr(act: Action, mode, max_wait_encr_thread_finish, capsys):
107112
# -Plugin fbSampleDbCrypt:
108113
# -Crypt key not set
109114
#
110-
alter_db_sttm = f'alter database encrypt with "{ENCRYPTION_PLUGIN}"'
115+
alter_db_sttm = f'alter database encrypt with "{ENCRYPTION_PLUGIN}"' # <<< ::: NB ::: DO NOT add '... key "{ENCRYPTION_KEY}"' here!
111116
wait_for_state = 'Database encrypted'
112117
elif mode == 'decrypt':
113118
alter_db_sttm = 'alter database decrypt'
114119
wait_for_state = 'Database not encrypted'
115120

116121

117-
e_thread_started = False
118122
e_thread_finished = False
123+
124+
# 0 = non crypted;
125+
# 1 = has been encrypted;
126+
# 2 = is DEcrypting;
127+
# 3 = is Encrypting;
128+
#
129+
REQUIRED_CRYPT_STATE = 1 if mode == 'encrypt' else 0
130+
current_crypt_state = -1
131+
d1 = py_dt.timedelta(0)
119132
with act.db.connect() as con:
133+
cur = con.cursor()
134+
ps = cur.prepare('select mon$crypt_state from mon$database')
120135

121136
t1=py_dt.datetime.now()
122-
d1 = t1-t1
123137
try:
138+
d1 = t1-t1
124139
con.execute_immediate(alter_db_sttm)
125140
con.commit()
126-
e_thread_started = True
127-
except DatabaseError as e:
128-
print( e.__str__() )
129-
130-
while e_thread_started:
131-
t2=py_dt.datetime.now()
132-
d1=t2-t1
133-
if d1.seconds*1000 + d1.microseconds//1000 > max_wait_encr_thread_finish:
134-
print(f'TIMEOUT EXPIRATION. Mode="{mode}" took {d1.seconds*1000 + d1.microseconds//1000} ms which exceeds limit = {max_wait_encr_thread_finish} ms.')
135-
break
136-
137-
# Possible output:
138-
# Database [not] encrypted
139-
# Database encrypted, crypt thread not complete
140-
act.isql(switches=['-q'], input = 'show database;', combine_output = True)
141-
if wait_for_state in act.stdout:
142-
if 'not complete' in act.stdout:
143-
pass
144-
else:
141+
while True:
142+
t2=py_dt.datetime.now()
143+
d1=t2-t1
144+
if d1.seconds*1000 + d1.microseconds//1000 > max_wait_encr_thread_finish:
145+
break
146+
147+
######################################################
148+
### C H E C K M O N $ C R Y P T _ S T A T E ###
149+
######################################################
150+
cur.execute(ps)
151+
current_crypt_state = cur.fetchone()[0]
152+
con.commit()
153+
if current_crypt_state == REQUIRED_CRYPT_STATE:
145154
e_thread_finished = True
146155
break
147-
act.reset()
148-
149-
act.expected_stdout = ''
150-
act.stdout = capsys.readouterr().out
151-
assert act.clean_stdout == act.clean_expected_stdout
152-
act.reset()
156+
else:
157+
time.sleep(0.5)
158+
except DatabaseError as e:
159+
print( e.__str__() )
153160

154-
assert e_thread_finished
161+
assert e_thread_finished, f'TIMEOUT EXPIRATION. Mode="{mode}" took {d1.seconds*1000 + d1.microseconds//1000} ms which exceeds limit = {max_wait_encr_thread_finish} ms; current_crypt_state={current_crypt_state}'
155162

156163

157164
#-----------------------------------------------------------------------
158165

159-
@pytest.mark.skip("FIXME: hangs on IBSurgeon test server.")
160166
@pytest.mark.encryption
161167
@pytest.mark.version('>=4.0.5')
162168
@pytest.mark.platform('Windows')
163-
def test_1(act: Action, tmp_sql_file: Path, tmp_log_file: Path, capsys):
164-
165-
with act.db.connect() as con:
166-
if act.vars['server-arch'] == 'SuperServer':
167-
pass
168-
else:
169-
pytest.skip("Currently fixed only for SuperServer. Temporary SKIPPED.")
169+
def test_1(act: Action, tmp_sql_file: Path, tmp_log_file: Path, tmp_gstat_log: Path, capsys):
170170

171171
# Scan line-by-line through databases.conf, find line starting with REQUIRED_ALIAS and extract name of file that
172172
# must be created in the $(dir_sampleDb)/qa/ folder. This name will be used further as target database (tmp_fdb).
@@ -187,8 +187,9 @@ def test_1(act: Action, tmp_sql_file: Path, tmp_log_file: Path, capsys):
187187
#
188188
assert fname_in_dbconf
189189

190-
#----------------------------------------------------------------
191-
190+
############################################
191+
### E N C R Y P T D A T A B A S E ###
192+
############################################
192193
run_encr_decr(act, 'encrypt', MAX_WAITING_ENCR_FINISH, capsys)
193194

194195
test_script = f"""
@@ -207,20 +208,14 @@ def test_1(act: Action, tmp_sql_file: Path, tmp_log_file: Path, capsys):
207208
declare v_swp_interval int;
208209
declare v_protocol type of column att_log.att_prot;
209210
begin
210-
select a.mon$remote_protocol, d.mon$sweep_interval
211-
from mon$attachments a
212-
cross join mon$database d
213-
where a.mon$attachment_id = current_connection
214-
into v_protocol, v_swp_interval;
215211
insert into att_log(
216212
att_prot
217213
,evt_name
218-
,swp_interval
219214
) values (
220-
:v_protocol
215+
rdb$get_context('SYSTEM', 'NETWORK_PROTOCOL')
221216
,:a_evt_name
222-
,:v_swp_interval
223217
);
218+
224219
end
225220
^
226221
create or alter trigger trg_detach on disconnect as
@@ -282,36 +277,60 @@ def test_1(act: Action, tmp_sql_file: Path, tmp_log_file: Path, capsys):
282277
#
283278
act.gfix(switches=['-h', f'{SWP_INTERVAL_TO_CHECK}', act.db.dsn], combine_output = True, io_enc = locale.getpreferredencoding())
284279

285-
# Launch ISQL which will hang because update conflict.
286-
# This ISQl will be 'self-terminated' further because we will change DB state to full shutdown:
280+
# gstat -h tmp_gh_7917_alias must show at this point:
281+
# Attributes encrypted, plugin fbSampleDbCrypt
282+
# ...
283+
# Sweep interval: 100
287284
#
288-
p_handed_isql = subprocess.Popen([act.vars['isql'], '-nod', '-i', str(tmp_sql_file),
289-
'-user', act.db.user,
290-
'-password', act.db.password, act.db.dsn],
291-
stderr = subprocess.STDOUT)
292-
time.sleep(2)
293-
294-
try:
295-
act.gfix(switches=['-shut', 'full', '-force', '0', act.db.dsn], combine_output = True, io_enc = locale.getpreferredencoding())
296-
finally:
297-
p_handed_isql.terminate()
298-
p_handed_isql.wait()
285+
with open(tmp_log_file, 'w') as f:
286+
# Launch ISQL which will hang because update conflict.
287+
# This ISQl will be 'self-terminated' further because we will change DB state to full shutdown:
288+
#
289+
p_handed_isql = subprocess.Popen([act.vars['isql'], '-nod', '-i', str(tmp_sql_file),
290+
'-user', act.db.user,
291+
'-password', act.db.password, act.db.dsn],
292+
stdout = f,
293+
stderr = subprocess.STDOUT)
294+
295+
# Let ISQL time to establish connection and fall in hanging state:
296+
time.sleep(3)
297+
298+
try:
299+
act.gfix(switches=['-shut', 'full', '-force', '0', act.db.dsn], combine_output = True, io_enc = locale.getpreferredencoding())
300+
finally:
301+
p_handed_isql.terminate()
302+
303+
p_handed_isql.wait(MAX_WAIT_FOR_ISQL_TERMINATE)
304+
if p_handed_isql.poll() is None:
305+
print(f'Hanged ISQL process WAS NOT terminated in {MAX_WAIT_FOR_ISQL_TERMINATE} second(s).!')
306+
else:
307+
print(f'Hanged ISQL process terminated with retcode = {p_handed_isql.poll()}')
299308

309+
# Result: log of hanged ISQL must contain now:
310+
# Statement failed, SQLSTATE = 08003
311+
# connection shutdown
312+
# -Database is shutdown.
313+
300314
act.gfix(switches=['-online', act.db.dsn], combine_output = True, io_enc = locale.getpreferredencoding())
301315
assert act.clean_stdout == ''
302316
act.reset()
303317

318+
# Must show: Attributes encrypted, plugin {ENCRYPTION_PLUGIN} - without 'shutdown'.
319+
# This is needed only for debug:
320+
with open(tmp_gstat_log, 'w') as f:
321+
subprocess.run( [ act.vars['gstat'], '-h', act.db.db_path, '-user', act.db.user, '-pas', act.db.password ], stdout = f, stderr = subprocess.STDOUT )
322+
304323
#----------------------------------------------------------------
305324

325+
TEST_QUERY = 'select att_prot,who_ami,evt_name from att_log order by trn_id'
306326
final_sql = f"""
307-
set count on;
308327
set list on;
309-
select att_prot,who_ami,evt_name,swp_interval from att_log
310-
order by trn_id;
328+
select mon$crypt_state from mon$database;
329+
{TEST_QUERY};
330+
quit;
311331
"""
312332
tmp_sql_file.write_text(final_sql)
313333

314-
315334
with open(tmp_log_file, 'w') as f:
316335
# Explained by Alex, letter 13-dec-2023 13:18.
317336
# Following ISQL will create attach that provokes AUTO SWEEP (because Next - OST now greater than SWP_INTERVAL_TO_CHECK).
@@ -342,44 +361,38 @@ def test_1(act: Action, tmp_sql_file: Path, tmp_log_file: Path, capsys):
342361
print(e.__str__())
343362

344363
p_chk_sql.terminate()
364+
p_chk_sql.wait(MAX_WAIT_FOR_ISQL_TERMINATE)
345365

346366
# Check if child process has terminated. Set and return returncode attribute. Otherwise, returns None.
347367
if p_chk_sql.poll() is None:
348-
print('Final ISQL process WAS NOT terminated!')
368+
print(f'### ERROR ### Final ISQL process WAS NOT terminated in {MAX_WAIT_FOR_ISQL_TERMINATE} second(s).!')
349369
else:
350-
print(f'Final ISQL process terminated with retcode = {p_chk_sql.poll()}')
370+
print(f'Final ISQL process terminated')
371+
#print(f'Final ISQL process terminated with retcode = {p_chk_sql.poll()}')
351372

373+
############################################
374+
### D E C R Y P T D A T A B A S E ###
375+
############################################
376+
run_encr_decr(act, 'decrypt', MAX_WAITING_ENCR_FINISH, capsys)
352377

353-
# do NOT put here this:
354-
#run_encr_decr(act, 'decrypt', MAX_WAITING_ENCR_FINISH, capsys)
355-
# - otherwise pytest will not return control
356-
357378
with open(tmp_log_file, 'r') as f:
358379
for line in f:
359380
if line.strip():
360-
print('final ISQL log:',line)
381+
print(line)
361382

362383
act.expected_stdout = f"""
363-
Final ISQL process terminated with retcode = 0
364-
final ISQL log: ATT_PROT TCP
365-
final ISQL log: WHO_AMI {act.db.user.upper()}
366-
final ISQL log: EVT_NAME attach
367-
final ISQL log: SWP_INTERVAL {SWP_INTERVAL_TO_CHECK}
368-
369-
final ISQL log: ATT_PROT TCP
370-
final ISQL log: WHO_AMI {act.db.user.upper()}
371-
final ISQL log: EVT_NAME detach
372-
final ISQL log: SWP_INTERVAL {SWP_INTERVAL_TO_CHECK}
373-
374-
final ISQL log: Records affected: 2
384+
Hanged ISQL process terminated with retcode = 1
385+
Final ISQL process terminated
386+
387+
ATT_PROT TCP
388+
WHO_AMI {act.db.user.upper()}
389+
EVT_NAME attach
390+
391+
ATT_PROT TCP
392+
WHO_AMI {act.db.user.upper()}
393+
EVT_NAME detach
375394
"""
376395

377396
act.stdout = capsys.readouterr().out
378397
assert act.clean_stdout == act.clean_expected_stdout
379398
act.reset()
380-
381-
# NB! We have to decrypt database now. Otherwise teardown will fail with:
382-
# firebird.driver.types.DatabaseError: Missing database encryption key for your attachment
383-
# -Plugin fbSampleDbCrypt:
384-
# -Crypt key not set
385-
run_encr_decr(act, 'decrypt', MAX_WAITING_ENCR_FINISH, capsys)

0 commit comments

Comments
 (0)