]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Oracle sqlippools: Many query fixes and align schema and SP with other drivers (...
authorTerry Burton <tez@terryburton.co.uk>
Thu, 23 Jan 2020 19:22:00 +0000 (19:22 +0000)
committerArran Cudbard-Bell <a.cudbardb@freeradius.org>
Thu, 23 Jan 2020 19:22:00 +0000 (14:22 -0500)
SP-based IP allocation giving of the order of 500 allocs/sec without
specific DB tuning. (Using Docker on a development laptop.)

raddb/mods-config/sql/ippool/oracle/procedure.sql [new file with mode: 0644]
raddb/mods-config/sql/ippool/oracle/procedures.sql [deleted file]
raddb/mods-config/sql/ippool/oracle/queries.conf
raddb/mods-config/sql/ippool/oracle/schema.sql

diff --git a/raddb/mods-config/sql/ippool/oracle/procedure.sql b/raddb/mods-config/sql/ippool/oracle/procedure.sql
new file mode 100644 (file)
index 0000000..e483236
--- /dev/null
@@ -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 (file)
index 598900d..0000000
+++ /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;
-/
index 452ee0dd3cb917a8d4dcfe2718e3086fd75a4b30..108ee9b0bd3f32092680a5fa19de0136240fc7d6 100644 (file)
@@ -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}'"
index c85c293c06b6a3c99f02b5249636d6dc22554324..d550a5d8fa10725d202b8a1ece6934ce093c5ab4 100644 (file)
@@ -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;