From: Terry Burton Date: Thu, 23 Jan 2020 19:22:00 +0000 (+0000) Subject: Oracle sqlippools: Many query fixes and align schema and SP with other drivers (... X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e2b86accc57afb094e2d9792ad81b4888cf7c85d;p=thirdparty%2Ffreeradius-server.git Oracle sqlippools: Many query fixes and align schema and SP with other drivers (#3270) SP-based IP allocation giving of the order of 500 allocs/sec without specific DB tuning. (Using Docker on a development laptop.) --- diff --git a/raddb/mods-config/sql/ippool/oracle/procedure.sql b/raddb/mods-config/sql/ippool/oracle/procedure.sql new file mode 100644 index 00000000000..e4832369778 --- /dev/null +++ b/raddb/mods-config/sql/ippool/oracle/procedure.sql @@ -0,0 +1,129 @@ +-- +-- A stored procedure to reallocate a user's previous address, otherwise +-- provide a free address. +-- +-- Using this SP reduces the usual set dialogue of queries to a single +-- query: +-- +-- BEGIN; SELECT FOR UPDATE; UPDATE; COMMIT; -> SELECT sp() FROM dual +-- +-- The stored procedure is executed on an database instance within a single +-- round trip which often leads to reduced deadlocking and significant +-- performance improvements especially on multi-master clusters, perhaps even +-- by an order of magnitude or more. +-- +-- To use this stored procedure the corresponding queries.conf statements must +-- be configured as follows: +-- +-- allocate_begin = "" +-- allocate_find = "\ +-- SELECT fr_allocate_previous_or_new_framedipaddress( \ +-- '%{control:${pool_name}}', \ +-- '%{User-Name}', \ +-- '%{%{Calling-Station-Id}:-0}', \ +-- '%{NAS-IP-Address}', \ +-- '${pool_key}', \ +-- ${lease_duration} \ +-- ) FROM dual" +-- allocate_update = "" +-- allocate_commit = "" +-- + +CREATE OR REPLACE FUNCTION fr_allocate_previous_or_new_framedipaddress ( + v_pool_name IN VARCHAR2, + v_username IN VARCHAR2, + v_callingstationid IN VARCHAR2, + v_nasipaddress IN VARCHAR2, + v_pool_key IN VARCHAR2, + v_lease_duration IN INTEGER +) +RETURN varchar2 IS + PRAGMA AUTONOMOUS_TRANSACTION; + r_address varchar2(15); +BEGIN + + -- Reissue an existing IP address lease when re-authenticating a session + -- + BEGIN + SELECT framedipaddress INTO r_address FROM radippool WHERE id IN ( + SELECT id FROM ( + SELECT * + FROM radippool + WHERE pool_name = v_pool_name + AND expiry_time > current_timestamp + AND username = v_username + AND callingstationid = v_callingstationid + ) WHERE ROWNUM <= 1 + ) FOR UPDATE SKIP LOCKED; + EXCEPTION + WHEN NO_DATA_FOUND THEN + r_address := NULL; + END; + + -- Reissue an user's previous IP address, provided that the lease is + -- available (i.e. enable sticky IPs) + -- + -- When using this SELECT you should delete the one above. You must also + -- set allocate_clear = "" in queries.conf to persist the associations + -- for expired leases. + -- + -- BEGIN + -- SELECT framedipaddress INTO r_address FROM radippool WHERE id IN ( + -- SELECT id FROM ( + -- SELECT * + -- FROM radippool + -- WHERE pool_name = v_pool_name + -- AND username = v_username + -- AND callingstationid = v_callingstationid + -- ) WHERE ROWNUM <= 1 + -- ) FOR UPDATE SKIP LOCKED; + -- EXCEPTION + -- WHEN NO_DATA_FOUND THEN + -- r_address := NULL; + -- END; + + -- If we didn't reallocate a previous address then pick the least + -- recently used address from the pool which maximises the likelihood + -- of re-assigning the other addresses to their recent user + -- + IF r_address IS NULL THEN + BEGIN + SELECT framedipaddress INTO r_address FROM radippool WHERE id IN ( + SELECT id FROM ( + SELECT * + FROM radippool + WHERE pool_name = v_pool_name + AND expiry_time < CURRENT_TIMESTAMP + ORDER BY expiry_time + ) WHERE ROWNUM <= 1 + ) FOR UPDATE SKIP LOCKED; + EXCEPTION + WHEN NO_DATA_FOUND THEN + r_address := NULL; + END; + END IF; + + -- Return nothing if we failed to allocated an address + -- + IF r_address IS NULL THEN + COMMIT; + RETURN r_address; + END IF; + + -- Update the pool having allocated an IP address + -- + UPDATE radippool + SET + nasipaddress = v_nasipaddress, + pool_key = v_pool_key, + callingstationid = v_callingstationid, + username = v_username, + expiry_time = CURRENT_TIMESTAMP + v_lease_duration * INTERVAL '1' SECOND(1) + WHERE framedipaddress = r_address; + + -- Return the address that we allocated + COMMIT; + RETURN r_address; + +END; +/ diff --git a/raddb/mods-config/sql/ippool/oracle/procedures.sql b/raddb/mods-config/sql/ippool/oracle/procedures.sql deleted file mode 100644 index 598900df060..00000000000 --- a/raddb/mods-config/sql/ippool/oracle/procedures.sql +++ /dev/null @@ -1,41 +0,0 @@ -CREATE OR REPLACE FUNCTION msqlippool(user varchar2, pool varchar2) -RETURN varchar2 IS - - PRAGMA AUTONOMOUS_TRANSACTION; - ip_temp varchar2(20); -BEGIN - - -- If the user's pool is dynamic, get an ipaddress (oldest one) from the corresponding pool - - if pool = 'Dynamic' then - select framedipaddress into ip_temp from (select framedipaddress from radippool where expiry_time < current_timestamp and pool_name = pool ORDER BY expiry_time) where rownum = 1; - return (ip_temp); - - -- Else, then get the static ipaddress for that user from the corresponding pool - - else - select framedipaddress into ip_temp from radippool where username = user and pool_name = pool; - return (ip_temp); - end if; - -exception - - -- This block is executed if there's no free ipaddresses or no static ip assigned to the user - - when NO_DATA_FOUND then - if pool = 'Dynamic' then - return(''); -- so sqlippool can log it on radius.log - end if; - - -- Else, grabs a free IP from the static pool and saves it in radippool so the user will always get the same IP the next time - - select framedipaddress into ip_temp from (select framedipaddress from radippool where expiry_time < current_timestamp and username is null and pool_name = pool) where rownum = 1; - UPDATE radippool SET username = user where framedipaddress = ip_temp; - commit; - return (ip_temp); - - when others - then return('Oracle Exception'); - -END; -/ diff --git a/raddb/mods-config/sql/ippool/oracle/queries.conf b/raddb/mods-config/sql/ippool/oracle/queries.conf index 452ee0dd3cb..108ee9b0bd3 100644 --- a/raddb/mods-config/sql/ippool/oracle/queries.conf +++ b/raddb/mods-config/sql/ippool/oracle/queries.conf @@ -17,68 +17,72 @@ off_begin = "commit" # to the user that they had last session... # allocate_find = "\ - SELECT framedipaddress \ - FROM ${ippool_table} \ - WHERE pool_name = '%{control:Pool-Name}' \ - AND ( \ - expiry_time < current_timestamp \ - OR ( nasipaddress = '%{NAS-IP-Address}' AND pool_key = '${pool_key}' ) \ - ) \ - AND rownum <= 1 \ - ORDER BY \ - (username <> '%{SQL-User-Name}'), \ - (callingstationid <> '%{Calling-Station-Id}'), \ - expiry_time \ - FOR UPDATE" + SELECT framedipaddress FROM ${ippool_table} WHERE id IN ( \ + SELECT id FROM ( \ + SELECT * \ + FROM ${ippool_table} \ + WHERE pool_name = '%{control:Pool-Name}' \ + AND ( \ + expiry_time < current_timestamp \ + OR ( nasipaddress = '%{NAS-IP-Address}' AND pool_key = '${pool_key}' ) \ + ) \ + ORDER BY \ + DECODE(username,'%{SQL-User-Name}',2,1), \ + DECODE(callingstationid,'%{%{Calling-Station-Id}:-0}',2,1), \ + expiry_time \ + ) WHERE ROWNUM <= 1 \ + ) FOR UPDATE" # # The above query again, but with SKIP LOCKED. This requires Oracle > 11g. # It may work in 9i and 10g, but is not documented, so YMMV. # #allocate_find = "\ -# SELECT framedipaddress \ -# FROM ${ippool_table} \ -# WHERE pool_name = '%{control:${pool_name}}' \ -# AND expiry_time < current_timestamp \ -# AND rownum <= 1 \ -# ORDER BY \ -# (username <> '%{SQL-User-Name}'), \ -# (callingstationid <> '%{Calling-Station-Id}'), \ -# expiry_time \ -# FOR UPDATE SKIP LOCKED" - -# -# This function is available if you want to use multiple pools -# -#allocate_find = "\ -# SELECT msqlippool('%{SQL-User-Name}','%{control:Pool-Name}') \ -# FROM dual" +# SELECT framedipaddress FROM ${ippool_table} WHERE id IN ( \ +# SELECT id FROM ( \ +# SELECT * \ +# FROM ${ippool_table} \ +# WHERE pool_name = '%{control:Pool-Name}' \ +# AND ( \ +# expiry_time < current_timestamp \ +# OR ( nasipaddress = '%{NAS-IP-Address}' AND pool_key = '${pool_key}' ) \ +# ) \ +# ORDER BY \ +# DECODE(username,'%{SQL-User-Name}',2,1), \ +# DECODE(callingstationid,'%{%{Calling-Station-Id}:-0}',2,1), \ +# expiry_time \ +# ) WHERE ROWNUM <= 1 \ +# ) FOR UPDATE SKIP LOCKED" # # If you prefer to allocate a random IP address every time, use this query instead # Note: This is very slow if you have a lot of free IPs. # #allocate_find = "\ -# SELECT framedipaddress \ -# FROM ${ippool_table} \ -# WHERE pool_name = '%{control:Pool-Name}' \ -# AND expiry_time < current_timestamp \ -# AND rownum <= 1 \ -# ORDER BY RANDOM() \ -# FOR UPDATE" +# SELECT framedipaddress FROM ${ippool_table} WHERE id IN ( \ +# SELECT id FROM ( \ +# SELECT * \ +# FROM ${ippool_table} \ +# WHERE pool_name = '%{control:Pool-Name}' \ +# AND expiry_time < current_timestamp \ +# ORDER BY DBMS_RANDOM.VALUE \ +# ) WHERE ROWNUM <= 1 \ +# ) FOR UPDATE" # # The above query again, but with SKIP LOCKED. This requires Oracle > 11g. # It may work in 9i and 10g, but is not documented, so YMMV. # #allocate_find = "\ -# SELECT framedipaddress \ -# FROM ${ippool_table} \ -# WHERE pool_name = '%{control:${pool_name}}' \ -# AND expiry_time < current_timestamp \ -# AND rownum <= 1 \ -# ORDER BY RANDOM() \ -# FOR UPDATE SKIP LOCKED" +# SELECT framedipaddress FROM ${ippool_table} WHERE id IN ( \ +# SELECT id FROM (\ +# SELECT * \ +# FROM ${ippool_table} \ +# WHERE pool_name = '%{control:Pool-Name}' \ +# AND expiry_time < current_timestamp \ +# ORDER BY DBMS_RANDOM.VALUE \ +# ) WHERE ROWNUM <= 1 \ +# ) FOR UPDATE SKIP LOCKED" # # If an IP could not be allocated, check to see whether the pool exists or not @@ -104,7 +108,7 @@ allocate_update = "\ SET \ nasipaddress = '%{NAS-IP-Address}', \ pool_key = '${pool_key}', \ - callingstationid = '%{Calling-Station-Id}', \ + callingstationid = '%{%{Calling-Station-Id}:-0}', \ username = '%{SQL-User-Name}', \ expiry_time = current_timestamp + INTERVAL '${lease_duration}' second(1) \ WHERE framedipaddress = '%I'" @@ -122,11 +126,30 @@ allocate_update = "\ allocate_clear = "\ UPDATE ${ippool_table} \ SET \ - nasipaddress = '', \ - pool_key = 0, \ - callingstationid = '', \ + nasipaddress = '0', \ + pool_key = '0', \ + callingstationid = '0', \ expiry_time = current_timestamp - INTERVAL '1' second(1) \ - WHERE pool_key = '${pool_key}'" + WHERE nasipaddress = '%{NAS-IP-Address}' \ + AND pool_key = '${pool_key}'" + + +# +# Use a stored procedure to find AND allocate the address. Read and customise +# `procedure.sql` in this directory to determine the optimal configuration. +# +#allocate_begin = "" +#allocate_find = "\ +# SELECT fr_allocate_previous_or_new_framedipaddress( \ +# '%{control:Pool-Name}', \ +# '%{SQL-User-Name}', \ +# '%{%{Calling-Station-Id}:-0}', \ +# '%{NAS-IP-Address}', \ +# '${pool_key}', \ +# '${lease_duration}' \ +# )" +#allocate_update = "" +#allocate_commit = "" # # This query extends an IP address lease by "lease_duration" when an accounting @@ -145,14 +168,15 @@ start_update = "\ stop_clear = "\ UPDATE ${ippool_table} \ SET \ - nasipaddress = '', \ - pool_key = 0, \ - callingstationid = '', \ + nasipaddress = '0', \ + pool_key = '0', \ + callingstationid = '0', \ expiry_time = current_timestamp - INTERVAL '1' second(1) \ WHERE nasipaddress = '%{Nas-IP-Address}' \ AND pool_key = '${pool_key}' \ AND username = '%{SQL-User-Name}' \ - AND callingstationid = '%{Calling-Station-Id}'" + AND callingstationid = '%{%{Calling-Station-Id}:-0}' \ + AND framedipaddress = '%{${attribute_name}}'" # # This query extends an IP address lease by "lease_duration" when an accounting @@ -166,7 +190,7 @@ alive_update = "\ AND pool_key = '${pool_key}' \ AND framedipaddress = '%{${attribute_name}}' \ AND username = '%{SQL-User-Name}' \ - AND callingstationid = '%{Calling-Station-Id}'" + AND callingstationid = '%{%{Calling-Station-Id}:-0}'" # # This query frees all IP addresses allocated to a NAS when an @@ -175,9 +199,9 @@ alive_update = "\ on_clear = "\ UPDATE ${ippool_table} \ SET \ - nasipaddress = '', \ - pool_key = 0, \ - callingstationid = '', \ + nasipaddress = '0', \ + pool_key = '0', \ + callingstationid = '0', \ expiry_time = current_timestamp - INTERVAL '1' second(1) \ WHERE nasipaddress = '%{Nas-IP-Address}'" @@ -188,8 +212,8 @@ on_clear = "\ off_clear = "\ UPDATE ${ippool_table} \ SET \ - nasipaddress = '', \ - pool_key = 0, \ - callingstationid = '', \ + nasipaddress = '0', \ + pool_key = '0', \ + callingstationid = '0', \ expiry_time = current_timestamp - INTERVAL '1' second(1) \ WHERE nasipaddress = '%{Nas-IP-Address}'" diff --git a/raddb/mods-config/sql/ippool/oracle/schema.sql b/raddb/mods-config/sql/ippool/oracle/schema.sql index c85c293c06b..d550a5d8fa1 100644 --- a/raddb/mods-config/sql/ippool/oracle/schema.sql +++ b/raddb/mods-config/sql/ippool/oracle/schema.sql @@ -1,19 +1,18 @@ CREATE TABLE radippool ( id INT PRIMARY KEY, pool_name VARCHAR(30) NOT NULL, - framedipaddress VARCHAR(30) NOT NULL, - nasipaddress VARCHAR(30) NOT NULL, - pool_key INT NOT NULL, - CalledStationId VARCHAR(64), + framedipaddress VARCHAR(15) NOT NULL, + nasipaddress VARCHAR(15) NOT NULL, + pool_key VARCHAR(30) NOT NULL, + CalledStationId VARCHAR(64) NOT NULL, CallingStationId VARCHAR(64) NOT NULL, expiry_time timestamp(0) NOT NULL, - username VARCHAR(100) + username VARCHAR(64) ); -CREATE INDEX radippool_poolname_ipaadr ON radippool (pool_name, framedipaddress); CREATE INDEX radippool_poolname_expire ON radippool (pool_name, expiry_time); -CREATE INDEX radippool_nasipaddr_key ON radippool (nasipaddress, pool_key); -CREATE INDEX radippool_nasipaddr_calling ON radippool (nasipaddress, callingstationid); +CREATE INDEX radippool_framedipaddress ON radippool (framedipaddress); +CREATE INDEX radippool_nasip_poolkey_ipaddress ON radippool (nasipaddress, pool_key, framedipaddress); CREATE SEQUENCE radippool_seq START WITH 1 INCREMENT BY 1;