WL#6940: Server version token and check

Affects: Server-5.7   —   Status: Complete

Version Tokens can help a system of clients and servers to synchronize
with each other.

The current requirement for the feature comes from Fabric. But it shall
be seen as a general synchronization feature, which can also be used
independent from Fabric. In other words, the server implementation shall
be done in a way, that it can be used directly by applications.

The feature requires changes in the MySQL server, in Fabric, and in the
connectors. This worklog specifies the changes in the MySQL server.
NF1: The server side support will be implemented as an audit plugin.

FR2: The plugin will provide a read/write session level variable to hold the
session level version token list that will allow setting/reading the list in
full only

FR3: The plugin will provide an imitation of global level variable to store the 
version tokens.

FR4: The plugin will provide a UDF to set the above stated variable in full 
which will get replicated. UDF to partially update the variable, i.e. add/delete 
particular key-value pairs shall also be provided. A UDF is provided to show the 
list of version tokens present.

FR5: Token names and values in global and the session token lists values will
consist of valid characters of the system character set and will exclude equal
'=' and semicolon ';', quotes and spaces. The tokens are case sensitive.
Any extra spaces which are part of either name or value will be deleted and not
considered as they are inserted in the hash. For example, in
key1=value1;, key name will be key1 and value is
value1 both without any spaces.

FR6: When set or retrieved, the token lists will be represented as a semi-colon
separated list of name=value pairs.

FR7: At session creation time (post-authentication) the session's token list
will be empty.

FR8: At plugin load time the server's token list will be empty

FR9: Before executing each query the plugin will compare the session token list
with the global token list and will return an error if:
  - a session token name is not present in the global list
  - a session token value does not correspond to the value for the same token in
    the global list

In case the session level variable itself is to be changed from some invalid
value (i.e. some name/value which is not in accordance with the global hash)
it cannot be done directly as the query itself fails because of invalid values.

This scenario can be handled by using COM_RESET_CONNECTION which resets the
session value to blank and hence no checks are performed when a new value is
set.

FR10: If FR9 is not met, the query execution will stop with an error

NF11: Before doing the comparisons in FR9 the plugin will acquire locking
service locks in share mode for tokens in session version vector.

NF12: The plugin will release the locking service locks it has at the end of query
execution.

FR13: A user needs super privileges to make any modifications to global version 
vector and for even viewing them.
-- The plugin Structure --

A shared library shall be introduced which implements:
1. An audit plugin tracking query execution and connect/disconnect events.
   This also holds a list of version tokens to be considered which are
   accessible through UDFs.
2. UDFs to set, edit, delete and show version_tokens_list.


-- The global token list --

The plugin will internally hold a global hash(version_tokens_hash) with token
name/values as its members.

At plugin load time the global hash is always empty.

The hash contains the version token pairs in structs.


-- The version_tokens_list variable --

This is not a pure plugin variable, but a kind of imitation.

It can be changed completely using version_tokens_set() UDF.

Partial modifications are done through version_tokens_edit() UDF.

Individual tokens can be deleted using version_tokens_delete() UDF.

The variable can be displayed using version_tokens_show() UDF.

All the above UDFs need super privileges. If any token is repeated in the 
arguments for setting/editing the variable in these UDFs, the one which comes 
last is considered. For example, when version_tokens_set("token1=val1; 
token2=val2; token1=val3") is executed, val3 is considered for token1.

This variable when set or edited will:

1. Take an exclusive lock on the global hash.
2. Parse the semicolon separated list of global tokens and store these into
   version_tokens_hash.
3. Release the exclusive lock on the hash.

When displayed through show(), all the tokens present in the hash are displayed, 
not necessarily in a sorted order.


-- Session level tokens --

This session attributes are stored as system session variable called 
session_version_tokens with SESSION_ONLY flag set. This variable can be easily 
read using the thd object in plugin. It is stored in the same format as the 
string used for the global version tokens list i.e. semicolon separated list of 
tokens.

-- tokens_version_number --

To avoid any unnecessary comparisons, a version number shall be maintained in
the plugin to track the changes to global plugin variable. On updating the
variable, this version number shall be incremented by 1. When the plugin is
loaded, this variable starts with the value 1. This integer is only for the 
internal implementation and is invisible to the user.

A session level variable called tokens_version_number (default 0), with flags 
SESSION_ONLY and INVISIBLE is also introduced to hold this value. Before 
comparing the session level tokens with global tokens, the value stored in 
tokens_version_number variable is compared against version number stored in the 
plugin. If the numbers match, the comparison step can be safely skipped. Else, 
the comparison is done again, and the variable is updated with the latest value 
if the comparison is successful.

When the session_version_tokens variable is updated, tokens_version_number is 
set back to 0. Since the global version value is never 0, comparision will take 
place for next query execution in that session.

-- The version_tokens_set() UDF --

There will be a new UDF exposed by the plugin that will have the following
syntax:

version_tokens_set(version_tokens_list varchar());

This when called, will parse the semicolon separated key value pairs and store 
them in version_tokens_hash.

-- The version_tokens_edit() UDF --

Syntax:

version_tokens_edit(version_tokens_list varchar());

This when called, will parse the semicolon separated key value pairs and search 
for them in version_tokens_hash. If the entries are found, their values are 
edited. Else the new pair is stored in the hash.

-- The version_tokens_delete() UDF --

Syntax:

version_tokens_delete(version_tokens_names_list varchar());

This when called, will parse the semicolon separated version token names and 
search for them in version_tokens_hash. If found, they are deleted from the 
hash. If '*' is passed as an argument, all the tokens are deleted.

-- The version_tokens_show() UDF --

Syntax:

version_tokens_show();

This when called, will fetch all the key value pairs from version_tokens_hash, 
form a semicolon separated list of them and returns it.

-- At query execution start event --

The plugin will:

1. If there are no session vectors, do nothing.
2. Grab locking service shared locks for all elements of session version vectors
3. If the version number matches with token_version_number, move to query
   execution. Else, compare them with the existing ones in global hash.
4. If they differ, unlock the version vectors and return the relevant error
   (if the token name doesn't exist or the value is different). Else, update
   token_version_number.
5. Keep the locks for the duration of the query 

-- At query execution end event --

If there are no session vectors, do nothing. Else, the plugin will release all
the locking service locks taken.

-- At plugin unload event --

The plugin will:
1. Take exclusive locks on all the global version vector names
2. Clear the hash.
3. Release locks.

-- Admin locking of tokens --

If the admin wants to lock certain tokens in share/exclusive mode, it can be 
done directly through the following helper UDFs talking to the locking service
APIs (see WL#8161 for further details) :

--- version_tokens_lock_shared ---

version_tokens_lock_shared(lock_name1 VARCHAR[, ...], lock_timeout_seconds INT)
RETURNS INT

returns 0 on failure, 1 on success.

Takes (atomically) shared locking service locks on lock_name1, lock_name2, ... 
The last argument is the lock wait timeout in seconds.


--- version_tokens_lock_exclusive ---

version_tokens_lock_exclusive(lock_name1 VARCHAR[, ...], lock_timeout_seconds
INT) RETURNS INT

returns 0 on failure, 1 on success.

Takes (atomically) exclusive locking service locks on lock_name1, lock_name2, ... 
The last argument is the lock wait timeout in seconds.

--- version_tokens_unlock ---

version_tokens_unlock() RETURNS INT

returns 0 on failure, 1 on success.

Releases all locking service locks taken by the thread.