WL#1339 "Add per account max_user_connections limit (maximum number

of concurrent connections for the same account)"

Added support of account specific max_user_connections limit. Made all
user limits to be counted per account instead of the old behavior,
which was per user/host accounting. Added option which enables the old
behavior. Added testing of these to the test suite.

(After review version).
parent d6efb4bb
This diff is collapsed.
...@@ -10,8 +10,8 @@ GRANT USAGE ON *.* TO 'mysqltest_1'@'localhost' REQUIRE CIPHER 'EDH-RSA-DES-CBC3 ...@@ -10,8 +10,8 @@ GRANT USAGE ON *.* TO 'mysqltest_1'@'localhost' REQUIRE CIPHER 'EDH-RSA-DES-CBC3
GRANT SELECT ON `mysqltest`.* TO 'mysqltest_1'@'localhost' GRANT SELECT ON `mysqltest`.* TO 'mysqltest_1'@'localhost'
grant delete on mysqltest.* to mysqltest_1@localhost; grant delete on mysqltest.* to mysqltest_1@localhost;
select * from mysql.user where user="mysqltest_1"; select * from mysql.user where user="mysqltest_1";
Host User Password Select_priv Insert_priv Update_priv Delete_priv Create_priv Drop_priv Reload_priv Shutdown_priv Process_priv File_priv Grant_priv References_priv Index_priv Alter_priv Show_db_priv Super_priv Create_tmp_table_priv Lock_tables_priv Execute_priv Repl_slave_priv Repl_client_priv Create_view_priv Show_view_priv ssl_type ssl_cipher x509_issuer x509_subject max_questions max_updates max_connections Host User Password Select_priv Insert_priv Update_priv Delete_priv Create_priv Drop_priv Reload_priv Shutdown_priv Process_priv File_priv Grant_priv References_priv Index_priv Alter_priv Show_db_priv Super_priv Create_tmp_table_priv Lock_tables_priv Execute_priv Repl_slave_priv Repl_client_priv Create_view_priv Show_view_priv ssl_type ssl_cipher x509_issuer x509_subject max_questions max_updates max_connections max_user_connections
localhost mysqltest_1 N N N N N N N N N N N N N N N N N N N N N 0 0 0 localhost mysqltest_1 N N N N N N N N N N N N N N N N N N N N N 0 0 0 0
show grants for mysqltest_1@localhost; show grants for mysqltest_1@localhost;
Grants for mysqltest_1@localhost Grants for mysqltest_1@localhost
GRANT USAGE ON *.* TO 'mysqltest_1'@'localhost' REQUIRE CIPHER 'EDH-RSA-DES-CBC3-SHA' GRANT USAGE ON *.* TO 'mysqltest_1'@'localhost' REQUIRE CIPHER 'EDH-RSA-DES-CBC3-SHA'
......
...@@ -96,6 +96,7 @@ user CREATE TABLE `user` ( ...@@ -96,6 +96,7 @@ user CREATE TABLE `user` (
`max_questions` int(11) unsigned NOT NULL default '0', `max_questions` int(11) unsigned NOT NULL default '0',
`max_updates` int(11) unsigned NOT NULL default '0', `max_updates` int(11) unsigned NOT NULL default '0',
`max_connections` int(11) unsigned NOT NULL default '0', `max_connections` int(11) unsigned NOT NULL default '0',
`max_user_connections` int(11) unsigned NOT NULL default '0',
PRIMARY KEY (`Host`,`User`) PRIMARY KEY (`Host`,`User`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='Users and global privileges' ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='Users and global privileges'
show create table func; show create table func;
......
drop table if exists t1;
create table t1 (i int);
delete from mysql.user where user like 'mysqltest\_%';
delete from mysql.db where user like 'mysqltest\_%';
delete from mysql.tables_priv where user like 'mysqltest\_%';
delete from mysql.columns_priv where user like 'mysqltest\_%';
flush privileges;
grant usage on *.* to mysqltest_1@localhost with max_queries_per_hour 2;
select * from t1;
i
select * from t1;
i
select * from t1;
ERROR 42000: User 'mysqltest_1' has exceeded the 'max_questions' resource (current value: 2)
select * from t1;
ERROR 42000: User 'mysqltest_1' has exceeded the 'max_questions' resource (current value: 2)
drop user mysqltest_1@localhost;
grant usage on *.* to mysqltest_1@localhost with max_updates_per_hour 2;
select * from t1;
i
select * from t1;
i
select * from t1;
i
delete from t1;
delete from t1;
delete from t1;
ERROR 42000: User 'mysqltest_1' has exceeded the 'max_updates' resource (current value: 2)
select * from t1;
i
delete from t1;
ERROR 42000: User 'mysqltest_1' has exceeded the 'max_updates' resource (current value: 2)
select * from t1;
i
drop user mysqltest_1@localhost;
grant usage on *.* to mysqltest_1@localhost with max_connections_per_hour 2;
select * from t1;
i
select * from t1;
i
connect(localhost,mysqltest_1,,test,MYSQL_PORT,MYSQL_SOCK);
ERROR 42000: User 'mysqltest_1' has exceeded the 'max_connections' resource (current value: 2)
select * from t1;
i
connect(localhost,mysqltest_1,,test,9306,/home/dlenev/src/mysql-5.0-1339/mysql-test/var/tmp/master.sock);
ERROR 42000: User 'mysqltest_1' has exceeded the 'max_connections' resource (current value: 2)
drop user mysqltest_1@localhost;
flush privileges;
grant usage on *.* to mysqltest_1@localhost with max_user_connections 2;
select * from t1;
i
select * from t1;
i
connect(localhost,mysqltest_1,,test,MYSQL_PORT,MYSQL_SOCK);
ERROR 42000: User 'mysqltest_1' has exceeded the 'max_user_connections' resource (current value: 2)
select * from t1;
i
grant usage on *.* to mysqltest_1@localhost with max_user_connections 3;
select * from t1;
i
connect(localhost,mysqltest_1,,test,MYSQL_PORT,MYSQL_SOCK);
ERROR 42000: User 'mysqltest_1' has exceeded the 'max_user_connections' resource (current value: 3)
drop user mysqltest_1@localhost;
select @@session.max_user_connections, @@global.max_user_connections;
@@session.max_user_connections @@global.max_user_connections
0 0
set session max_user_connections= 2;
ERROR HY000: Variable 'max_user_connections' is a GLOBAL variable and should be set with SET GLOBAL
set global max_user_connections= 2;
select @@session.max_user_connections, @@global.max_user_connections;
@@session.max_user_connections @@global.max_user_connections
2 2
grant usage on *.* to mysqltest_1@localhost;
select @@session.max_user_connections, @@global.max_user_connections;
@@session.max_user_connections @@global.max_user_connections
2 2
select * from t1;
i
connect(localhost,mysqltest_1,,test,MYSQL_PORT,MYSQL_SOCK);
ERROR 42000: User mysqltest_1 has already more than 'max_user_connections' active connections
grant usage on *.* to mysqltest_1@localhost with max_user_connections 3;
select @@session.max_user_connections, @@global.max_user_connections;
@@session.max_user_connections @@global.max_user_connections
3 2
connect(localhost,mysqltest_1,,test,MYSQL_PORT,MYSQL_SOCK);
ERROR 42000: User 'mysqltest_1' has exceeded the 'max_user_connections' resource (current value: 3)
set global max_user_connections= 0;
drop user mysqltest_1@localhost;
drop table t1;
#
# Test behavior of various per-account limits (aka quotas)
#
# Prepare play-ground
--disable_warnings
drop table if exists t1;
--enable_warnings
create table t1 (i int);
# Just be sure that nothing will bother us
delete from mysql.user where user like 'mysqltest\_%';
delete from mysql.db where user like 'mysqltest\_%';
delete from mysql.tables_priv where user like 'mysqltest\_%';
delete from mysql.columns_priv where user like 'mysqltest\_%';
flush privileges;
# Test of MAX_QUERIES_PER_HOUR limit
grant usage on *.* to mysqltest_1@localhost with max_queries_per_hour 2;
connect (mqph, localhost, mysqltest_1,,);
connection mqph;
select * from t1;
select * from t1;
--error 1226
select * from t1;
connect (mqph2, localhost, mysqltest_1,,);
connection mqph2;
--error 1226
select * from t1;
# cleanup
connection default;
drop user mysqltest_1@localhost;
disconnect mqph;
disconnect mqph2;
# Test of MAX_UPDATES_PER_HOUR limit
grant usage on *.* to mysqltest_1@localhost with max_updates_per_hour 2;
connect (muph, localhost, mysqltest_1,,);
connection muph;
select * from t1;
select * from t1;
select * from t1;
delete from t1;
delete from t1;
--error 1226
delete from t1;
select * from t1;
connect (muph2, localhost, mysqltest_1,,);
connection muph2;
--error 1226
delete from t1;
select * from t1;
# Cleanup
connection default;
drop user mysqltest_1@localhost;
disconnect muph;
disconnect muph2;
# Test of MAX_CONNECTIONS_PER_HOUR limit
grant usage on *.* to mysqltest_1@localhost with max_connections_per_hour 2;
connect (mcph1, localhost, mysqltest_1,,);
connection mcph1;
select * from t1;
connect (mcph2, localhost, mysqltest_1,,);
connection mcph2;
select * from t1;
--replace_result $MASTER_MYPORT MYSQL_PORT $MASTER_MYSOCK MYSQL_SOCK
--error 1226
connect (mcph3, localhost, mysqltest_1,,);
# Old connection is still ok
select * from t1;
# Let us try to close old connections and try again. This will also test that
# counters are not thrown away if there are no connections for this user.
disconnect mcph1;
disconnect mcph2;
--error 1226
connect (mcph3, localhost, mysqltest_1,,);
# Cleanup
connection default;
drop user mysqltest_1@localhost;
# Test of MAX_USER_CONNECTIONS limit
# We need this to reset internal mqh_used variable
flush privileges;
grant usage on *.* to mysqltest_1@localhost with max_user_connections 2;
connect (muc1, localhost, mysqltest_1,,);
connection muc1;
select * from t1;
connect (muc2, localhost, mysqltest_1,,);
connection muc2;
select * from t1;
--replace_result $MASTER_MYPORT MYSQL_PORT $MASTER_MYSOCK MYSQL_SOCK
--error 1226
connect (muc3, localhost, mysqltest_1,,);
# Closing of one of connections should help
disconnect muc1;
connect (muc3, localhost, mysqltest_1,,);
select * from t1;
# Changing of limit should also help (and immediately)
connection default;
grant usage on *.* to mysqltest_1@localhost with max_user_connections 3;
connect (muc4, localhost, mysqltest_1,,);
connection muc4;
select * from t1;
--replace_result $MASTER_MYPORT MYSQL_PORT $MASTER_MYSOCK MYSQL_SOCK
--error 1226
connect (muc5, localhost, mysqltest_1,,);
# Clean up
connection default;
disconnect muc2;
disconnect muc3;
disconnect muc4;
drop user mysqltest_1@localhost;
# Now let us test interaction between global and per-account
# max_user_connections limits
select @@session.max_user_connections, @@global.max_user_connections;
# Local max_user_connections variable can't be set directly
# since this limit is per-account
--error 1229
set session max_user_connections= 2;
# But it is ok to set global max_user_connections
set global max_user_connections= 2;
select @@session.max_user_connections, @@global.max_user_connections;
# Let us check that global limit works
grant usage on *.* to mysqltest_1@localhost;
connect (muca1, localhost, mysqltest_1,,);
connection muca1;
select @@session.max_user_connections, @@global.max_user_connections;
connect (muca2, localhost, mysqltest_1,,);
connection muca2;
select * from t1;
--replace_result $MASTER_MYPORT MYSQL_PORT $MASTER_MYSOCK MYSQL_SOCK
--error 1203
connect (muca3, localhost, mysqltest_1,,);
# Now we are testing that per-account limit prevails over gloabl limit
connection default;
grant usage on *.* to mysqltest_1@localhost with max_user_connections 3;
connect (muca3, localhost, mysqltest_1,,);
connection muca3;
select @@session.max_user_connections, @@global.max_user_connections;
--replace_result $MASTER_MYPORT MYSQL_PORT $MASTER_MYSOCK MYSQL_SOCK
--error 1226
connect (muca4, localhost, mysqltest_1,,);
# Cleanup
connection default;
disconnect muca1;
disconnect muca2;
disconnect muca3;
set global max_user_connections= 0;
drop user mysqltest_1@localhost;
# Final cleanup
drop table t1;
...@@ -148,6 +148,7 @@ then ...@@ -148,6 +148,7 @@ then
c_u="$c_u max_questions int(11) unsigned DEFAULT 0 NOT NULL," c_u="$c_u max_questions int(11) unsigned DEFAULT 0 NOT NULL,"
c_u="$c_u max_updates int(11) unsigned DEFAULT 0 NOT NULL," c_u="$c_u max_updates int(11) unsigned DEFAULT 0 NOT NULL,"
c_u="$c_u max_connections int(11) unsigned DEFAULT 0 NOT NULL," c_u="$c_u max_connections int(11) unsigned DEFAULT 0 NOT NULL,"
c_u="$c_u max_user_connections int(11) unsigned DEFAULT 0 NOT NULL,"
c_u="$c_u PRIMARY KEY Host (Host,User)" c_u="$c_u PRIMARY KEY Host (Host,User)"
c_u="$c_u ) engine=MyISAM" c_u="$c_u ) engine=MyISAM"
c_u="$c_u CHARACTER SET utf8 COLLATE utf8_bin" c_u="$c_u CHARACTER SET utf8 COLLATE utf8_bin"
...@@ -155,24 +156,24 @@ then ...@@ -155,24 +156,24 @@ then
if test "$1" = "test" if test "$1" = "test"
then then
i_u="INSERT INTO user VALUES ('localhost','root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0); i_u="INSERT INTO user VALUES ('localhost','root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0,0);
INSERT INTO user VALUES ('$hostname','root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0); INSERT INTO user VALUES ('$hostname','root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0,0);
REPLACE INTO user VALUES ('127.0.0.1','root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0); REPLACE INTO user VALUES ('127.0.0.1','root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0,0);
INSERT INTO user (host,user) values ('localhost',''); INSERT INTO user (host,user) values ('localhost','');
INSERT INTO user (host,user) values ('$hostname','');" INSERT INTO user (host,user) values ('$hostname','');"
else else
i_u="INSERT INTO user VALUES ('localhost','root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0);" i_u="INSERT INTO user VALUES ('localhost','root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0,0);"
if test "$windows" = "0" if test "$windows" = "0"
then then
i_u="$i_u i_u="$i_u
INSERT INTO user VALUES ('$hostname','root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0); INSERT INTO user VALUES ('$hostname','root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0,0);
INSERT INTO user (host,user) values ('$hostname',''); INSERT INTO user (host,user) values ('$hostname','');
INSERT INTO user (host,user) values ('localhost','');" INSERT INTO user (host,user) values ('localhost','');"
else else
i_u="$i_u i_u="$i_u
INSERT INTO user VALUES ('%','root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0); INSERT INTO user VALUES ('%','root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0,0);
INSERT INTO user VALUES ('localhost','','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0); INSERT INTO user VALUES ('localhost','','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0,0);
INSERT INTO user VALUES ('%','','','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','','','','',0,0,0);" INSERT INTO user VALUES ('%','','','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','','','','',0,0,0,0);"
fi fi
fi fi
fi fi
......
...@@ -170,6 +170,11 @@ ALTER TABLE user ADD Show_view_priv enum('N','Y') DEFAULT 'N' NOT NULL AFTER Cre ...@@ -170,6 +170,11 @@ ALTER TABLE user ADD Show_view_priv enum('N','Y') DEFAULT 'N' NOT NULL AFTER Cre
# #
UPDATE user SET Create_view_priv=Create_priv, Show_view_priv=Create_priv where user<>"" AND @hadCreateViewPriv = 0; UPDATE user SET Create_view_priv=Create_priv, Show_view_priv=Create_priv where user<>"" AND @hadCreateViewPriv = 0;
#
# Add max_user_connections resource limit
#
ALTER TABLE user ADD max_user_connections int(11) unsigned DEFAULT '0' NOT NULL AFTER max_connections;
# #
# Create some possible missing tables # Create some possible missing tables
# #
......
...@@ -304,6 +304,7 @@ static SYMBOL symbols[] = { ...@@ -304,6 +304,7 @@ static SYMBOL symbols[] = {
{ "MAX_QUERIES_PER_HOUR", SYM(MAX_QUERIES_PER_HOUR)}, { "MAX_QUERIES_PER_HOUR", SYM(MAX_QUERIES_PER_HOUR)},
{ "MAX_ROWS", SYM(MAX_ROWS)}, { "MAX_ROWS", SYM(MAX_ROWS)},
{ "MAX_UPDATES_PER_HOUR", SYM(MAX_UPDATES_PER_HOUR)}, { "MAX_UPDATES_PER_HOUR", SYM(MAX_UPDATES_PER_HOUR)},
{ "MAX_USER_CONNECTIONS", SYM(MAX_USER_CONNECTIONS_SYM)},
{ "MEDIUM", SYM(MEDIUM_SYM)}, { "MEDIUM", SYM(MEDIUM_SYM)},
{ "MEDIUMBLOB", SYM(MEDIUMBLOB)}, { "MEDIUMBLOB", SYM(MEDIUMBLOB)},
{ "MEDIUMINT", SYM(MEDIUMINT)}, { "MEDIUMINT", SYM(MEDIUMINT)},
......
...@@ -1022,6 +1022,7 @@ extern my_bool opt_slave_compressed_protocol, use_temp_pool; ...@@ -1022,6 +1022,7 @@ extern my_bool opt_slave_compressed_protocol, use_temp_pool;
extern my_bool opt_readonly, lower_case_file_system; extern my_bool opt_readonly, lower_case_file_system;
extern my_bool opt_enable_named_pipe, opt_sync_frm; extern my_bool opt_enable_named_pipe, opt_sync_frm;
extern my_bool opt_secure_auth; extern my_bool opt_secure_auth;
extern my_bool opt_old_style_user_limits;
extern uint opt_crash_binlog_innodb; extern uint opt_crash_binlog_innodb;
extern char *shared_memory_base_name, *mysqld_unix_port; extern char *shared_memory_base_name, *mysqld_unix_port;
extern bool opt_enable_shared_memory; extern bool opt_enable_shared_memory;
......
...@@ -299,6 +299,12 @@ my_bool opt_short_log_format= 0; ...@@ -299,6 +299,12 @@ my_bool opt_short_log_format= 0;
my_bool opt_log_queries_not_using_indexes= 0; my_bool opt_log_queries_not_using_indexes= 0;
my_bool lower_case_file_system= 0; my_bool lower_case_file_system= 0;
my_bool opt_innodb_safe_binlog= 0; my_bool opt_innodb_safe_binlog= 0;
my_bool opt_old_style_user_limits= 0;
/*
True if there is at least one per-hour limit for some user, so we should check
them before each query (and possibly reset counters when hour is changed).
False otherwise.
*/
volatile bool mqh_used = 0; volatile bool mqh_used = 0;
uint mysqld_port, test_flags, select_errors, dropping_tables, ha_open_options; uint mysqld_port, test_flags, select_errors, dropping_tables, ha_open_options;
...@@ -4184,7 +4190,8 @@ enum options_mysqld ...@@ -4184,7 +4190,8 @@ enum options_mysqld
OPT_OPTIMIZER_SEARCH_DEPTH, OPT_OPTIMIZER_SEARCH_DEPTH,
OPT_OPTIMIZER_PRUNE_LEVEL, OPT_OPTIMIZER_PRUNE_LEVEL,
OPT_UPDATABLE_VIEWS_WITH_LIMIT, OPT_UPDATABLE_VIEWS_WITH_LIMIT,
OPT_AUTO_INCREMENT, OPT_AUTO_INCREMENT_OFFSET OPT_AUTO_INCREMENT, OPT_AUTO_INCREMENT_OFFSET,
OPT_OLD_STYLE_USER_LIMITS
}; };
...@@ -4586,6 +4593,10 @@ Disable with --skip-ndbcluster (will save memory).", ...@@ -4586,6 +4593,10 @@ Disable with --skip-ndbcluster (will save memory).",
"Only use one thread (for debugging under Linux).", 0, 0, 0, GET_NO_ARG, "Only use one thread (for debugging under Linux).", 0, 0, 0, GET_NO_ARG,
NO_ARG, 0, 0, 0, 0, 0, 0}, NO_ARG, 0, 0, 0, 0, 0, 0},
#endif #endif
{"old-style-user-limits", OPT_OLD_STYLE_USER_LIMITS,
"Enable old-style user limits (before 5.0.3 user resources were counted per each user+host vs. per account)",
(gptr*) &opt_old_style_user_limits, (gptr*) &opt_old_style_user_limits,
0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
{"pid-file", OPT_PID_FILE, "Pid file used by safe_mysqld.", {"pid-file", OPT_PID_FILE, "Pid file used by safe_mysqld.",
(gptr*) &pidfile_name_ptr, (gptr*) &pidfile_name_ptr, 0, GET_STR, (gptr*) &pidfile_name_ptr, (gptr*) &pidfile_name_ptr, 0, GET_STR,
REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
......
...@@ -250,8 +250,7 @@ sys_var_long_ptr sys_max_relay_log_size("max_relay_log_size", ...@@ -250,8 +250,7 @@ sys_var_long_ptr sys_max_relay_log_size("max_relay_log_size",
fix_max_relay_log_size); fix_max_relay_log_size);
sys_var_thd_ulong sys_max_sort_length("max_sort_length", sys_var_thd_ulong sys_max_sort_length("max_sort_length",
&SV::max_sort_length); &SV::max_sort_length);
sys_var_long_ptr sys_max_user_connections("max_user_connections", sys_var_max_user_conn sys_max_user_connections("max_user_connections");
&max_user_connections);
sys_var_thd_ulong sys_max_tmp_tables("max_tmp_tables", sys_var_thd_ulong sys_max_tmp_tables("max_tmp_tables",
&SV::max_tmp_tables); &SV::max_tmp_tables);
sys_var_long_ptr sys_max_write_lock_count("max_write_lock_count", sys_var_long_ptr sys_max_write_lock_count("max_write_lock_count",
...@@ -2471,7 +2470,7 @@ bool sys_var_thd_time_zone::check(THD *thd, set_var *var) ...@@ -2471,7 +2470,7 @@ bool sys_var_thd_time_zone::check(THD *thd, set_var *var)
bool sys_var_thd_time_zone::update(THD *thd, set_var *var) bool sys_var_thd_time_zone::update(THD *thd, set_var *var)
{ {
/* We are using Time_zone object found during check() phase */ /* We are using Time_zone object found during check() phase */
*get_tz_ptr(thd,var->type)= var->save_result.time_zone; *get_tz_ptr(thd,var->type)= var->save_result.time_zone;
return 0; return 0;
} }
...@@ -2515,6 +2514,51 @@ void sys_var_thd_time_zone::set_default(THD *thd, enum_var_type type) ...@@ -2515,6 +2514,51 @@ void sys_var_thd_time_zone::set_default(THD *thd, enum_var_type type)
thd->variables.time_zone= global_system_variables.time_zone; thd->variables.time_zone= global_system_variables.time_zone;
} }
bool sys_var_max_user_conn::check(THD *thd, set_var *var)
{
if (var->type == OPT_GLOBAL)
return sys_var_thd::check(thd, var);
else
{
/*
Per-session values of max_user_connections can't be set directly.
QQ: May be we should have a separate error message for this?
*/
my_error(ER_GLOBAL_VARIABLE, MYF(0), name);
return TRUE;
}
}
bool sys_var_max_user_conn::update(THD *thd, set_var *var)
{
DBUG_ASSERT(var->type == OPT_GLOBAL);
pthread_mutex_lock(&LOCK_global_system_variables);
max_user_connections= var->save_result.ulonglong_value;
pthread_mutex_unlock(&LOCK_global_system_variables);
return 0;
}
void sys_var_max_user_conn::set_default(THD *thd, enum_var_type type)
{
DBUG_ASSERT(type == OPT_GLOBAL);
pthread_mutex_lock(&LOCK_global_system_variables);
max_user_connections= (ulong) option_limits->def_value;
pthread_mutex_unlock(&LOCK_global_system_variables);
}
byte *sys_var_max_user_conn::value_ptr(THD *thd, enum_var_type type,
LEX_STRING *base)
{
if (type != OPT_GLOBAL &&
thd->user_connect && thd->user_connect->user_resources.user_conn)
return (byte*) &(thd->user_connect->user_resources.user_conn);
return (byte*) &(max_user_connections);
}
/* /*
Functions to update thd->options bits Functions to update thd->options bits
*/ */
......
...@@ -727,6 +727,23 @@ public: ...@@ -727,6 +727,23 @@ public:
Time_zone **get_tz_ptr(THD *thd, enum_var_type type); Time_zone **get_tz_ptr(THD *thd, enum_var_type type);
}; };
class sys_var_max_user_conn : public sys_var_thd
{
public:
sys_var_max_user_conn(const char *name_arg):
sys_var_thd(name_arg) {}
bool check(THD *thd, set_var *var);
bool update(THD *thd, set_var *var);
bool check_default(enum_var_type type)
{
return type != OPT_GLOBAL || !option_limits;
}
void set_default(THD *thd, enum_var_type type);
SHOW_TYPE type() { return SHOW_LONG; }
byte *value_ptr(THD *thd, enum_var_type type, LEX_STRING *base);
};
/**************************************************************************** /****************************************************************************
Classes for parsing of the SET command Classes for parsing of the SET command
****************************************************************************/ ****************************************************************************/
......
...@@ -332,10 +332,19 @@ my_bool acl_init(THD *org_thd, bool dont_read_acl_tables) ...@@ -332,10 +332,19 @@ my_bool acl_init(THD *org_thd, bool dont_read_acl_tables)
ptr = get_field(&mem, table->field[next_field++]); ptr = get_field(&mem, table->field[next_field++]);
user.user_resource.updates=ptr ? atoi(ptr) : 0; user.user_resource.updates=ptr ? atoi(ptr) : 0;
ptr = get_field(&mem, table->field[next_field++]); ptr = get_field(&mem, table->field[next_field++]);
user.user_resource.connections=ptr ? atoi(ptr) : 0; user.user_resource.conn_per_hour= ptr ? atoi(ptr) : 0;
if (user.user_resource.questions || user.user_resource.updates || if (user.user_resource.questions || user.user_resource.updates ||
user.user_resource.connections) user.user_resource.conn_per_hour)
mqh_used=1; mqh_used=1;
if (table->fields >= 34)
{
/* Starting from 5.0.3 we have max_user_connections field */
ptr= get_field(&mem, table->field[next_field++]);
user.user_resource.user_conn= ptr ? atoi(ptr) : 0;
}
else
user.user_resource.user_conn= 0;
} }
else else
{ {
...@@ -922,12 +931,14 @@ static void acl_update_user(const char *user, const char *host, ...@@ -922,12 +931,14 @@ static void acl_update_user(const char *user, const char *host,
!my_strcasecmp(system_charset_info, host, acl_user->host.hostname)) !my_strcasecmp(system_charset_info, host, acl_user->host.hostname))
{ {
acl_user->access=privileges; acl_user->access=privileges;
if (mqh->bits & 1) if (mqh->specified_limits & USER_RESOURCES::QUERIES_PER_HOUR)
acl_user->user_resource.questions=mqh->questions; acl_user->user_resource.questions=mqh->questions;
if (mqh->bits & 2) if (mqh->specified_limits & USER_RESOURCES::UPDATES_PER_HOUR)
acl_user->user_resource.updates=mqh->updates; acl_user->user_resource.updates=mqh->updates;
if (mqh->bits & 4) if (mqh->specified_limits & USER_RESOURCES::CONNECTIONS_PER_HOUR)
acl_user->user_resource.connections=mqh->connections; acl_user->user_resource.conn_per_hour= mqh->conn_per_hour;
if (mqh->specified_limits & USER_RESOURCES::USER_CONNECTIONS)
acl_user->user_resource.user_conn= mqh->user_conn;
if (ssl_type != SSL_TYPE_NOT_SPECIFIED) if (ssl_type != SSL_TYPE_NOT_SPECIFIED)
{ {
acl_user->ssl_type= ssl_type; acl_user->ssl_type= ssl_type;
...@@ -1605,7 +1616,8 @@ static int replace_user_table(THD *thd, TABLE *table, const LEX_USER &combo, ...@@ -1605,7 +1616,8 @@ static int replace_user_table(THD *thd, TABLE *table, const LEX_USER &combo,
if (combo.password.str) // If password given if (combo.password.str) // If password given
table->field[2]->store(password, password_len, system_charset_info); table->field[2]->store(password, password_len, system_charset_info);
else if (!rights && !revoke_grant && else if (!rights && !revoke_grant &&
lex->ssl_type == SSL_TYPE_NOT_SPECIFIED && !lex->mqh.bits) lex->ssl_type == SSL_TYPE_NOT_SPECIFIED &&
!lex->mqh.specified_limits)
{ {
DBUG_RETURN(0); DBUG_RETURN(0);
} }
...@@ -1667,13 +1679,16 @@ static int replace_user_table(THD *thd, TABLE *table, const LEX_USER &combo, ...@@ -1667,13 +1679,16 @@ static int replace_user_table(THD *thd, TABLE *table, const LEX_USER &combo,
} }
USER_RESOURCES mqh= lex->mqh; USER_RESOURCES mqh= lex->mqh;
if (mqh.bits & 1) if (mqh.specified_limits & USER_RESOURCES::QUERIES_PER_HOUR)
table->field[28]->store((longlong) mqh.questions); table->field[28]->store((longlong) mqh.questions);
if (mqh.bits & 2) if (mqh.specified_limits & USER_RESOURCES::UPDATES_PER_HOUR)
table->field[29]->store((longlong) mqh.updates); table->field[29]->store((longlong) mqh.updates);
if (mqh.bits & 4) if (mqh.specified_limits & USER_RESOURCES::CONNECTIONS_PER_HOUR)
table->field[30]->store((longlong) mqh.connections); table->field[30]->store((longlong) mqh.conn_per_hour);
mqh_used = mqh_used || mqh.questions || mqh.updates || mqh.connections; if (table->fields >= 34 &&
(mqh.specified_limits & USER_RESOURCES::USER_CONNECTIONS))
table->field[33]->store((longlong) mqh.user_conn);
mqh_used= mqh_used || mqh.questions || mqh.updates || mqh.conn_per_hour;
} }
if (old_row_exists) if (old_row_exists)
{ {
...@@ -3345,8 +3360,10 @@ bool mysql_show_grants(THD *thd,LEX_USER *lex_user) ...@@ -3345,8 +3360,10 @@ bool mysql_show_grants(THD *thd,LEX_USER *lex_user)
} }
} }
if ((want_access & GRANT_ACL) || if ((want_access & GRANT_ACL) ||
(acl_user->user_resource.questions | acl_user->user_resource.updates | (acl_user->user_resource.questions ||
acl_user->user_resource.connections)) acl_user->user_resource.updates ||
acl_user->user_resource.conn_per_hour ||
acl_user->user_resource.user_conn))
{ {
global.append(" WITH",5); global.append(" WITH",5);
if (want_access & GRANT_ACL) if (want_access & GRANT_ACL)
...@@ -3355,8 +3372,10 @@ bool mysql_show_grants(THD *thd,LEX_USER *lex_user) ...@@ -3355,8 +3372,10 @@ bool mysql_show_grants(THD *thd,LEX_USER *lex_user)
"MAX_QUERIES_PER_HOUR"); "MAX_QUERIES_PER_HOUR");
add_user_option(&global, acl_user->user_resource.updates, add_user_option(&global, acl_user->user_resource.updates,
"MAX_UPDATES_PER_HOUR"); "MAX_UPDATES_PER_HOUR");
add_user_option(&global, acl_user->user_resource.connections, add_user_option(&global, acl_user->user_resource.conn_per_hour,
"MAX_CONNECTIONS_PER_HOUR"); "MAX_CONNECTIONS_PER_HOUR");
add_user_option(&global, acl_user->user_resource.user_conn,
"MAX_USER_CONNECTIONS");
} }
protocol->prepare_for_resend(); protocol->prepare_for_resend();
protocol->store(global.ptr(),global.length(),global.charset()); protocol->store(global.ptr(),global.length(),global.charset());
......
...@@ -172,14 +172,10 @@ static int get_or_create_user_conn(THD *thd, const char *user, ...@@ -172,14 +172,10 @@ static int get_or_create_user_conn(THD *thd, const char *user,
} }
uc->user=(char*) (uc+1); uc->user=(char*) (uc+1);
memcpy(uc->user,temp_user,temp_len+1); memcpy(uc->user,temp_user,temp_len+1);
uc->user_len= user_len; uc->host= uc->user + user_len + 1;
uc->host=uc->user + uc->user_len + 1;
uc->len = temp_len; uc->len = temp_len;
uc->connections = 1; uc->connections= uc->questions= uc->updates= uc->conn_per_hour= 0;
uc->questions=uc->updates=uc->conn_per_hour=0;
uc->user_resources=*mqh; uc->user_resources=*mqh;
if (max_user_connections && mqh->connections > max_user_connections)
uc->user_resources.connections = max_user_connections;
uc->intime=thd->thr_create_time; uc->intime=thd->thr_create_time;
if (my_hash_insert(&hash_user_connections, (byte*) uc)) if (my_hash_insert(&hash_user_connections, (byte*) uc))
{ {
...@@ -352,12 +348,16 @@ int check_user(THD *thd, enum enum_server_command command, ...@@ -352,12 +348,16 @@ int check_user(THD *thd, enum enum_server_command command,
thd->db_access=0; thd->db_access=0;
/* Don't allow user to connect if he has done too many queries */ /* Don't allow user to connect if he has done too many queries */
if ((ur.questions || ur.updates || ur.connections || if ((ur.questions || ur.updates || ur.conn_per_hour || ur.user_conn ||
max_user_connections) && max_user_connections) &&
get_or_create_user_conn(thd,thd->user,thd->host_or_ip,&ur)) get_or_create_user_conn(thd,
opt_old_style_user_limits ? thd->user : thd->priv_user,
opt_old_style_user_limits ? thd->host_or_ip : thd->priv_host,
&ur))
DBUG_RETURN(-1); DBUG_RETURN(-1);
if (thd->user_connect && if (thd->user_connect &&
(thd->user_connect->user_resources.connections || (thd->user_connect->user_resources.conn_per_hour ||
thd->user_connect->user_resources.user_conn ||
max_user_connections) && max_user_connections) &&
check_for_max_user_connections(thd, thd->user_connect)) check_for_max_user_connections(thd, thd->user_connect))
DBUG_RETURN(-1); DBUG_RETURN(-1);
...@@ -448,19 +448,28 @@ static int check_for_max_user_connections(THD *thd, USER_CONN *uc) ...@@ -448,19 +448,28 @@ static int check_for_max_user_connections(THD *thd, USER_CONN *uc)
DBUG_ENTER("check_for_max_user_connections"); DBUG_ENTER("check_for_max_user_connections");
(void) pthread_mutex_lock(&LOCK_user_conn); (void) pthread_mutex_lock(&LOCK_user_conn);
if (max_user_connections && if (max_user_connections && !uc->user_resources.user_conn &&
max_user_connections < (uint) uc->connections) max_user_connections < (uint) uc->connections)
{ {
net_printf_error(thd, ER_TOO_MANY_USER_CONNECTIONS, uc->user); net_printf_error(thd, ER_TOO_MANY_USER_CONNECTIONS, uc->user);
error=1; error=1;
goto end; goto end;
} }
if (uc->user_resources.connections && if (uc->user_resources.user_conn &&
uc->user_resources.connections <= uc->conn_per_hour) uc->user_resources.user_conn < uc->connections)
{
net_printf_error(thd, ER_USER_LIMIT_REACHED, uc->user,
"max_user_connections",
(long) uc->user_resources.user_conn);
error= 1;
goto end;
}
if (uc->user_resources.conn_per_hour &&
uc->user_resources.conn_per_hour <= uc->conn_per_hour)
{ {
net_printf_error(thd, ER_USER_LIMIT_REACHED, uc->user, net_printf_error(thd, ER_USER_LIMIT_REACHED, uc->user,
"max_connections", "max_connections",
(long) uc->user_resources.connections); (long) uc->user_resources.conn_per_hour);
error=1; error=1;
goto end; goto end;
} }
...@@ -3507,7 +3516,7 @@ create_error: ...@@ -3507,7 +3516,7 @@ create_error:
Query_log_event qinfo(thd, thd->query, thd->query_length, 0); Query_log_event qinfo(thd, thd->query, thd->query_length, 0);
mysql_bin_log.write(&qinfo); mysql_bin_log.write(&qinfo);
} }
if (mqh_used && lex->sql_command == SQLCOM_GRANT) if (lex->sql_command == SQLCOM_GRANT)
{ {
List_iterator <LEX_USER> str_list(lex->users_list); List_iterator <LEX_USER> str_list(lex->users_list);
LEX_USER *user; LEX_USER *user;
...@@ -5682,8 +5691,7 @@ bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables, ...@@ -5682,8 +5691,7 @@ bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables,
{ {
acl_reload(thd); acl_reload(thd);
grant_reload(thd); grant_reload(thd);
if (mqh_used) reset_mqh(thd, (LEX_USER *) NULL, TRUE);
reset_mqh(thd,(LEX_USER *) NULL,TRUE);
} }
#endif #endif
if (options & REFRESH_LOG) if (options & REFRESH_LOG)
......
...@@ -334,6 +334,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); ...@@ -334,6 +334,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize);
%token MAX_CONNECTIONS_PER_HOUR %token MAX_CONNECTIONS_PER_HOUR
%token MAX_QUERIES_PER_HOUR %token MAX_QUERIES_PER_HOUR
%token MAX_UPDATES_PER_HOUR %token MAX_UPDATES_PER_HOUR
%token MAX_USER_CONNECTIONS_SYM
%token MEDIUM_SYM %token MEDIUM_SYM
%token MIN_ROWS %token MIN_ROWS
%token NAMES_SYM %token NAMES_SYM
...@@ -6959,6 +6960,7 @@ keyword: ...@@ -6959,6 +6960,7 @@ keyword:
| MAX_CONNECTIONS_PER_HOUR {} | MAX_CONNECTIONS_PER_HOUR {}
| MAX_QUERIES_PER_HOUR {} | MAX_QUERIES_PER_HOUR {}
| MAX_UPDATES_PER_HOUR {} | MAX_UPDATES_PER_HOUR {}
| MAX_USER_CONNECTIONS_SYM {}
| MEDIUM_SYM {} | MEDIUM_SYM {}
| MERGE_SYM {} | MERGE_SYM {}
| MICROSECOND_SYM {} | MICROSECOND_SYM {}
...@@ -7775,18 +7777,23 @@ grant_option: ...@@ -7775,18 +7777,23 @@ grant_option:
| MAX_QUERIES_PER_HOUR ULONG_NUM | MAX_QUERIES_PER_HOUR ULONG_NUM
{ {
Lex->mqh.questions=$2; Lex->mqh.questions=$2;
Lex->mqh.bits |= 1; Lex->mqh.specified_limits|= USER_RESOURCES::QUERIES_PER_HOUR;
} }
| MAX_UPDATES_PER_HOUR ULONG_NUM | MAX_UPDATES_PER_HOUR ULONG_NUM
{ {
Lex->mqh.updates=$2; Lex->mqh.updates=$2;
Lex->mqh.bits |= 2; Lex->mqh.specified_limits|= USER_RESOURCES::UPDATES_PER_HOUR;
} }
| MAX_CONNECTIONS_PER_HOUR ULONG_NUM | MAX_CONNECTIONS_PER_HOUR ULONG_NUM
{ {
Lex->mqh.connections=$2; Lex->mqh.conn_per_hour= $2;
Lex->mqh.bits |= 4; Lex->mqh.specified_limits|= USER_RESOURCES::CONNECTIONS_PER_HOUR;
} }
| MAX_USER_CONNECTIONS_SYM ULONG_NUM
{
Lex->mqh.user_conn= $2;
Lex->mqh.specified_limits|= USER_RESOURCES::USER_CONNECTIONS;
}
; ;
begin: begin:
......
...@@ -205,16 +205,65 @@ typedef struct st_lex_user { ...@@ -205,16 +205,65 @@ typedef struct st_lex_user {
} LEX_USER; } LEX_USER;
/*
This structure specifies the maximum amount of resources which
can be consumed by each account. Zero value of a member means
there is no limit.
*/
typedef struct user_resources { typedef struct user_resources {
uint questions, updates, connections, bits; /* Maximum number of queries/statements per hour. */
uint questions;
/*
Maximum number of updating statements per hour (which statements are
updating is defined by uc_update_queries array).
*/
uint updates;
/* Maximum number of connections established per hour. */
uint conn_per_hour;
/* Maximum number of concurrent connections. */
uint user_conn;
/*
Values of this enum and specified_limits member are used by the
parser to store which user limits were specified in GRANT statement.
*/
enum {QUERIES_PER_HOUR= 1, UPDATES_PER_HOUR= 2, CONNECTIONS_PER_HOUR= 4,
USER_CONNECTIONS= 8};
uint specified_limits;
} USER_RESOURCES; } USER_RESOURCES;
/*
This structure is used for counting resources consumed and for checking
them against specified user limits.
*/
typedef struct user_conn { typedef struct user_conn {
char *user, *host; /*
uint len, connections, conn_per_hour, updates, questions, user_len; Pointer to user+host key (pair separated by '\0') defining the entity
for which resources are counted (By default it is user account thus
priv_user/priv_host pair is used. If --old-style-user-limits option
is enabled, resources are counted for each user+host separately).
*/
char *user;
/* Pointer to host part of the key. */
char *host;
/* Total length of the key. */
uint len;
/* Current amount of concurrent connections for this account. */
uint connections;
/*
Current number of connections per hour, number of updating statements
per hour and total number of statements per hour for this account.
*/
uint conn_per_hour, updates, questions;
/* Maximum amount of resources which account is allowed to consume. */
USER_RESOURCES user_resources; USER_RESOURCES user_resources;
/*
The moment of time when per hour counters were reset last time
(i.e. start of "hour" for conn_per_hour, updates, questions counters).
*/
time_t intime; time_t intime;
} USER_CONN; } USER_CONN;
/* Bits in form->update */ /* Bits in form->update */
#define REG_MAKE_DUPP 1 /* Make a copy of record when read */ #define REG_MAKE_DUPP 1 /* Make a copy of record when read */
#define REG_NEW_RECORD 2 /* Write a new record if not found */ #define REG_NEW_RECORD 2 /* Write a new record if not found */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment