2021-04-26 20:07:00 +02:00
#coding:utf-8
2022-01-26 21:10:46 +01:00
"""
ID : issue - 6298
ISSUE : 6298
TITLE : Provide ability to see current state of DB encryption
DESCRIPTION :
2023-03-11 08:00:53 +01:00
Test adds lot of data to database , changes FW to ON and runs ' alter database encrypt ... ' .
Then it starts loop with query :
' select mon$crypt_page, mon$crypt_state from mon$database ' , with repeating it every 0.5 second .
Loop continues until we find < ENCRYPTING_PAGES_MIN_CNT > different number of encrypted pages ,
2023-03-11 08:04:23 +01:00
or if timeout < MAX_WAITING_ENCR_FINISH > ms expired .
2023-03-11 08:00:53 +01:00
If ( after loop ) number of pages detected in encrypted state less then < ENCRYPTING_PAGES_MIN_CNT >
then test is considered as failed .
NB : we do NOT wait for the encryption process to complete for the whole database because this
time strongly depends on hardware of testing host and concurrent workload . We just want to see
* several * different values of mon $ crypt_page where mon $ crypt_state = < RUNNING_ENCRYPTING_STATE > .
2022-01-26 21:10:46 +01:00
JIRA : CORE - 6048
2022-02-02 15:46:19 +01:00
FBTEST : bugs . core_6048
2022-06-13 21:55:59 +02:00
NOTES :
[ 13.06 .2022 ] pzotov
Checked on 5.0 .0 .509 - both on Linux and Windows .
2023-03-11 08:00:53 +01:00
[ 11.03 .2023 ] pzotov
Checked on 5.0 .0 .972 , 4.0 .3 .2907 ( Windows ) .
2022-06-13 21:55:59 +02:00
: : : NB - 1 : : :
2023-03-11 08:00:53 +01:00
Before ~ 06 - sep - 2021 encryption * blocked * obtaining data from monitoring tables .
2022-06-13 21:55:59 +02:00
See also : https : / / github . com / FirebirdSQL / firebird / issues / 6947
: : : NB - 2 : : :
2022-06-13 21:59:51 +02:00
Careful tuning required on each tesing box for this test .
2022-01-26 21:10:46 +01:00
"""
2021-04-26 20:07:00 +02:00
2022-06-13 21:55:59 +02:00
import os
import time
import datetime as py_dt
from datetime import timedelta
2021-04-26 20:07:00 +02:00
import pytest
2022-01-26 21:10:46 +01:00
from firebird . qa import *
2022-06-13 21:55:59 +02:00
from firebird . driver import DatabaseError , DbWriteMode
2021-04-26 20:07:00 +02:00
2023-03-11 08:00:53 +01:00
###########################
### S E T T I N G S ###
###########################
# QA_GLOBALS -- dict, is defined in qa/plugin.py, obtain settings
# from act.files_dir/'test_config.ini':
enc_settings = QA_GLOBALS [ ' encryption ' ]
# ACHTUNG: this must be carefully tuned on every new host:
#
MAX_WAITING_ENCR_FINISH = int ( enc_settings [ ' MAX_WAIT_FOR_ENCR_FINISH_WIN ' if os . name == ' nt ' else ' MAX_WAIT_FOR_ENCR_FINISH_NIX ' ] )
assert MAX_WAITING_ENCR_FINISH > 0
ENCRYPTION_PLUGIN = enc_settings [ ' encryption_plugin ' ] # fbSampleDbCrypt
ENCRYPTION_KEY = enc_settings [ ' encryption_key ' ] # Red
# How many *different* page numbers we want to see as being encrypted before break and finish test:
#
ENCRYPTING_PAGES_MIN_CNT = 3
# Value in mon$database.mon$crypt_state for completed encryption:
#
COMPLETED_ENCRYPTION_STATE = 1
# Value in mon$database.mon$crypt_state for CURRENTLY running encryption:
#
RUNNING_ENCRYPTING_STATE = 3
# How many rows will be inserted in order to make encryption thread do its work for some valuable time:
N_ROWS = 15000 if os . name == ' nt ' else 10000
2021-04-26 20:07:00 +02:00
2022-06-13 21:55:59 +02:00
F_LEN = 16383
init_script = f """
set bail on ;
create table tlog ( id bigint generated by default as identity constraint pk_tlog primary key , crypt_page int , crypt_state smallint ) ;
create table test ( s varchar ( { F_LEN } ) ) ;
commit ;
set term ^ ;
execute block as
declare n bigint = { N_ROWS } ;
begin
while ( n > 0 ) do
begin
insert into test ( s ) values ( lpad ( ' ' , { F_LEN } , uuid_to_char ( gen_uuid ( ) ) ) ) ;
n = n - 1 ;
end
end
^
commit
^
"""
2023-03-11 08:00:53 +01:00
db = db_factory ( init = init_script , charset = ' none ' , page_size = 4096 )
2022-01-26 21:10:46 +01:00
act = python_act ( ' db ' , substitutions = [ ( ' [ \t ]+ ' , ' ' ) ] )
2021-04-26 20:07:00 +02:00
2022-06-13 21:55:59 +02:00
@pytest.mark.encryption
2023-03-11 08:00:53 +01:00
@pytest.mark.version ( ' >=4.0.2 ' )
2022-06-13 21:55:59 +02:00
def test_1 ( act : Action , capsys ) :
2022-07-31 12:49:47 +02:00
2022-06-13 21:55:59 +02:00
with act . connect_server ( ) as srv :
srv . database . set_write_mode ( database = act . db . db_path , mode = DbWriteMode . SYNC )
encryption_finished = False
encryption_started = False
with act . db . connect ( ) as con , act . db . connect ( ) as con2 :
t1 = py_dt . datetime . now ( )
d1 = t1 - t1
sttm = f ' alter database encrypt with " { ENCRYPTION_PLUGIN } " key " { ENCRYPTION_KEY } " '
try :
con . execute_immediate ( sttm )
con . commit ( )
encryption_started = True
except DatabaseError as e :
# -ALTER DATABASE failed
# -Crypt plugin fbSampleDbCrypt failed to load
# ==> no sense to do anything else, encryption_started remains False.
print ( e . __str__ ( ) )
cur2 = con2 . cursor ( )
ps = cur2 . prepare ( ' select mon$crypt_page, mon$crypt_state from mon$database ' )
2023-03-11 08:00:53 +01:00
# This will store different number of pages which are currently encrypted.
# When length of this set will exceed ENCRYPTING_PAGES_MIN_CNT then we break from loop:
#
encrypting_pages_set = set ( )
waiting_in_loop = - 1
2022-06-13 21:55:59 +02:00
while encryption_started :
t2 = py_dt . datetime . now ( )
d1 = t2 - t1
2023-03-11 08:00:53 +01:00
waiting_in_loop = d1 . seconds * 1000 + d1 . microseconds / / 1000
if waiting_in_loop > MAX_WAITING_ENCR_FINISH :
print ( f ' TIMEOUT EXPIRATION: encryption took { d1 . seconds * 1000 + d1 . microseconds / / 1000 } ms which exceeds limit = { MAX_WAITING_ENCR_FINISH } ms. ' )
2022-06-13 21:55:59 +02:00
break
cur2 . execute ( ps )
crypt_page , crypt_state = cur2 . fetchone ( )
con2 . commit ( )
# 0 = non crypted;
# 1 = has been encrypted;
# 2 = is DEcrypting;
# 3 = is Encrypting;
2023-03-11 08:00:53 +01:00
if crypt_state == RUNNING_ENCRYPTING_STATE :
encrypting_pages_set . add ( crypt_page , )
if crypt_state == COMPLETED_ENCRYPTION_STATE :
2022-06-13 21:55:59 +02:00
encryption_finished = True
break
2023-03-11 08:00:53 +01:00
elif len ( encrypting_pages_set ) > ENCRYPTING_PAGES_MIN_CNT :
break
2022-06-13 21:55:59 +02:00
else :
2023-03-11 08:00:53 +01:00
time . sleep ( 0.5 )
# ---------------------------------------------------------
expected_msg = f ' EXPECTED: at least { ENCRYPTING_PAGES_MIN_CNT } different mon$crypt_page values found during encryption process. '
if encryption_started :
if len ( encrypting_pages_set ) > ENCRYPTING_PAGES_MIN_CNT :
print ( expected_msg )
2022-06-13 21:55:59 +02:00
else :
2023-03-11 08:00:53 +01:00
print ( f ' UNEXPECTED: only { len ( encrypting_pages_set ) } different mon$crypt_page values found for { waiting_in_loop } ms: %s ' % ' , ' . join ( ( str ( x ) for x in sorted ( encrypting_pages_set ) ) ) )
print ( f ' At least { ENCRYPTING_PAGES_MIN_CNT } different values expected to be found; encryption_finished = { encryption_finished } ' )
else :
print ( ' UNEXPECTED: encryption did not start. ' )
# ---------------------------------------------------------
expected_stdout = f """
{ expected_msg }
"""
2022-06-13 21:55:59 +02:00
act . expected_stdout = expected_stdout
act . stdout = capsys . readouterr ( ) . out
assert act . clean_stdout == act . clean_expected_stdout
act . reset ( )