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"""
4849import os
4950import time
7879SWP_INTERVAL_TO_CHECK = 100
7980TX_NUMBER_BEFORE_HANG = SWP_INTERVAL_TO_CHECK + 10
8081
82+ MAX_WAIT_FOR_ISQL_TERMINATE = 11
83+
8184db = 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
8589tmp_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
9095def 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