Merge branch '2.0' into develop
This commit is contained in:
commit
d7f79942be
@ -293,3 +293,17 @@ add_custom_target(generate_html
|
||||
-DCMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}
|
||||
-P generate-html.cmake
|
||||
COMMENT "Generating HTML files" VERBATIM)
|
||||
|
||||
if(PACKAGE)
|
||||
message(STATUS "You can install startup scripts and system configuration files for MaxScale by running the 'postinst' shell script located at ${CMAKE_INSTALL_PREFIX}.")
|
||||
message(STATUS "To remove these installed files, run the 'postrm' shell script located in the same folder.")
|
||||
endif()
|
||||
|
||||
# NOTE: If you make changes here, ensure they are compatible with the
|
||||
# situation in gwdirs.h.in.
|
||||
if (NOT CMAKE_INSTALL_PREFIX EQUAL "/usr")
|
||||
install(DIRECTORY DESTINATION var/cache/maxscale)
|
||||
install(DIRECTORY DESTINATION var/log/maxscale)
|
||||
install(DIRECTORY DESTINATION var/run/maxscale)
|
||||
endif()
|
||||
|
||||
|
@ -7,29 +7,52 @@ to specific plugins or to MariaDB MaxScale as a whole this document is divided
|
||||
into a number of sections, the purpose of which are to isolate the
|
||||
limitations to the components which illustrate them.
|
||||
|
||||
## Limitations in the MariaDB MaxScale core
|
||||
# Limitations in the MariaDB MaxScale core
|
||||
|
||||
This section describes the limitations that are common to all
|
||||
configuration of plugins with MariaDB MaxScale.
|
||||
|
||||
## Limitations with MySQL Protocol support
|
||||
## Crash if one of several listeners for a Service fails as startup
|
||||
|
||||
If a service has multiple listeners and one of those listeners fails
|
||||
at startup, MariaDB MaxScale will crash.
|
||||
|
||||
A typical reason for a listener to fail is that it has been configured
|
||||
with a non-existing socket path or a port that MariaDB MaxScale is not allowed
|
||||
to use.
|
||||
|
||||
Workaround: Ensure that socket paths and ports are valid.
|
||||
|
||||
Issues [MXS-710](https://jira.mariadb.org/browse/MXS-710) and
|
||||
[MXS-711](https://jira.mariadb.org/browse/MXS-711) relate to this.
|
||||
|
||||
# Protocol limitations
|
||||
|
||||
## Limitations with MySQL Protocol support (MySQLClient)
|
||||
|
||||
Compression is not included in MySQL server handshake
|
||||
|
||||
## Limitations with Galera Cluster Monitoring
|
||||
# Monitor limitations
|
||||
|
||||
A server can only be monitored by one monitor. If multiple monitors monitor the
|
||||
same server, the state of the server is non-deterministic.
|
||||
|
||||
## Limitations with Galera Cluster Monitoring (galeramon)
|
||||
|
||||
The default master selection is based only on MIN(wsrep_local_index). This
|
||||
can be influenced with the server priority mechanic described in the
|
||||
[Galera Monitor](../Monitors/Galera-Monitor.md) manual.
|
||||
|
||||
## Limitations in the connection router
|
||||
# Router limitations
|
||||
|
||||
## Limitations in the connection router (readconnroute)
|
||||
|
||||
* If Master changes (ie. new Master promotion) during current connection
|
||||
the router cannot check the change.
|
||||
|
||||
* Sending of binary data with LOAD DATA LOCAL INFILE is not supported
|
||||
|
||||
## Limitations in the Read/Write Splitter
|
||||
## Limitations in the Read/Write Splitter (readwritesplit)
|
||||
|
||||
Read queries are routed to the master server in the following situations:
|
||||
|
||||
@ -42,6 +65,20 @@ Read queries are routed to the master server in the following situations:
|
||||
* if there are multiple statements inside one query e.g.
|
||||
`INSERT INTO ... ; SELECT LAST_INSERT_ID();`
|
||||
|
||||
### Backend write timeout handling
|
||||
|
||||
The backend connections opened by the readwritesplit will not be kept alive if
|
||||
they aren't used. To keep all of the connections alive, a session command must
|
||||
be periodically executed (for example `SET @a = 1;`).
|
||||
|
||||
If the backend server is configured with a low
|
||||
[wait_timeout](https://mariadb.com/kb/en/mariadb/server-system-variables/#wait_timeout),
|
||||
it is possible that connections get closed during long sessions. It is
|
||||
recommended to set the wait_timeout to a high value and let MaxScale handle the
|
||||
client timeouts. This can be achieved by using the
|
||||
[_connection_timeout_](../Getting-Started/Configuration-Guide.md#connection_timeout)
|
||||
parameter for the service.
|
||||
|
||||
### Limitations in multi-statement handling
|
||||
|
||||
When a multi-statement query is executed through the readwritesplit
|
||||
@ -163,21 +200,7 @@ possible that a slave fails to execute something because of some
|
||||
non-fatal, temporary failure, while the execution of the same command
|
||||
succeeds in other backends.
|
||||
|
||||
## Authentication Related Limitations
|
||||
|
||||
* MariaDB MaxScale can not manage authentication that uses wildcard matching in hostnames
|
||||
in the mysql.user table of the backend database. The only wildcards that can be
|
||||
used are in IP address entries.
|
||||
|
||||
* MySQL old style passwords are not supported. MySQL versions 4.1 and newer use
|
||||
a new authentication protocol which does not support pre-4.1 style passwords.
|
||||
|
||||
* When users have different passwords based on the host from which they connect
|
||||
MariaDB MaxScale is unable to determine which password it should use to connect to the
|
||||
backend database. This results in failed connections and unusable usernames
|
||||
in MariaDB MaxScale.
|
||||
|
||||
## Schemarouter limitations
|
||||
## Schemarouter limitations (schemarouter)
|
||||
|
||||
The schemarouter router currently has some limitations due to the nature of
|
||||
the sharding implementation and the way the session variables are detected
|
||||
@ -206,12 +229,7 @@ and routed. Here is a list of the current limitations.
|
||||
the query will be routed to the first available server. This possibly
|
||||
returns an error about database rights instead of a missing database.
|
||||
|
||||
## Dbfwfilter limitations
|
||||
|
||||
The Database Firewall filter does not support multi-statements. Using them
|
||||
will result in an error being sent to the client.
|
||||
|
||||
## Avrorouter limitations
|
||||
## Avrorouter limitations (avrorouter)
|
||||
|
||||
The avrorouter does not support the following data types and conversions.
|
||||
|
||||
@ -221,3 +239,30 @@ The avrorouter does not support the following data types and conversions.
|
||||
|
||||
The avrorouter does not do any crash recovery. This means that the avro files
|
||||
need to be truncated to valid block lengths before starting the avrorouter.
|
||||
|
||||
# Authenticator limitations
|
||||
|
||||
## MySQL Authentication Related Limitations (MySQLAuth)
|
||||
|
||||
* MariaDB MaxScale can not manage authentication that uses wildcard matching in hostnames
|
||||
in the mysql.user table of the backend database. The only wildcards that can be
|
||||
used are in IP address entries.
|
||||
|
||||
* MySQL old style passwords are not supported. MySQL versions 4.1 and newer use
|
||||
a new authentication protocol which does not support pre-4.1 style passwords.
|
||||
|
||||
* When users have different passwords based on the host from which they connect
|
||||
MariaDB MaxScale is unable to determine which password it should use to connect to the
|
||||
backend database. This results in failed connections and unusable usernames
|
||||
in MariaDB MaxScale.
|
||||
|
||||
# Filter limitations
|
||||
|
||||
Filters are not guaranteed to receive complete MySQL packets if they are used
|
||||
with the readconnroute router. This can be fixed by using the readwritesplit
|
||||
router.
|
||||
|
||||
## Database Firewall limitations (dbfwfilter)
|
||||
|
||||
The Database Firewall filter does not support multi-statements. Using them
|
||||
will result in an error being sent to the client.
|
||||
|
@ -0,0 +1,56 @@
|
||||
# Installing MariaDB MaxScale using a tarball
|
||||
|
||||
MariaDB MaxScale is also made available as a tarball, which is named like `maxscale-X.Y.X.tar.gz` where `X.Y.Z` is the same as the corresponding version, e.g. `maxscale-2.0.1.tar.gz`.
|
||||
|
||||
The tarball has been built with the assumption that it will be installed in `/usr/local`. However, it is possible to install it in any directory, but in that case MariaDB MaxScale must be invoked with a flag.
|
||||
|
||||
## Installing as root in `/usr/local`
|
||||
|
||||
If you have root access to the system you probably want to install MariaDB MaxScale under the user and group `maxscale`.
|
||||
|
||||
The required steps are as follows:
|
||||
|
||||
$ sudo groupadd maxscale
|
||||
$ sudo useradd -g maxscale maxscale
|
||||
$ cd /usr/local
|
||||
$ sudo tar -xzvf maxscale-X.Y.Z.tar.gz
|
||||
$ sudo ln -s maxscale-X.Y.Z maxscale
|
||||
$ cd maxscale
|
||||
$ chown -R maxscale var
|
||||
|
||||
Creating the symbolic link is necessary, since MariaDB MaxScale has been built with with the assumption that its base-directory is `/usr/local/maxscale`. It also makes it easy to switch between different versions of MariaDB MaxScale that have been installed side by side in `/usr/local`; just make the symbolic link point to another installation.
|
||||
|
||||
The following step is to create the MariaDB MaxScale configuration file `/etc/maxscale.cnf`. The file `etc/maxscale.cnf.template` can be used as a base. Please refer to [Configuration Guide](Configuration-Guide.md) for details.
|
||||
|
||||
When the configuration file has been created, MariaDB MaxScale can be started.
|
||||
|
||||
$ sudo bin/maxscale --user=maxscale -d
|
||||
|
||||
The `-d` flag causes maxscale _not_ to turn itself into a daemon, which is adviseable the first time MariaDB MaxScale is started, as it makes it easier to spot problems.
|
||||
|
||||
If you want to place the configuration file somewhere else but in `/etc` you can invoke MariaDB MaxScale with the `--config` flag, for instance, `--config=/usr/local/maxscale/etc/maxscale.cnf`.
|
||||
|
||||
## Installing in any Directory
|
||||
|
||||
Enter a directory where you have the right to create a subdirectory. Then do as follows.
|
||||
|
||||
$ tar -xzvf maxscale-X.Y.Z.tar.gz
|
||||
|
||||
The next step is to create the MaxScale configuration file `maxscale-X.Y.Z/etc/maxscale.cnf`. The file `maxscale-X.Y.Z/etc/maxscale.cnf.template` can be used as a base. Please refer to [Configuration Guide](Configuration-Guide.md) for details.
|
||||
|
||||
When the configuration file has been created, MariaDB MaxScale can be started.
|
||||
|
||||
$ cd maxscale-X.Y.Z
|
||||
$ LD_LIBRARY_PATH=lib/maxscale bin/maxscale -d --basedir=.
|
||||
|
||||
With the flag `--basedir`, MariaDB MaxScale is told where the `bin`, `etc`, `lib`
|
||||
and `var` directories are found. Unless it is specified, MariaDB MaxScale assumes
|
||||
the directories are found in `/usr/local/maxscale` and the configuration
|
||||
file in `/etc`.
|
||||
|
||||
It is also possible to specify the directories and the location of the
|
||||
configuration file individually. Invoke MaxScale like
|
||||
|
||||
$ LD_LIBRARY_PATH=lib/maxscale bin/maxscale --help
|
||||
|
||||
to find out the appropriate flags.
|
@ -34,6 +34,10 @@ service maxscale start
|
||||
|
||||
An example configuration file is installed into the `/etc/` folder. This file should be changed according to your needs.
|
||||
|
||||
## Install MariaDB MaxScale Using a Tarball
|
||||
|
||||
MaxScale can also be installed using a tarball. That may be required if you are using a Linux distribution for which there exist no installation package or if you want to install many different MaxScale versions side by side. For instructions on how to do that, please refer to [Install MariaDB MaxScale using a Tarball](Install-MariaDB-MaxScale-Using-a-Tarball.md).
|
||||
|
||||
## Building MariaDB MaxScale From Source Code
|
||||
|
||||
Alternatively you may download the MariaDB MaxScale source and build your own binaries. To do this, refer to the separate document [Building MariaDB MaxScale from Source Code](Building-MaxScale-from-Source-Code.md)
|
||||
|
@ -125,3 +125,8 @@ Example:
|
||||
```
|
||||
QUERY-TRANSACTION 0-14-1245
|
||||
```
|
||||
|
||||
## Example Client
|
||||
|
||||
MaxScale includes an example CDC client application written in Python 3. You can
|
||||
find the source code for it [in the MaxScale repository](https://github.com/mariadb-corporation/MaxScale/tree/2.0/server/modules/protocol/examples/cdc.py).
|
||||
|
@ -21,16 +21,15 @@ password=cdc_password
|
||||
## Creating new CDC users
|
||||
|
||||
```
|
||||
bash$ cdc_users [-h] USER PASSWORD
|
||||
bash$ cdc_users.py [-h] USER PASSWORD
|
||||
```
|
||||
|
||||
The output of this command should be appended to the _cdcusers_ file at
|
||||
`/var/lib/maxscale/<service name>/`.
|
||||
|
||||
```
|
||||
bash$ cdc_users user1 pass1 >> /var/lib/maxscale/avro-service/cdcusers
|
||||
bash$ cdc_users.py user1 pass1 >> /var/lib/maxscale/avro-service/cdcusers
|
||||
```
|
||||
|
||||
Users can be deleted by removing the related rows in 'cdcusers' file. For
|
||||
more details on the format of the _cdcusers_ file, read the [CDC Protocol documentation](CDC.md).
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
# The Maxscale Administrative & Monitoring Client Application
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Configuring MariaDB MaxScale for MaxAdmin](#configuring)
|
||||
- [Running MaxAdmin](#running)
|
||||
- [Working With Administration Interface Users](#interface)
|
||||
- [Getting Help](#help)
|
||||
@ -14,7 +15,6 @@
|
||||
- [Working with Monitors](#monitors)
|
||||
- [MariaDB MaxScale Status Commands](#statuscommands)
|
||||
- [Administration Commands](#admincommands)
|
||||
- [Configuring MariaDB MaxScale to Accept MaxAdmin Connections](#connections)
|
||||
- [Tuning MariaDB MaxScale](#tuning)
|
||||
|
||||
<a name="overview"></a>
|
||||
@ -30,53 +30,145 @@ MaxAdmin supports
|
||||
|
||||
* Execution of command scripts
|
||||
|
||||
<a name="configuring"></a>
|
||||
# Configuring MariaDB MaxScale for MaxAdmin
|
||||
|
||||
In order to be able to use MaxAdmin, MariaDB MaxScale must be configured for it.
|
||||
|
||||
There are two ways MaxAdmin can connect to to MaxScale.
|
||||
|
||||
* Using a Unix domain socket.
|
||||
* Using a hostname and port.
|
||||
|
||||
The first alternative is introduced in MaxScale 2.0 and is the secure and
|
||||
recommended way. The second alternative is available for backward compatibility,
|
||||
but is _insecure_ and **deprecated** and _will be removed in a future version of
|
||||
MaxScale_.
|
||||
|
||||
An example configuration looks as follows:
|
||||
|
||||
```
|
||||
[MaxAdmin]
|
||||
type=service
|
||||
router=cli
|
||||
|
||||
[MaxAdmin Unix Listener]
|
||||
type=listener
|
||||
service=MaxAdmin
|
||||
protocol=maxscaled
|
||||
socket=default
|
||||
|
||||
[MaxAdmin Inet Listener]
|
||||
type=listener
|
||||
service=MaxAdmin
|
||||
protocol=maxscaled
|
||||
address=localhost
|
||||
port=6603
|
||||
```
|
||||
|
||||
In the configuration above, two listeners are created; one listening on the default
|
||||
Unix domain socket and one listening on the default port.
|
||||
|
||||
Which approach is used has other implications than just how the communication between
|
||||
MaxAdmin and MariaDB MaxScale is handled. In the former case, the authorization is
|
||||
based upon the Linux identity and in the latter case on explicitly created user
|
||||
accounts that have **no** relationship to the Linux accounts.
|
||||
|
||||
Note that if the socket path or port are changed, then MaxAdmin has to be invoked
|
||||
with `-S` or `-P` respectively.
|
||||
|
||||
<a name="running"></a>
|
||||
# Running MaxAdmin
|
||||
|
||||
Depending on whether MariaDB MaxScale has been configured to use Unix domain sockets
|
||||
or internet sockets, MaxAdmin needs to be invoked slightly differently.
|
||||
|
||||
If Unix domain sockets are used, then MaxAdmin needs no additional arguments:
|
||||
|
||||
alice@host$ maxadmin
|
||||
MaxAdmin>
|
||||
|
||||
The above implies that the Linux user _alice_ has been enabled to use MaxAdmin.
|
||||
|
||||
If internet sockets are used, then either the host, port, user or password has
|
||||
to be specified explicitly:
|
||||
|
||||
alice@host$ maxadmin -u maxscale-admin
|
||||
Password:
|
||||
MaxScale>
|
||||
|
||||
If Unix domain sockets are used, then initially only `root` has access. MaxAdmin
|
||||
usage can subsequently be enabled for other Linux users.
|
||||
|
||||
The MaxAdmin client application may be run in two different modes, either as an interactive command shell for executing commands against MariaDB MaxScale or by passing commands on the MaxAdmin command line itself.
|
||||
|
||||
<a name="interface"></a>
|
||||
# Working With Administration Interface Users
|
||||
|
||||
MaxScale communicates with MariaDB MaxScale using UNIX domain sockets, which means that it can only be used on the very host where MariaDB MaxScale is running. Initially MaxAdmin can connect only when run as `root`. Other Linux users can subsequently be allowed access.
|
||||
|
||||
**NOTE**: Remote access with -h and -P options is no longer supported.
|
||||
|
||||
## What Users Have Been Defined?
|
||||
|
||||
In order to see the current users (UNIX users) that have been defined for the administration interface use the command _show users_.
|
||||
In order to see the Linux users for whom MaxAdmin usage has been enabled and
|
||||
any explicitly created accounts, use the command _show users_.
|
||||
|
||||
MaxScale> show users
|
||||
Administration interface users:
|
||||
User names: vilho, dba, massi, mark
|
||||
Enabled Linux accounts (secure) : alice, bob, cecil
|
||||
Created network accounts (insecure): maxscale-admin
|
||||
MaxScale>
|
||||
|
||||
Please note `root` will not be shown.
|
||||
Please note that `root` will not be shown.
|
||||
|
||||
MaxScale> show users
|
||||
Administration interface users:
|
||||
No administration users have been defined.
|
||||
MaxScale>
|
||||
## Enabling a Linux account
|
||||
|
||||
To enable MaxAdmin usage for a particular Linux account, use the command _enable account_.
|
||||
This command is passed a user name, which should be the same as that of an existing Linux user.
|
||||
|
||||
MaxScale> enable account bob
|
||||
|
||||
Note that it is not checked that the provided name indeed corresponds to an existing
|
||||
Linux account, so it is possible to enable an account that does not exist yet.
|
||||
|
||||
Note also that it is possible to enable a Linux account irrespective of how MaxAdmin
|
||||
has connected to MariaDB MaxScale. That is, the command is not restricted to MaxAdmin
|
||||
users connecting over a Unix domain socket.
|
||||
|
||||
## Disabling a Linux account
|
||||
|
||||
To disable MaxAdmin usage for a particular Linux account, use the command _disable account_.
|
||||
This command is passed a user name, which should be a Linux user for whom MaxAdmin usage
|
||||
earlier has been enabled.
|
||||
|
||||
MaxScale> disable account bob
|
||||
|
||||
Note also that it is possible to disable a Linux account irrespective of how MaxAdmin
|
||||
has connected to MariaDB MaxScale. That is, the command is not restricted to MaxAdmin
|
||||
users connecting over a Unix domain socket.
|
||||
|
||||
Note that it is possible to disable the current user, but that will only affect the
|
||||
next attempt to use MaxAdmin. `root` cannot be removed.
|
||||
|
||||
## Add A New User
|
||||
|
||||
To add a new administrative user to the MariaDB MaxScale server use the command _add user_. This command is passed a user name, which should be the same as that of an existing Linux user.
|
||||
To add a new MaxAdmin user to be used when MaxAdmin connects over an internet socket,
|
||||
use the command _add user_. This command is passed a user name and a password.
|
||||
|
||||
MaxScale> add user maria
|
||||
User maria has been successfully added.
|
||||
MaxScale> add user maxscale-admin secretpwd
|
||||
User maxscale-admin has been successfully added.
|
||||
MaxScale>
|
||||
|
||||
Note that a user that is given the rights to use MaxAdmin, has the very same rights `root` has, including adding and removing users.
|
||||
Note that there is no difference in rights between an enabled Linux account and an
|
||||
explicitly created user.
|
||||
|
||||
## Delete A User
|
||||
|
||||
To remove a user the command _remove user_ is used and it is simply invoked with the user to be removed.
|
||||
To remove a user the command _remove user_ is used and it is invoked with the
|
||||
username and password.
|
||||
|
||||
MaxScale> remove user maria
|
||||
User maria has been successfully removed.
|
||||
MaxScale> remove user maxscale-admin secretpwd
|
||||
User maxscale-admin has been successfully removed.
|
||||
MaxScale>
|
||||
|
||||
Note that it is possible to remove the current user, but that will only affect the next attempt to use MaxAdmin. `root` cannot be removed.
|
||||
Note that it is possible to remove the current user, but that will only affect the
|
||||
next attempt to use MaxAdmin.
|
||||
|
||||
# Command Line Switches
|
||||
|
||||
@ -93,6 +185,26 @@ The MaxAdmin command accepts a number of switches
|
||||
<td>--socket=...</td>
|
||||
<td>The UNIX domain socket path that MaxAdmin will use to connect to the MariaDB MaxScale server. If no -S option is given then the default socket path /tmp/maxadmin.sock will be used.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-u user</td>
|
||||
<td>--user=...</td>
|
||||
<td>Sets the username that will be used for the MaxScale connection. If no -u option is passed on the MaxAdmin command line then the default username of ‘admin’ will be used.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-p password</td>
|
||||
<td>--password=...</td>
|
||||
<td>Sets the user password that will be used. If no -p option is passed on the command line then MaxAdmin will prompt for interactive entry of the password.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-h hostname</td>
|
||||
<td>--hostname=...</td>
|
||||
<td>The hostname of the MaxScale server to connect to. If no -h option is passed on the command line then MaxAdmin will attempt to connect to the host ‘localhost’.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-P port</td>
|
||||
<td>--port=...</td>
|
||||
<td>The port that MaxAdmin will use to connect to the MaxScale server. if no -P option is given then the default port of 6603 will be used.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-?</td>
|
||||
<td>--help</td>
|
||||
@ -859,28 +971,6 @@ A command, _reload config_, is available that will cause MariaDB MaxScale to rel
|
||||
|
||||
The MariaDB MaxScale server may be shutdown using the _shutdown maxscale_ command.
|
||||
|
||||
<a name="connections"></a>
|
||||
# Configuring MariaDB MaxScale to Accept MaxAdmin Connections
|
||||
|
||||
In order to enable the use of MaxAdmin, the service CLI with an accompanying listener must be added to the MariaDB MaxScale configuration file.
|
||||
|
||||
The default entries required are shown below.
|
||||
|
||||
[CLI]
|
||||
type=service
|
||||
router=cli
|
||||
|
||||
[CLI Listener]
|
||||
type=listener
|
||||
service=CLI
|
||||
protocol=maxscaled
|
||||
socket=default
|
||||
#socket=/somepath/maxadmin.socket
|
||||
|
||||
**NOTE**: As the protocol maxscaled only supports UNIX domain sockets it is thus not possible to connect remotely to MariaDB MaxScale.
|
||||
|
||||
In the example, the default socket path (/tmp/maxadmin.sock) is used. It can be changed to an specific path and in that case MaxAdmin must be invoked with the -S option.
|
||||
|
||||
<a name="tuning"></a>
|
||||
# Tuning MariaDB MaxScale
|
||||
|
||||
|
@ -126,4 +126,4 @@ The source code of MaxScale is tagged at GitHub with a tag, which is identical
|
||||
with the version of MaxScale. For instance, the tag of version X.Y.Z of MaxScale
|
||||
is X.Y.Z. Further, *master* always refers to the latest released non-beta version.
|
||||
|
||||
The source code is available [here](https://github.com/mariadb-corporation/maxscale-bsl).
|
||||
The source code is available [here](https://github.com/mariadb-corporation/MaxScale).
|
||||
|
130
Documentation/Release-Notes/MaxScale-2.0.1-Release-Notes.md
Normal file
130
Documentation/Release-Notes/MaxScale-2.0.1-Release-Notes.md
Normal file
@ -0,0 +1,130 @@
|
||||
# MariaDB MaxScale 2.0.1 Release Notes
|
||||
|
||||
Release 2.0.1 is a GA release.
|
||||
|
||||
This document describes the changes in release 2.0.1, when compared to
|
||||
[release 2.0.0](MaxScale-2.0.0-Release-Notes.md).
|
||||
|
||||
If you are upgrading from 1.4.3, please also read the release notes
|
||||
of [2.0.0](./MaxScale-2.0.0-Release-Notes.md).
|
||||
|
||||
For any problems you encounter, please consider submitting a bug
|
||||
report at [Jira](https://jira.mariadb.org).
|
||||
|
||||
## Updated Features
|
||||
|
||||
### Starting MariaDB MaxScale
|
||||
|
||||
There is now a new command line parameter `--basedir=PATH` that will
|
||||
cause all directory paths and the location of the configuration file
|
||||
to be defined relative to that path.
|
||||
|
||||
For instance, invoking MariaDB MaxScale like
|
||||
|
||||
$ maxscale --basedir=/path/maxscale
|
||||
|
||||
has the same effect as invoking MariaDB MaxScale like
|
||||
|
||||
$ maxscale --config=/path/maxscale/etc/maxscale.cnf
|
||||
--configdir=/path/maxscale/etc
|
||||
--logdir=/path/maxscale/var/log/maxscale
|
||||
--cachhedir=/path/maxscale/var/cache/maxscale
|
||||
--libdir=/path/maxscale/lib/maxscale
|
||||
--datadir=/path/maxscale/var/lib/maxscale
|
||||
--execdir=/path/maxscale/bin
|
||||
--language=/path/maxscale/var/lib/maxscale
|
||||
--piddir=/path/maxscale/var/run/maxscale
|
||||
|
||||
### Password parameter
|
||||
|
||||
In the configuration entry for a _service_ or _monitor_, the value of
|
||||
the password to be used can now be specified using `password` in addition
|
||||
to `passwd`. The use of the latter will be deprecated and removed in later
|
||||
releases of MaxScale.
|
||||
|
||||
[SomeService]
|
||||
...
|
||||
password=mypasswd
|
||||
|
||||
### Routing hint priority change
|
||||
|
||||
Routing hints now have the highest priority when a routing decision is made. If
|
||||
there is a conflict between the original routing decision made by the
|
||||
readwritesplit and the routing hint attached to the query, the routing hint
|
||||
takes higher priority.
|
||||
|
||||
What this change means is that, if a query would normally be routed to the
|
||||
master but the routing hint instructs the router to route it to the slave, it
|
||||
would be routed to the slave.
|
||||
|
||||
**WARNING**: This change can alter the way some statements are routed and could
|
||||
possibly cause data loss, corruption or inconsisteny. Please consult the [Hint
|
||||
Syntax](../Reference/Hint-Syntax.md) and
|
||||
[ReadWriteSplit](../Routers/ReadWriteSplit.md) documentation before using
|
||||
routing hints.
|
||||
|
||||
### MaxAdmin Usage
|
||||
|
||||
In 2.0.0 (Beta), the authentication mechanism of MaxAdmin was completely
|
||||
changed, so that MaxAdmin could only connect to MaxScale using a Unix domain
|
||||
socket, thus _only when run on the same host_, and authorization was based
|
||||
on the Unix identity. Remote access was no longer supported.
|
||||
|
||||
To the user this was visible so that while you in 1.4.3 had to provide
|
||||
a password when starting _maxadmin_ and when adding a user
|
||||
```
|
||||
user@host $ maxadmin -p password
|
||||
MaxAdmin> add user john johns-password
|
||||
```
|
||||
in 2.0.0 (Beta), where only Unix domain sockets could be used, you did not
|
||||
have to provide a password neither when starting _maxadmin_, nor when adding
|
||||
users
|
||||
```
|
||||
user@host $ maxadmin
|
||||
MaxAdmin> add user john
|
||||
```
|
||||
as the MaxScale user corresponded to a Unix user, provided the Linux user
|
||||
had been added as a user of MaxScale.
|
||||
|
||||
In 2.0.1 (GA) this has been changed so that the 1.4.3 behaviour is intact
|
||||
but _deprecated_, and the 2.0.0 (Beta) behaviour is exposed using a new set
|
||||
of commands:
|
||||
```
|
||||
MaxAdmin> enable account alice
|
||||
MaxAdmin> disable account alice
|
||||
```
|
||||
Note that the way you need to invoke _maxadmin_ depends upon how MariaDB
|
||||
MaxScale has been configued.
|
||||
|
||||
Please consult
|
||||
[MaxAdmin documentation](../Reference/MaxAdmin.md) for more details.
|
||||
|
||||
## Bug fixes
|
||||
|
||||
[Here is a list of bugs fixed since the release of MaxScale 2.0.1.](https://jira.mariadb.org/issues/?jql=project%20%3D%20MXS%20AND%20issuetype%20%3D%20Bug%20AND%20status%20%3D%20Closed%20AND%20fixVersion%20in%20(2.0.0%2C%202.0.1)%20AND%20resolved%20%3E%3D%20-21d%20ORDER%20BY%20priority%20DESC%2C%20updated%20DESC)
|
||||
|
||||
* [MXS-847](https://jira.mariadb.org/browse/MXS-847): server_down event is executed 8 times due to putting sever into maintenance mode
|
||||
* [MXS-845](https://jira.mariadb.org/browse/MXS-845): "Server down" event is re-triggered after maintenance mode is repeated
|
||||
* [MXS-842](https://jira.mariadb.org/browse/MXS-842): Unexpected / undocumented behaviour when multiple available masters from mmmon monitor
|
||||
* [MXS-846](https://jira.mariadb.org/browse/MXS-846): MMMon: Maintenance mode on slave logs error message every second
|
||||
|
||||
## Known Issues and Limitations
|
||||
|
||||
There are some limitations and known issues within this version of MaxScale.
|
||||
For more information, please refer to the [Limitations](../About/Limitations.md) document.
|
||||
|
||||
## Packaging
|
||||
|
||||
RPM and Debian packages are provided for the Linux distributions supported
|
||||
by MariaDB Enterprise.
|
||||
|
||||
Packages can be downloaded [here](https://mariadb.com/resources/downloads).
|
||||
|
||||
## Source Code
|
||||
|
||||
The source code of MaxScale is tagged at GitHub with a tag, which is identical
|
||||
with the version of MaxScale. For instance, the tag of version X.Y.Z of MaxScale
|
||||
is maxscale-X.Y.Z. Further, *master* always refers to the latest released
|
||||
non-beta version.
|
||||
|
||||
The source code is available [here](https://github.com/mariadb-corporation/MaxScale).
|
@ -152,16 +152,16 @@ conversion process, delete these two files and restart MaxScale.
|
||||
|
||||
# Example Client
|
||||
|
||||
The avrorouter comes with an example client program, _cdc_, written in Python 3.
|
||||
The avrorouter comes with an example client program, _cdc.py_, written in Python 3.
|
||||
This client can connect to a MaxScale configured with the CDC protocol and the
|
||||
avrorouter.
|
||||
|
||||
Before using this client, you will need to install the Python 3 interpreter and
|
||||
add users to the service with the _cdc_users_ script. Fore more details about
|
||||
add users to the service with the _cdc_users.py_ script. Fore more details about
|
||||
the user creation, please refer to the [CDC Protocol](../Protocols/CDC.md)
|
||||
and [CDC Users](../Protocols/CDC_users.md) documentation.
|
||||
|
||||
Read the output of `cdc --help` for a full list of supported options
|
||||
Read the output of `cdc.py --help` for a full list of supported options
|
||||
and a short usage description of the client program.
|
||||
|
||||
# Avro Schema Generator
|
||||
@ -206,27 +206,27 @@ protocol=CDC
|
||||
port=4001
|
||||
```
|
||||
|
||||
Here is an example how you can query for data in JSON format using the _cdc_ Python script.
|
||||
Here is an example how you can query for data in JSON format using the _cdc.py_ Python script.
|
||||
It queries the table _test.mytable_ for all change records.
|
||||
|
||||
```
|
||||
cdc --user=myuser --password=mypasswd --host=127.0.0.1 --port=4001 test.mytable
|
||||
cdc.py --user=myuser --password=mypasswd --host=127.0.0.1 --port=4001 test.mytable
|
||||
```
|
||||
|
||||
You can then combine it with the _cdc_kafka_producer_ to publish these change records to a Kafka broker.
|
||||
You can then combine it with the _cdc_kafka_producer.py_ to publish these change records to a Kafka broker.
|
||||
|
||||
```
|
||||
cdc --user=myuser --password=mypasswd --host=127.0.0.1 --port=4001 test.mytable | cdc_kafka_producer --kafka-broker 127.0.0.1:9092 --kafka-topic test.mytable
|
||||
cdc.py --user=myuser --password=mypasswd --host=127.0.0.1 --port=4001 test.mytable | cdc_kafka_producer.py --kafka-broker 127.0.0.1:9092 --kafka-topic test.mytable
|
||||
```
|
||||
|
||||
For more information on how to use these scripts, see the output of `cdc -h` and `cdc_kafka_producer -h`.
|
||||
For more information on how to use these scripts, see the output of `cdc.py -h` and `cdc_kafka_producer.py -h`.
|
||||
|
||||
# Building Avrorouter
|
||||
|
||||
To build the avrorouter from source, you will need the [Avro C](https://avro.apache.org/docs/current/api/c/)
|
||||
library, liblzma and sqlite3 development headers. When configuring MaxScale with
|
||||
CMake, you will need to add `-DBUILD_AVRO=Y -DBUILD_CDC=Y` to build the
|
||||
avrorouter and the CDC protocol module.
|
||||
library, liblzma, [the Jansson library](http://www.digip.org/jansson/) and sqlite3 development headers. When
|
||||
configuring MaxScale with CMake, you will need to add `-DBUILD_AVRO=Y
|
||||
-DBUILD_CDC=Y` to build the avrorouter and the CDC protocol module.
|
||||
|
||||
For more details about building MaxScale from source, please refer to the
|
||||
[Building MaxScale from Source Code](../Getting-Started/Building-MaxScale-from-Source-Code.md) document.
|
||||
|
@ -1,10 +1,13 @@
|
||||
# CLI
|
||||
|
||||
The command line interface as used by `maxadmin`. This is a variant of the debugcli that is built slightly differently so that it may be accessed by the client application `maxadmin`. The CLI requires the use of the `maxscaled` protocol.
|
||||
The command line interface as used by `maxadmin`. The _CLI_ router requires the use
|
||||
of the `maxscaled` protocol.
|
||||
|
||||
## Configuration
|
||||
|
||||
There are two components to the definition required in order to run the command line interface to use with MaxAdmin; a service and a listener.
|
||||
Two components are required in order to run the command line interface for use with
|
||||
_maxadmin_; a service and a listener. The listener may either use a Unix domain socket
|
||||
or an internet socket.
|
||||
|
||||
The default entries required are shown below.
|
||||
|
||||
@ -13,9 +16,34 @@ The default entries required are shown below.
|
||||
type=service
|
||||
router=cli
|
||||
|
||||
[CLI Listener]
|
||||
# Unix Domain Socket
|
||||
[CLI Unix Listener]
|
||||
type=listener
|
||||
service=CLI
|
||||
protocol=maxscaled
|
||||
socket=default
|
||||
|
||||
# Internet Socket
|
||||
[CLI Inet Listener]
|
||||
type=listener
|
||||
service=CLI
|
||||
protocol=maxscaled
|
||||
address=localhost
|
||||
port=6603
|
||||
```
|
||||
In the example above, two listeners have been specified; one that listens on the
|
||||
default Unix domain socket and one that listens on the default port. In the latter
|
||||
case, if the `address=` entry is removed, connections are allowed from any machine
|
||||
on your network.
|
||||
|
||||
In the former case, if the value of `socket` is changed and in the latter case,
|
||||
if the value of `port` is changed, _maxadmin_ must be invoked with the `-S` and
|
||||
`-P` options respectively.
|
||||
|
||||
Note that if Unix domain sockets are used, the connection is secure, but _maxadmin_
|
||||
can only be used on the same host where MariaDB MaxScale runs. If internet sockets
|
||||
are used, the connection is _inherently insecure_ but _maxadmin_ can be used from
|
||||
another host than the one where MariaDB MaxScale runs.
|
||||
|
||||
Note that the latter approach is **deprecated** and will be removed in a future
|
||||
version of MariaDB MaxScale.
|
||||
|
@ -30,6 +30,11 @@ running|A server that is up and running. All servers that MariaDB MaxScale can c
|
||||
|
||||
If no `router_options` parameter is configured in the service definition, the router will use the default value of `running`. This means that it will load balance connections across all running servers defined in the `servers` parameter of the service.
|
||||
|
||||
When a connection is being created and the candidate server is being chosen, the
|
||||
list of servers is processed in from first entry to last. This means that if two
|
||||
servers with equal weight and status are found, the one that's listed first in
|
||||
the _servers_ parameter for the service is chosen.
|
||||
|
||||
## Limitations
|
||||
|
||||
For a list of readconnroute limitations, please read the [Limitations](../About/Limitations.md) document.
|
||||
|
@ -117,9 +117,6 @@ The final stage in the configuration is to add the option service which is used
|
||||
protocol=maxscaled
|
||||
socket=default
|
||||
|
||||
**Note**: maxscaled protocol supports only UNIX domain sockets and in the example default is set.
|
||||
Changing it requires maxadmin to use -S with the new path. Default /tmp/maxadmin.sock is for both maxadmin and maxscaled.
|
||||
|
||||
## Starting MariaDB MaxScale
|
||||
|
||||
Upon completion of the configuration process MariaDB MaxScale is ready to be started for the first time. This may either be done manually by running the maxscale command or via the service interface.
|
||||
|
@ -147,9 +147,6 @@ protocol=maxscaled
|
||||
socket=default
|
||||
```
|
||||
|
||||
**Note**: maxscaled protocol supports only UNIX domain sockets and in the example default is set.
|
||||
Changing it requires maxadmin to use -S with the new path. Default /tmp/maxadmin.sock is for both maxadmin and maxscaled.
|
||||
|
||||
## Starting MariaDB MaxScale
|
||||
|
||||
Upon completion of the configuration process MariaDB MaxScale is ready to be started for the first time. This may either be done manually by running the maxscale command or via the service interface.
|
||||
|
@ -174,8 +174,6 @@ service=CLI
|
||||
protocol=maxscaled
|
||||
socket=default
|
||||
```
|
||||
**Note**: maxscaled protocol supports only UNIX domain sockets and in the example default is set.
|
||||
Changing it requires maxadmin to use -S with the new path. Default /tmp/maxadmin.sock is for both maxadmin and maxscaled.
|
||||
|
||||
# Starting MariaDB MaxScale
|
||||
|
||||
|
@ -133,8 +133,6 @@ service=CLI
|
||||
protocol=maxscaled
|
||||
socket=default
|
||||
```
|
||||
**Note**: maxscaled protocol supports only UNIX domain sockets and in the example default is set.
|
||||
Changing it requires maxadmin to use -S with the new path. Default /tmp/maxadmin.sock is for both maxadmin and maxscaled.
|
||||
|
||||
# Starting MariaDB MaxScale
|
||||
|
||||
|
@ -62,8 +62,7 @@ service=AdminInterface
|
||||
protocol=maxscaled
|
||||
socket=default
|
||||
```
|
||||
**Note**: maxscaled protocol supports only UNIX domain sockets and in the example default is set.
|
||||
Changing it requires maxadmin to use -S with the new path. Default /tmp/maxadmin.sock is for both maxadmin and maxscaled.
|
||||
|
||||
## Prepare Nagios configuration files.
|
||||
|
||||
Assuming Nagios installed on a separated server and the plugins are in /usr/lib64/nagios/plugins and configuration files are in /etc/nagios:
|
||||
|
@ -208,7 +208,7 @@ router=cli
|
||||
type=listener
|
||||
service=MaxAdmin Service
|
||||
protocol=maxscaled
|
||||
port=6603
|
||||
socket=default
|
||||
```
|
||||
|
||||
Now we have created the MariaDB MaxScale configuration file and all we need to do is to save
|
||||
|
@ -12,14 +12,18 @@ configuration file.
|
||||
|
||||
## MaxAdmin
|
||||
|
||||
The way a user of MaxAdmin is authenticated has been completely changed.
|
||||
In 2.0, MaxAdmin can only connect to MariaDB MaxScale using a domain socket, thus
|
||||
_only when run on the same host_, and authorization is based upon the UNIX
|
||||
identity. Remote access is no longer supported.
|
||||
The default way the communication between MaxAdmin and MariaDB MaxScale is
|
||||
handled has been changed from an internet socket to a Unix domain socket.
|
||||
The former alternative is still available but has been _deprecated_.
|
||||
|
||||
When 2.0 has been installed, MaxAdmin can only be used by `root` and
|
||||
other users must be added anew. Please consult
|
||||
[MaxAdmin documentation](../Reference/MaxAdmin.md) for more details.
|
||||
If no arguments are given to MaxAdmin, it will attempt to connect to
|
||||
MariaDB MaxScale using a Unix domain socket. After the upgrade you will
|
||||
need to provide at least one internet socket related flag - `-h`, `-P`,
|
||||
`-u` or `-p` - to force MaxAdmin to use the internet socket approach.
|
||||
|
||||
E.g.
|
||||
|
||||
user@host $ maxadmin -u admin
|
||||
|
||||
## MySQL Monitor
|
||||
|
||||
|
@ -27,6 +27,7 @@
|
||||
* @endverbatim
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
@ -45,6 +46,7 @@
|
||||
#include <locale.h>
|
||||
#include <errno.h>
|
||||
#include <getopt.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <version.h>
|
||||
|
||||
@ -60,15 +62,21 @@
|
||||
#define STRERROR_BUFLEN 512
|
||||
#endif
|
||||
|
||||
static int connectMaxScale(char *socket);
|
||||
static int setipaddress(struct in_addr *a, char *p);
|
||||
static int authMaxScale(int so);
|
||||
#define MAX_PASSWORD_LEN 80
|
||||
|
||||
static int connectUsingUnixSocket(const char *socket);
|
||||
static int connectUsingInetSocket(const char *hostname, const char *port,
|
||||
const char *user, const char* password);
|
||||
static int setipaddress(struct in_addr *a, const char *p);
|
||||
static bool authUnixSocket(int so);
|
||||
static bool authInetSocket(int so, const char *user, const char *password);
|
||||
static int sendCommand(int so, char *cmd);
|
||||
static void DoSource(int so, char *cmd);
|
||||
static void DoUsage(const char*);
|
||||
static int isquit(char *buf);
|
||||
static void PrintVersion(const char *progname);
|
||||
static void read_inifile(char **, int*);
|
||||
static bool getPassword(char *password, size_t length);
|
||||
|
||||
#ifdef HISTORY
|
||||
|
||||
@ -79,10 +87,15 @@ prompt(EditLine *el __attribute__((__unused__)))
|
||||
|
||||
return prompt;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static struct option long_options[] =
|
||||
{
|
||||
{"host", required_argument, 0, 'h'},
|
||||
{"user", required_argument, 0, 'u'},
|
||||
{"password", required_argument, 0, 'p'},
|
||||
{"port", required_argument, 0, 'P'},
|
||||
{"socket", required_argument, 0, 'S'},
|
||||
{"version", no_argument, 0, 'v'},
|
||||
{"help", no_argument, 0, '?'},
|
||||
@ -90,6 +103,10 @@ static struct option long_options[] =
|
||||
{0, 0, 0, 0}
|
||||
};
|
||||
|
||||
#define MAXADMIN_DEFAULT_HOST "localhost"
|
||||
#define MAXADMIN_DEFAULT_PORT "6603"
|
||||
#define MAXADMIN_DEFAULT_USER "admin"
|
||||
|
||||
/**
|
||||
* The main for the maxadmin client
|
||||
*
|
||||
@ -112,7 +129,12 @@ main(int argc, char **argv)
|
||||
#else
|
||||
char buf[1024];
|
||||
#endif
|
||||
char *conn_socket = MAXADMIN_DEFAULT_SOCKET;
|
||||
char *hostname = NULL;
|
||||
char *port = NULL;
|
||||
char *user = NULL;
|
||||
char *passwd = NULL;
|
||||
char *conn_socket = NULL;
|
||||
char *default_socket = MAXADMIN_DEFAULT_SOCKET;
|
||||
int use_emacs = 0;
|
||||
int so;
|
||||
int option_index = 0;
|
||||
@ -120,11 +142,28 @@ main(int argc, char **argv)
|
||||
|
||||
read_inifile(&conn_socket, &use_emacs);
|
||||
|
||||
while ((c = getopt_long(argc, argv, "S:v?e",
|
||||
while ((c = getopt_long(argc, argv, "h:p:P:u:S:v?e",
|
||||
long_options, &option_index)) >= 0)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case 'h':
|
||||
hostname = strdup(optarg);
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
passwd = strdup(optarg);
|
||||
memset(optarg, '\0', strlen(optarg));
|
||||
break;
|
||||
|
||||
case 'P':
|
||||
port = strdup(optarg);
|
||||
break;
|
||||
|
||||
case 'u':
|
||||
user = strdup(optarg);
|
||||
break;
|
||||
|
||||
case 'S':
|
||||
conn_socket = strdup(optarg);
|
||||
break;
|
||||
@ -138,22 +177,72 @@ main(int argc, char **argv)
|
||||
break;
|
||||
|
||||
case '?':
|
||||
DoUsage(*argv);
|
||||
DoUsage(argv[0]);
|
||||
exit(optopt ? EXIT_FAILURE : EXIT_SUCCESS);
|
||||
}
|
||||
}
|
||||
|
||||
/* Connet to MaxScale using UNIX domain socket */
|
||||
if ((so = connectMaxScale(conn_socket)) == -1)
|
||||
if ((hostname || port || user || passwd) && (conn_socket))
|
||||
{
|
||||
exit(1);
|
||||
// Either socket or any parameters related to hostname/port.
|
||||
DoUsage(argv[0]);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* Check for successful authentication */
|
||||
if (!authMaxScale(so))
|
||||
if (hostname || port || user || passwd)
|
||||
{
|
||||
fprintf(stderr, "Failed to connect to MaxScale, incorrect username.\n");
|
||||
exit(1);
|
||||
assert(!conn_socket);
|
||||
|
||||
if (!hostname)
|
||||
{
|
||||
hostname = MAXADMIN_DEFAULT_HOST;
|
||||
}
|
||||
|
||||
if (!port)
|
||||
{
|
||||
port = MAXADMIN_DEFAULT_PORT;
|
||||
}
|
||||
|
||||
if (!user)
|
||||
{
|
||||
user = MAXADMIN_DEFAULT_USER;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!conn_socket)
|
||||
{
|
||||
conn_socket = MAXADMIN_DEFAULT_SOCKET;
|
||||
}
|
||||
}
|
||||
|
||||
assert(!((hostname || port) && conn_socket));
|
||||
|
||||
if (conn_socket)
|
||||
{
|
||||
if ((so = connectUsingUnixSocket(conn_socket)) == -1)
|
||||
{
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
char password[MAX_PASSWORD_LEN];
|
||||
|
||||
if (passwd == NULL)
|
||||
{
|
||||
if (!getPassword(password, MAX_PASSWORD_LEN))
|
||||
{
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
passwd = password;
|
||||
}
|
||||
|
||||
if ((so = connectUsingInetSocket(hostname, port, user, passwd)) == -1)
|
||||
{
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
if (optind < argc)
|
||||
@ -309,44 +398,114 @@ main(int argc, char **argv)
|
||||
* @return The connected socket or -1 on error
|
||||
*/
|
||||
static int
|
||||
connectMaxScale(char *conn_socket)
|
||||
connectUsingUnixSocket(const char *conn_socket)
|
||||
{
|
||||
struct sockaddr_un local_addr;
|
||||
int so;
|
||||
int keepalive = 1;
|
||||
int optval = 1;
|
||||
int so = -1;
|
||||
|
||||
if ((so = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
|
||||
if ((so = socket(AF_UNIX, SOCK_STREAM, 0)) != -1)
|
||||
{
|
||||
struct sockaddr_un local_addr;
|
||||
|
||||
memset(&local_addr, 0, sizeof local_addr);
|
||||
local_addr.sun_family = AF_UNIX;
|
||||
strncpy(local_addr.sun_path, conn_socket, sizeof(local_addr.sun_path) - 1);
|
||||
|
||||
if (connect(so, (struct sockaddr *) &local_addr, sizeof(local_addr)) == 0)
|
||||
{
|
||||
int keepalive = 1;
|
||||
if (setsockopt(so, SOL_SOCKET, SO_KEEPALIVE, &keepalive, sizeof(keepalive)))
|
||||
{
|
||||
fprintf(stderr, "Warning: Could not set keepalive.\n");
|
||||
}
|
||||
|
||||
/* Client is sending connection credentials (Pid, User, Group) */
|
||||
int optval = 1;
|
||||
if (setsockopt(so, SOL_SOCKET, SO_PASSCRED, &optval, sizeof(optval)) == 0)
|
||||
{
|
||||
if (!authUnixSocket(so))
|
||||
{
|
||||
close(so);
|
||||
so = -1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
char errbuf[STRERROR_BUFLEN];
|
||||
fprintf(stderr, "Could not set SO_PASSCRED: %s\n",
|
||||
strerror_r(errno, errbuf, sizeof(errbuf)));
|
||||
close(so);
|
||||
so = -1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
char errbuf[STRERROR_BUFLEN];
|
||||
fprintf(stderr, "Unable to connect to MaxScale at %s: %s\n",
|
||||
conn_socket, strerror_r(errno, errbuf, sizeof(errbuf)));
|
||||
close(so);
|
||||
so = -1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
char errbuf[STRERROR_BUFLEN];
|
||||
fprintf(stderr, "Unable to create socket: %s\n",
|
||||
strerror_r(errno, errbuf, sizeof(errbuf)));
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(&local_addr, 0, sizeof local_addr);
|
||||
local_addr.sun_family = AF_UNIX;
|
||||
strncpy(local_addr.sun_path, conn_socket, sizeof(local_addr.sun_path)-1);
|
||||
return so;
|
||||
}
|
||||
|
||||
if (connect(so, (struct sockaddr *) &local_addr, sizeof(local_addr)) < 0)
|
||||
/**
|
||||
* Connect to the MaxScale server
|
||||
*
|
||||
* @param hostname The hostname to connect to
|
||||
* @param port The port to use for the connection
|
||||
* @return The connected socket or -1 on error
|
||||
*/
|
||||
static int
|
||||
connectUsingInetSocket(const char *hostname, const char *port,
|
||||
const char *user, const char *passwd)
|
||||
{
|
||||
int so;
|
||||
|
||||
if ((so = socket(AF_INET, SOCK_STREAM, 0)) != -1)
|
||||
{
|
||||
struct sockaddr_in addr;
|
||||
|
||||
memset(&addr, 0, sizeof addr);
|
||||
addr.sin_family = AF_INET;
|
||||
setipaddress(&addr.sin_addr, hostname);
|
||||
addr.sin_port = htons(atoi(port));
|
||||
|
||||
if (connect(so, (struct sockaddr *) &addr, sizeof(addr)) == 0)
|
||||
{
|
||||
int keepalive = 1;
|
||||
if (setsockopt(so, SOL_SOCKET, SO_KEEPALIVE, &keepalive, sizeof(keepalive)))
|
||||
{
|
||||
fprintf(stderr, "Warning: Could not set keepalive.\n");
|
||||
}
|
||||
|
||||
if (!authInetSocket(so, user, passwd))
|
||||
{
|
||||
close(so);
|
||||
so = -1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
char errbuf[STRERROR_BUFLEN];
|
||||
fprintf(stderr, "Unable to connect to MaxScale at %s, %s: %s\n",
|
||||
hostname, port, strerror_r(errno, errbuf, sizeof(errbuf)));
|
||||
close(so);
|
||||
so = -1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
char errbuf[STRERROR_BUFLEN];
|
||||
fprintf(stderr, "Unable to connect to MaxScale at %s: %s\n",
|
||||
conn_socket, strerror_r(errno, errbuf, sizeof(errbuf)));
|
||||
close(so);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (setsockopt(so, SOL_SOCKET, SO_KEEPALIVE, &keepalive, sizeof(keepalive)))
|
||||
{
|
||||
perror("setsockopt");
|
||||
}
|
||||
|
||||
/* Client is sending connection credentials (Pid, User, Group) */
|
||||
if (setsockopt(so, SOL_SOCKET, SO_PASSCRED, &optval, sizeof(optval)) != 0)
|
||||
{
|
||||
fprintf(stderr, "SO_PASSCRED failed\n");
|
||||
return -1;
|
||||
fprintf(stderr, "Unable to create socket: %s\n",
|
||||
strerror_r(errno, errbuf, sizeof(errbuf)));
|
||||
}
|
||||
|
||||
return so;
|
||||
@ -360,7 +519,7 @@ connectMaxScale(char *conn_socket)
|
||||
* @return 1 on success, 0 on failure
|
||||
*/
|
||||
static int
|
||||
setipaddress(struct in_addr *a, char *p)
|
||||
setipaddress(struct in_addr *a, const char *p)
|
||||
{
|
||||
#ifdef __USE_POSIX
|
||||
struct addrinfo *ai = NULL, hint;
|
||||
@ -418,17 +577,84 @@ setipaddress(struct in_addr *a, char *p)
|
||||
* @param so The socket connected to MaxScale
|
||||
* @return Non-zero of succesful authentication
|
||||
*/
|
||||
static int
|
||||
authMaxScale(int so)
|
||||
static bool
|
||||
authUnixSocket(int so)
|
||||
{
|
||||
char buf[MAXADMIN_AUTH_REPLY_LEN];
|
||||
|
||||
if (read(so, buf, MAXADMIN_AUTH_REPLY_LEN) != MAXADMIN_AUTH_REPLY_LEN)
|
||||
{
|
||||
fprintf(stderr, "Could not read authentication response from MaxScale.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return strncmp(buf, MAXADMIN_FAILED_AUTH_MESSAGE, MAXADMIN_AUTH_REPLY_LEN);
|
||||
bool authenticated = (strncmp(buf, MAXADMIN_AUTH_SUCCESS_REPLY, MAXADMIN_AUTH_REPLY_LEN) == 0);
|
||||
|
||||
if (!authenticated)
|
||||
{
|
||||
fprintf(stderr, "Could connect to MaxScale, but was not authorized.\n");
|
||||
}
|
||||
|
||||
return authenticated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform authentication using the maxscaled protocol conventions
|
||||
*
|
||||
* @param so The socket connected to MaxScale
|
||||
* @param user The username to authenticate
|
||||
* @param password The password to authenticate with
|
||||
* @return Non-zero of succesful authentication
|
||||
*/
|
||||
static bool
|
||||
authInetSocket(int so, const char *user, const char *password)
|
||||
{
|
||||
char buf[20];
|
||||
size_t len;
|
||||
|
||||
len = MAXADMIN_AUTH_USER_PROMPT_LEN;
|
||||
if (read(so, buf, len) != len)
|
||||
{
|
||||
fprintf(stderr, "Could not read user prompt from MaxScale.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
len = strlen(user);
|
||||
if (write(so, user, len) != len)
|
||||
{
|
||||
fprintf(stderr, "Could not write user to MaxScale.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
len = MAXADMIN_AUTH_PASSWORD_PROMPT_LEN;
|
||||
if (read(so, buf, len) != len)
|
||||
{
|
||||
fprintf(stderr, "Could not read password prompt from MaxScale.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
len = strlen(password);
|
||||
if (write(so, password, len) != len)
|
||||
{
|
||||
fprintf(stderr, "Could not write password to MaxScale.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
len = MAXADMIN_AUTH_REPLY_LEN;
|
||||
if (read(so, buf, len) != len)
|
||||
{
|
||||
fprintf(stderr, "Could not read authentication response from MaxScale.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool authenticated = (strncmp(buf, MAXADMIN_AUTH_SUCCESS_REPLY, MAXADMIN_AUTH_REPLY_LEN) == 0);
|
||||
|
||||
if (!authenticated)
|
||||
{
|
||||
fprintf(stderr, "Could connect to MaxScale, but was not authorized.\n");
|
||||
}
|
||||
|
||||
return authenticated;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -550,13 +776,27 @@ DoUsage(const char *progname)
|
||||
{
|
||||
PrintVersion(progname);
|
||||
printf("The MaxScale administrative and monitor client.\n\n");
|
||||
printf("Usage: %s [-S socket] [<command file> | <command>]\n\n", progname);
|
||||
printf(" -S|--socket=... The UNIX socket to connect to, The default is\n");
|
||||
printf(" /tmp/maxadmin.sock\n");
|
||||
printf(" -v|--version print version information and exit\n");
|
||||
printf(" -?|--help Print this help text.\n");
|
||||
printf("Usage: %s [(-S socket)|([-u user] [-p password] [-h hostname] [-P port])]"
|
||||
"[<command file> | <command>]\n\n", progname);
|
||||
printf(" -S|--socket=... The UNIX domain socket to connect to, The default is\n");
|
||||
printf(" %s\n", MAXADMIN_DEFAULT_SOCKET);
|
||||
printf(" -u|--user=... The user name to use for the connection, default\n");
|
||||
printf(" is %s.\n", MAXADMIN_DEFAULT_USER);
|
||||
printf(" -p|--password=... The user password, if not given the password will\n");
|
||||
printf(" be prompted for interactively\n");
|
||||
printf(" -h|--host=... The maxscale host to connecto to. The default is\n");
|
||||
printf(" %s\n", MAXADMIN_DEFAULT_HOST);
|
||||
printf(" -P|--port=... The port to use for the connection, the default\n");
|
||||
printf(" port is %s.\n", MAXADMIN_DEFAULT_PORT);
|
||||
printf(" -v|--version Print version information and exit\n");
|
||||
printf(" -?|--help Print this help text.\n");
|
||||
printf("\n");
|
||||
printf("Any remaining arguments are treated as MaxScale commands or a file\n");
|
||||
printf("containing commands to execute.\n");
|
||||
printf("\n");
|
||||
printf("Either a socket or a hostname/port combination should be provided.\n");
|
||||
printf("If a port or hostname is provided, but not the other, then the default\n"
|
||||
"value is used.\n");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -687,3 +927,55 @@ read_inifile(char **conn_socket, int* editor)
|
||||
}
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get password
|
||||
*
|
||||
* @param password Buffer for password.
|
||||
* @param len The size of the buffer.
|
||||
*
|
||||
* @return Whether the password was obtained.
|
||||
*/
|
||||
bool getPassword(char *passwd, size_t len)
|
||||
{
|
||||
bool gotten = false;
|
||||
|
||||
struct termios tty_attr;
|
||||
tcflag_t c_lflag;
|
||||
|
||||
if (tcgetattr(STDIN_FILENO, &tty_attr) == 0)
|
||||
{
|
||||
c_lflag = tty_attr.c_lflag;
|
||||
tty_attr.c_lflag &= ~ICANON;
|
||||
tty_attr.c_lflag &= ~ECHO;
|
||||
|
||||
if (tcsetattr(STDIN_FILENO, 0, &tty_attr) == 0)
|
||||
{
|
||||
printf("Password: ");
|
||||
fgets(passwd, len, stdin);
|
||||
|
||||
tty_attr.c_lflag = c_lflag;
|
||||
|
||||
if (tcsetattr(STDIN_FILENO, 0, &tty_attr) == 0)
|
||||
{
|
||||
int i = strlen(passwd);
|
||||
|
||||
if (i > 1)
|
||||
{
|
||||
passwd[i - 1] = '\0';
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
|
||||
gotten = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!gotten)
|
||||
{
|
||||
fprintf(stderr, "Could not configure terminal.\n");
|
||||
}
|
||||
|
||||
return gotten;
|
||||
}
|
||||
|
@ -37,13 +37,25 @@
|
||||
* 23/07/13 Mark Riddoch Addition of error mechanism to add user
|
||||
* 23/05/16 Massimiliano Pinto admin_add_user and admin_remove_user
|
||||
* no longer accept password parameter
|
||||
* 02/09/16 Johan Wikman Enabled Linux accounts and MaxScale users
|
||||
*
|
||||
* @endverbatim
|
||||
*/
|
||||
static USERS *loadUsers();
|
||||
static void initialise();
|
||||
|
||||
static USERS *users = NULL;
|
||||
static USERS *loadLinuxUsers();
|
||||
static USERS *loadInetUsers();
|
||||
|
||||
static const char *admin_add_user(USERS** pusers, const char* fname,
|
||||
const char* uname, const char* password);
|
||||
static const char* admin_remove_user(USERS *users, const char* fname,
|
||||
const char *uname, const char *passwd);
|
||||
static bool admin_search_user(USERS *users, const char *uname);
|
||||
|
||||
|
||||
|
||||
static USERS *linux_users = NULL;
|
||||
static USERS *inet_users = NULL;
|
||||
static int admin_init = 0;
|
||||
|
||||
static char *ADMIN_ERR_NOMEM = "Out of memory";
|
||||
@ -61,7 +73,11 @@ static char *ADMIN_SUCCESS = NULL;
|
||||
|
||||
static const int LINELEN = 80;
|
||||
|
||||
static const char USERS_FILE_NAME[] = "maxadmin-users";
|
||||
static const char LINUX_USERS_FILE_NAME[] = "maxadmin-users";
|
||||
static const char INET_USERS_FILE_NAME[] = "passwd";
|
||||
|
||||
static const char INET_DEFAULT_USERNAME[] = "admin";
|
||||
static const char INET_DEFAULT_PASSWORD[] = "mariadb";
|
||||
|
||||
/**
|
||||
* Admin Users initialisation
|
||||
@ -75,125 +91,15 @@ initialise()
|
||||
}
|
||||
|
||||
admin_init = 1;
|
||||
users = loadUsers();
|
||||
linux_users = loadLinuxUsers();
|
||||
inet_users = loadInetUsers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify a username and password
|
||||
*
|
||||
* @param username Username to verify
|
||||
* @param password Password to verify
|
||||
* @return Non-zero if the username/password combination is valid
|
||||
*/
|
||||
int
|
||||
admin_verify(char *username, char *password)
|
||||
{
|
||||
char *pw;
|
||||
|
||||
initialise();
|
||||
if (users == NULL)
|
||||
{
|
||||
if (strcmp(username, "admin") == 0 && strcmp(password, "mariadb") == 0)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((pw = users_fetch(users, username)) == NULL)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
struct crypt_data cdata;
|
||||
cdata.initialized = 0;
|
||||
if (strcmp(pw, crypt_r(password, ADMIN_SALT, &cdata)) == 0)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Load the admin users
|
||||
*
|
||||
* @return Table of users
|
||||
*/
|
||||
static USERS *
|
||||
loadUsers()
|
||||
{
|
||||
USERS *rval;
|
||||
FILE *fp;
|
||||
char fname[PATH_MAX], *home;
|
||||
char uname[80];
|
||||
int added_users = 0;
|
||||
|
||||
initialise();
|
||||
snprintf(fname, sizeof(fname), "%s/%s", get_datadir(), USERS_FILE_NAME);
|
||||
if ((fp = fopen(fname, "r")) == NULL)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
if ((rval = users_alloc()) == NULL)
|
||||
{
|
||||
fclose(fp);
|
||||
return NULL;
|
||||
}
|
||||
while (fgets(uname, sizeof(uname), fp))
|
||||
{
|
||||
char *nl = strchr(uname, '\n');
|
||||
|
||||
if (nl)
|
||||
{
|
||||
*nl = '\0';
|
||||
}
|
||||
else if (!feof(fp))
|
||||
{
|
||||
MXS_ERROR("Line length exceeds %d characters, possible corrupted "
|
||||
"'passwd' file in: %s", LINELEN, fname);
|
||||
users_free(rval);
|
||||
rval = NULL;
|
||||
break;
|
||||
}
|
||||
|
||||
char *tmp_ptr = strchr(uname, ':');
|
||||
if (tmp_ptr)
|
||||
{
|
||||
*tmp_ptr = '\0';
|
||||
MXS_WARNING("Found user '%s' with password. "
|
||||
"This user might not be compatible with new maxadmin in MaxScale 2.0. "
|
||||
"Remove it with \"remove user %s\" through MaxAdmin", uname, uname);
|
||||
}
|
||||
if (users_add(rval, uname, ""))
|
||||
{
|
||||
added_users++;
|
||||
}
|
||||
}
|
||||
fclose(fp);
|
||||
|
||||
if (!added_users)
|
||||
{
|
||||
users_free(rval);
|
||||
rval = NULL;
|
||||
}
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add user
|
||||
*
|
||||
* @param uname Name of the new user
|
||||
* @return NULL on success or an error string on failure
|
||||
*/
|
||||
char *
|
||||
admin_add_user(char *uname)
|
||||
static const char *admin_add_user(USERS** pusers, const char* fname,
|
||||
const char* uname, const char* password)
|
||||
{
|
||||
FILE *fp;
|
||||
char fname[PATH_MAX], *home;
|
||||
|
||||
initialise();
|
||||
char path[PATH_MAX], *home;
|
||||
|
||||
if (access(get_datadir(), F_OK) != 0)
|
||||
{
|
||||
@ -203,51 +109,51 @@ admin_add_user(char *uname)
|
||||
}
|
||||
}
|
||||
|
||||
snprintf(fname, sizeof(fname), "%s/%s", get_datadir(), USERS_FILE_NAME);
|
||||
if (users == NULL)
|
||||
snprintf(path, sizeof(path), "%s/%s", get_datadir(), fname);
|
||||
if (*pusers == NULL)
|
||||
{
|
||||
MXS_NOTICE("Create initial password file.");
|
||||
|
||||
if ((users = users_alloc()) == NULL)
|
||||
if ((*pusers = users_alloc()) == NULL)
|
||||
{
|
||||
return ADMIN_ERR_NOMEM;
|
||||
}
|
||||
if ((fp = fopen(fname, "w")) == NULL)
|
||||
if ((fp = fopen(path, "w")) == NULL)
|
||||
{
|
||||
MXS_ERROR("Unable to create password file %s.", fname);
|
||||
MXS_ERROR("Unable to create password file %s.", path);
|
||||
return ADMIN_ERR_PWDFILEOPEN;
|
||||
}
|
||||
fclose(fp);
|
||||
}
|
||||
if (users_fetch(users, uname) != NULL)
|
||||
if (users_fetch(*pusers, (char*)uname) != NULL) // TODO: Make users const correct.
|
||||
{
|
||||
return ADMIN_ERR_DUPLICATE;
|
||||
}
|
||||
users_add(users, uname, "");
|
||||
if ((fp = fopen(fname, "a")) == NULL)
|
||||
users_add(*pusers, (char*)uname, password ? (char*)password : ""); // TODO: Make users const correct.
|
||||
if ((fp = fopen(path, "a")) == NULL)
|
||||
{
|
||||
MXS_ERROR("Unable to append to password file %s.", fname);
|
||||
MXS_ERROR("Unable to append to password file %s.", path);
|
||||
return ADMIN_ERR_FILEAPPEND;
|
||||
}
|
||||
fprintf(fp, "%s\n", uname);
|
||||
if (password)
|
||||
{
|
||||
fprintf(fp, "%s:%s\n", uname, password);
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(fp, "%s\n", uname);
|
||||
}
|
||||
fclose(fp);
|
||||
return ADMIN_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove maxscale user from in-memory structure and from password file
|
||||
*
|
||||
* @param uname Name of the new user
|
||||
* @return NULL on success or an error string on failure
|
||||
*/
|
||||
char* admin_remove_user(
|
||||
char* uname)
|
||||
static const char* admin_remove_user(USERS *users, const char* fname,
|
||||
const char *uname, const char *passwd)
|
||||
{
|
||||
FILE* fp;
|
||||
FILE* fp_tmp;
|
||||
char fname[PATH_MAX];
|
||||
char fname_tmp[PATH_MAX];
|
||||
char path[PATH_MAX];
|
||||
char path_tmp[PATH_MAX];
|
||||
char* home;
|
||||
char fusr[LINELEN];
|
||||
char fpwd[LINELEN];
|
||||
@ -260,43 +166,53 @@ char* admin_remove_user(
|
||||
return ADMIN_ERR_DELROOT;
|
||||
}
|
||||
|
||||
if (!admin_search_user(uname))
|
||||
if (!admin_search_user(users, uname))
|
||||
{
|
||||
MXS_ERROR("Couldn't find user %s. Removing user failed.", uname);
|
||||
return ADMIN_ERR_USERNOTFOUND;
|
||||
}
|
||||
|
||||
if (passwd)
|
||||
{
|
||||
if (admin_verify_inet_user(uname, passwd) == 0)
|
||||
{
|
||||
MXS_ERROR("Authentication failed, wrong user/password "
|
||||
"combination. Removing user failed.");
|
||||
return ADMIN_ERR_AUTHENTICATION;
|
||||
}
|
||||
}
|
||||
|
||||
/** Remove user from in-memory structure */
|
||||
users_delete(users, uname);
|
||||
users_delete(users, (char*)uname); // TODO: Make users const correct.
|
||||
|
||||
/**
|
||||
* Open passwd file and remove user from the file.
|
||||
*/
|
||||
snprintf(fname, sizeof(fname), "%s/%s", get_datadir(), USERS_FILE_NAME);
|
||||
snprintf(fname_tmp, sizeof(fname_tmp), "%s/%s_tmp", get_datadir(), USERS_FILE_NAME);
|
||||
snprintf(path, sizeof(path), "%s/%s", get_datadir(), fname);
|
||||
snprintf(path_tmp, sizeof(path_tmp), "%s/%s_tmp", get_datadir(), fname);
|
||||
/**
|
||||
* Rewrite passwd file from memory.
|
||||
*/
|
||||
if ((fp = fopen(fname, "r")) == NULL)
|
||||
if ((fp = fopen(path, "r")) == NULL)
|
||||
{
|
||||
int err = errno;
|
||||
MXS_ERROR("Unable to open password file %s : errno %d.\n"
|
||||
"Removing user from file failed; it must be done "
|
||||
"manually.",
|
||||
fname,
|
||||
path,
|
||||
err);
|
||||
return ADMIN_ERR_PWDFILEOPEN;
|
||||
}
|
||||
/**
|
||||
* Open temporary passwd file.
|
||||
*/
|
||||
if ((fp_tmp = fopen(fname_tmp, "w")) == NULL)
|
||||
if ((fp_tmp = fopen(path_tmp, "w")) == NULL)
|
||||
{
|
||||
int err = errno;
|
||||
MXS_ERROR("Unable to open tmp file %s : errno %d.\n"
|
||||
"Removing user from passwd file failed; it must be done "
|
||||
"manually.",
|
||||
fname_tmp,
|
||||
path_tmp,
|
||||
err);
|
||||
fclose(fp);
|
||||
return ADMIN_ERR_TMPFILEOPEN;
|
||||
@ -311,11 +227,11 @@ char* admin_remove_user(
|
||||
MXS_ERROR("Unable to process passwd file %s : errno %d.\n"
|
||||
"Removing user from file failed, and must be done "
|
||||
"manually.",
|
||||
fname,
|
||||
path,
|
||||
err);
|
||||
fclose(fp);
|
||||
fclose(fp_tmp);
|
||||
unlink(fname_tmp);
|
||||
unlink(path_tmp);
|
||||
return ADMIN_ERR_PWDFILEACCESS;
|
||||
}
|
||||
|
||||
@ -330,7 +246,7 @@ char* admin_remove_user(
|
||||
else if (!feof(fp))
|
||||
{
|
||||
MXS_ERROR("Line length exceeds %d characters, possible corrupted "
|
||||
"'passwd' file in: %s", LINELEN, fname);
|
||||
"'passwd' file in: %s", LINELEN, path);
|
||||
fclose(fp);
|
||||
fclose(fp_tmp);
|
||||
return ADMIN_ERR_PWDFILEACCESS;
|
||||
@ -358,11 +274,11 @@ char* admin_remove_user(
|
||||
"errno %d.\n"
|
||||
"Removing user from file failed, and must be "
|
||||
"done manually.",
|
||||
fname,
|
||||
path,
|
||||
err);
|
||||
fclose(fp);
|
||||
fclose(fp_tmp);
|
||||
unlink(fname_tmp);
|
||||
unlink(path_tmp);
|
||||
return ADMIN_ERR_PWDFILEACCESS;
|
||||
}
|
||||
}
|
||||
@ -370,16 +286,16 @@ char* admin_remove_user(
|
||||
/**
|
||||
* Replace original passwd file with new.
|
||||
*/
|
||||
if (rename(fname_tmp, fname))
|
||||
if (rename(path_tmp, path))
|
||||
{
|
||||
int err = errno;
|
||||
MXS_ERROR("Unable to rename new passwd file %s : errno "
|
||||
"%d.\n"
|
||||
"Rename it to %s manually.",
|
||||
fname_tmp,
|
||||
path_tmp,
|
||||
err,
|
||||
fname);
|
||||
unlink(fname_tmp);
|
||||
path);
|
||||
unlink(path_tmp);
|
||||
fclose(fp_tmp);
|
||||
return ADMIN_ERR_PWDFILEACCESS;
|
||||
}
|
||||
@ -387,47 +303,284 @@ char* admin_remove_user(
|
||||
return ADMIN_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Check for existance of the user
|
||||
*
|
||||
* @param user The user name to test
|
||||
* @return Non-zero if the user exists
|
||||
* @param uname The user name to test
|
||||
* @return True if the user exists
|
||||
*/
|
||||
int
|
||||
admin_search_user(char *user)
|
||||
static bool admin_search_user(USERS *users, const char *uname)
|
||||
{
|
||||
return (users_fetch(users, (char*)uname) != NULL); // TODO: Make users const correct.
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
void dcb_print_users(DCB *dcb, const char* heading, USERS *users)
|
||||
{
|
||||
dcb_printf(dcb, "%s", heading);
|
||||
|
||||
if (users)
|
||||
{
|
||||
HASHITERATOR *iter = hashtable_iterator(users->data);
|
||||
|
||||
if (iter)
|
||||
{
|
||||
char *sep = "";
|
||||
const char *user;
|
||||
|
||||
while ((user = hashtable_next(iter)) != NULL)
|
||||
{
|
||||
dcb_printf(dcb, "%s%s", sep, user);
|
||||
sep = ", ";
|
||||
}
|
||||
|
||||
hashtable_iterator_free(iter);
|
||||
}
|
||||
}
|
||||
|
||||
dcb_printf(dcb, "%s", "\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the admin users
|
||||
*
|
||||
* @return Table of users
|
||||
*/
|
||||
static USERS *
|
||||
loadUsers(const char *fname)
|
||||
{
|
||||
USERS *rval;
|
||||
FILE *fp;
|
||||
char path[PATH_MAX], *home;
|
||||
char uname[80];
|
||||
int added_users = 0;
|
||||
|
||||
initialise();
|
||||
snprintf(path, sizeof(path), "%s/%s", get_datadir(), fname);
|
||||
if ((fp = fopen(path, "r")) == NULL)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
if ((rval = users_alloc()) == NULL)
|
||||
{
|
||||
fclose(fp);
|
||||
return NULL;
|
||||
}
|
||||
while (fgets(uname, sizeof(uname), fp))
|
||||
{
|
||||
char *nl = strchr(uname, '\n');
|
||||
|
||||
if (nl)
|
||||
{
|
||||
*nl = '\0';
|
||||
}
|
||||
else if (!feof(fp))
|
||||
{
|
||||
MXS_ERROR("Line length exceeds %d characters, possibly corrupted "
|
||||
"'passwd' file in: %s", LINELEN, path);
|
||||
users_free(rval);
|
||||
rval = NULL;
|
||||
break;
|
||||
}
|
||||
|
||||
char *password;
|
||||
char *colon = strchr(uname, ':');
|
||||
if (colon)
|
||||
{
|
||||
// Inet case
|
||||
*colon = 0;
|
||||
password = colon + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Linux case.
|
||||
password = "";
|
||||
}
|
||||
|
||||
if (users_add(rval, uname, password))
|
||||
{
|
||||
added_users++;
|
||||
}
|
||||
}
|
||||
fclose(fp);
|
||||
|
||||
if (!added_users)
|
||||
{
|
||||
users_free(rval);
|
||||
rval = NULL;
|
||||
}
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
|
||||
static USERS *loadLinuxUsers()
|
||||
{
|
||||
return loadUsers(LINUX_USERS_FILE_NAME);
|
||||
}
|
||||
|
||||
static USERS *loadInetUsers()
|
||||
{
|
||||
return loadUsers(INET_USERS_FILE_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable Linux account
|
||||
*
|
||||
* @param uname Name of Linux user
|
||||
*
|
||||
* @return NULL on success or an error string on failure.
|
||||
*/
|
||||
const char *admin_enable_linux_account(const char *uname)
|
||||
{
|
||||
initialise();
|
||||
|
||||
int rv = 0;
|
||||
return admin_add_user(&linux_users, LINUX_USERS_FILE_NAME, uname, NULL);
|
||||
}
|
||||
|
||||
if (strcmp(user, DEFAULT_ADMIN_USER) == 0)
|
||||
/**
|
||||
* Disable Linux account
|
||||
*
|
||||
* @param uname Name of Linux user
|
||||
*
|
||||
* @return NULL on success or an error string on failure.
|
||||
*/
|
||||
const char* admin_disable_linux_account(const char* uname)
|
||||
{
|
||||
initialise();
|
||||
|
||||
return admin_remove_user(linux_users, LINUX_USERS_FILE_NAME, uname, NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether Linux account is enabled
|
||||
*
|
||||
* @param uname The user name
|
||||
*
|
||||
* @return True if the account is enabled, false otherwise.
|
||||
*/
|
||||
bool admin_linux_account_enabled(const char *uname)
|
||||
{
|
||||
initialise();
|
||||
|
||||
bool rv = false;
|
||||
|
||||
if (strcmp(uname, DEFAULT_ADMIN_USER) == 0)
|
||||
{
|
||||
rv = 1;
|
||||
rv = true;
|
||||
}
|
||||
else if (users)
|
||||
else if (linux_users)
|
||||
{
|
||||
rv = (users_fetch(users, user) != NULL);
|
||||
rv = admin_search_user(linux_users, uname);
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the statistics and user names of the administration users
|
||||
* Add insecure remote (network) user.
|
||||
*
|
||||
* @param dcb A DCB to send the output to
|
||||
* @param uname Name of the new user.
|
||||
* @param password Password of the new user.
|
||||
*
|
||||
* @return NULL on success or an error string on failure.
|
||||
*/
|
||||
void
|
||||
dcb_PrintAdminUsers(DCB *dcb)
|
||||
const char *admin_add_inet_user(const char *uname, const char* password)
|
||||
{
|
||||
if (users)
|
||||
initialise();
|
||||
|
||||
struct crypt_data cdata;
|
||||
cdata.initialized = 0;
|
||||
char *cpassword = crypt_r(password, ADMIN_SALT, &cdata);
|
||||
|
||||
return admin_add_user(&inet_users, INET_USERS_FILE_NAME, uname, cpassword);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove insecure remote (network) user
|
||||
*
|
||||
* @param uname Name of user to be removed.
|
||||
* @param password Password of user to be removed.
|
||||
*
|
||||
* @return NULL on success or an error string on failure.
|
||||
*/
|
||||
const char* admin_remove_inet_user(const char* uname, const char *password)
|
||||
{
|
||||
initialise();
|
||||
|
||||
return admin_remove_user(inet_users, INET_USERS_FILE_NAME, uname, password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for existance of remote user.
|
||||
*
|
||||
* @param user The user name to test.
|
||||
*
|
||||
* @return True if the user exists, false otherwise.
|
||||
*/
|
||||
bool admin_inet_user_exists(const char *uname)
|
||||
{
|
||||
initialise();
|
||||
|
||||
bool rv = false;
|
||||
|
||||
if (inet_users)
|
||||
{
|
||||
dcb_usersPrint(dcb, users);
|
||||
rv = admin_search_user(inet_users, uname);
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify a remote user name and password
|
||||
*
|
||||
* @param username Username to verify
|
||||
* @param password Password to verify
|
||||
*
|
||||
* @return True if the username/password combination is valid
|
||||
*/
|
||||
bool
|
||||
admin_verify_inet_user(const char *username, const char *password)
|
||||
{
|
||||
bool rv = false;
|
||||
|
||||
initialise();
|
||||
|
||||
if (inet_users)
|
||||
{
|
||||
const char* pw = users_fetch(inet_users, (char*)username); // TODO: Make users const-correct.
|
||||
|
||||
if (pw)
|
||||
{
|
||||
struct crypt_data cdata;
|
||||
cdata.initialized = 0;
|
||||
if (strcmp(pw, crypt_r(password, ADMIN_SALT, &cdata)) == 0)
|
||||
{
|
||||
rv = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
dcb_printf(dcb, "No administration users have been defined.\n");
|
||||
if (strcmp(username, INET_DEFAULT_USERNAME) == 0
|
||||
&& strcmp(password, INET_DEFAULT_PASSWORD) == 0)
|
||||
{
|
||||
rv = true;
|
||||
}
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print Linux and and inet users
|
||||
*
|
||||
* @param dcb A DCB to send the output to.
|
||||
*/
|
||||
void dcb_PrintAdminUsers(DCB *dcb)
|
||||
{
|
||||
dcb_print_users(dcb, "Enabled Linux accounts (secure) : ", linux_users);
|
||||
dcb_print_users(dcb, "Created network accounts (insecure): ", inet_users);
|
||||
}
|
||||
|
@ -84,6 +84,7 @@ static bool process_config_context(CONFIG_CONTEXT *);
|
||||
static int process_config_update(CONFIG_CONTEXT *);
|
||||
static void free_config_context(CONFIG_CONTEXT *);
|
||||
static char *config_get_value(CONFIG_PARAMETER *, const char *);
|
||||
static char *config_get_password(CONFIG_PARAMETER *);
|
||||
static const char *config_get_value_string(CONFIG_PARAMETER *, const char *);
|
||||
static int handle_global_item(const char *, const char *);
|
||||
static int handle_feedback_item(const char *, const char *);
|
||||
@ -119,7 +120,8 @@ static char *service_params[] =
|
||||
"router_options",
|
||||
"servers",
|
||||
"user",
|
||||
"passwd",
|
||||
"passwd", // DEPRECATE: See config_get_password.
|
||||
"password",
|
||||
"enable_root_user",
|
||||
"max_connections",
|
||||
"max_queued_connections",
|
||||
@ -139,6 +141,7 @@ static char *service_params[] =
|
||||
"ignore_databases_regex",
|
||||
"log_auth_warnings",
|
||||
"source", /**< Avrorouter only */
|
||||
"retry_on_failure",
|
||||
NULL
|
||||
};
|
||||
|
||||
@ -166,7 +169,8 @@ static char *monitor_params[] =
|
||||
"module",
|
||||
"servers",
|
||||
"user",
|
||||
"passwd",
|
||||
"passwd", // DEPRECATE: See config_get_password.
|
||||
"password",
|
||||
"script",
|
||||
"events",
|
||||
"mysql51_replication",
|
||||
@ -594,6 +598,30 @@ config_get_value(CONFIG_PARAMETER *params, const char *name)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// DEPRECATE: In 2.1 complain but accept if "passwd" is provided, in 2.2
|
||||
// DEPRECATE: drop support for "passwd".
|
||||
/**
|
||||
* Get the value of the password parameter
|
||||
*
|
||||
* The words looked for are "password" and "passwd".
|
||||
*
|
||||
* @param params The linked list of config parameters
|
||||
* @return the parameter value or NULL if not found
|
||||
*/
|
||||
static char *
|
||||
config_get_password(CONFIG_PARAMETER *params)
|
||||
{
|
||||
char *password = config_get_value(params, "password");
|
||||
char *passwd = config_get_value(params, "passwd");
|
||||
|
||||
if (password && passwd)
|
||||
{
|
||||
MXS_WARNING("Both 'password' and 'passwd' specified. Using value of 'password'.");
|
||||
}
|
||||
|
||||
return passwd ? passwd : password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of a config parameter as a string
|
||||
*
|
||||
@ -1386,7 +1414,7 @@ process_config_update(CONFIG_CONTEXT *context)
|
||||
max_queued_connections = config_get_value_string(obj->parameters, "max_queued_connections");
|
||||
queued_connection_timeout = config_get_value_string(obj->parameters, "queued_connection_timeout");
|
||||
user = config_get_value(obj->parameters, "user");
|
||||
auth = config_get_value(obj->parameters, "passwd");
|
||||
auth = config_get_password(obj->parameters);
|
||||
|
||||
auth_all_servers = config_get_value(obj->parameters, "auth_all_servers");
|
||||
strip_db_esc = config_get_value(obj->parameters, "strip_db_esc");
|
||||
@ -2318,7 +2346,7 @@ int create_new_service(CONFIG_CONTEXT *obj)
|
||||
}
|
||||
|
||||
char *user = config_get_value(obj->parameters, "user");
|
||||
char *auth = config_get_value(obj->parameters, "passwd");
|
||||
char *auth = config_get_password(obj->parameters);
|
||||
|
||||
if (user && auth)
|
||||
{
|
||||
@ -2331,7 +2359,7 @@ int create_new_service(CONFIG_CONTEXT *obj)
|
||||
obj->object,
|
||||
user ? "" : "the 'user' parameter",
|
||||
!user && !auth ? " and " : "",
|
||||
auth ? "" : "the 'passwd' parameter");
|
||||
auth ? "" : "the 'password' or 'passwd' parameter");
|
||||
}
|
||||
|
||||
char *subservices = config_get_value(obj->parameters, "subservices");
|
||||
@ -2736,7 +2764,7 @@ int create_new_monitor(CONFIG_CONTEXT *context, CONFIG_CONTEXT *obj, HASHTABLE*
|
||||
}
|
||||
|
||||
char *user = config_get_value(obj->parameters, "user");
|
||||
char *passwd = config_get_value(obj->parameters, "passwd");
|
||||
char *passwd = config_get_password(obj->parameters);
|
||||
if (user && passwd)
|
||||
{
|
||||
monitorAddUser(obj->element, user, passwd);
|
||||
|
@ -137,6 +137,7 @@ static struct option long_options[] =
|
||||
{"execdir", required_argument, 0, 'E'},
|
||||
{"language", required_argument, 0, 'N'},
|
||||
{"piddir", required_argument, 0, 'P'},
|
||||
{"basedir", required_argument, 0, 'R'},
|
||||
{"user", required_argument, 0, 'U'},
|
||||
{"syslog", required_argument, 0, 's'},
|
||||
{"maxlog", required_argument, 0, 'S'},
|
||||
@ -164,7 +165,7 @@ static void write_footer(void);
|
||||
static int ntfw_cb(const char*, const struct stat*, int, struct FTW*);
|
||||
static bool file_is_readable(const char* absolute_pathname);
|
||||
static bool file_is_writable(const char* absolute_pathname);
|
||||
bool handle_path_arg(char** dest, char* path, char* arg, bool rd, bool wr);
|
||||
bool handle_path_arg(char** dest, const char* path, char* arg, bool rd, bool wr);
|
||||
static void set_log_augmentation(const char* value);
|
||||
static void usage(void);
|
||||
static char* get_expanded_pathname(
|
||||
@ -697,30 +698,27 @@ static void print_log_n_stderr(
|
||||
const char* fprstr, /*< string to be printed to stderr */
|
||||
int eno) /*< errno, if it is set, zero, otherwise */
|
||||
{
|
||||
char* log_err = "Error :";
|
||||
char* fpr_err = "*\n* Error :";
|
||||
char* fpr_end = "\n*\n";
|
||||
|
||||
if (do_log)
|
||||
{
|
||||
mxs_log_init(NULL, get_logdir(), MXS_LOG_TARGET_FS);
|
||||
char errbuf[STRERROR_BUFLEN];
|
||||
MXS_ERROR("%s %s %s %s",
|
||||
log_err,
|
||||
logstr,
|
||||
eno == 0 ? " " : "Error :",
|
||||
eno == 0 ? " " : strerror_r(eno, errbuf, sizeof(errbuf)));
|
||||
if (mxs_log_init(NULL, get_logdir(), MXS_LOG_TARGET_FS))
|
||||
{
|
||||
char errbuf[STRERROR_BUFLEN];
|
||||
MXS_ERROR("%s%s%s%s",
|
||||
logstr,
|
||||
eno == 0 ? "" : " (",
|
||||
eno == 0 ? "" : strerror_r(eno, errbuf, sizeof(errbuf)),
|
||||
eno == 0 ? "" : ")");
|
||||
}
|
||||
}
|
||||
if (do_stderr)
|
||||
{
|
||||
char errbuf[STRERROR_BUFLEN];
|
||||
fprintf(stderr,
|
||||
"%s %s %s %s %s",
|
||||
fpr_err,
|
||||
"* Error: %s%s%s%s\n",
|
||||
fprstr,
|
||||
eno == 0 ? " " : "Error :",
|
||||
eno == 0 ? " " : strerror_r(eno, errbuf, sizeof(errbuf)),
|
||||
fpr_end);
|
||||
eno == 0 ? "" : " (",
|
||||
eno == 0 ? "" : strerror_r(eno, errbuf, sizeof(errbuf)),
|
||||
eno == 0 ? "" : ")");
|
||||
}
|
||||
}
|
||||
|
||||
@ -887,32 +885,52 @@ static void usage(void)
|
||||
{
|
||||
fprintf(stderr,
|
||||
"\nUsage : %s [OPTION]...\n\n"
|
||||
" -c, --config-check Validate configuration file and exit\n"
|
||||
" -d, --nodaemon enable running in terminal process (default:disabled)\n"
|
||||
" -f, --config=FILE relative or absolute pathname of MaxScale configuration file\n"
|
||||
" (default:/etc/maxscale.cnf)\n"
|
||||
" -l, --log=[file|shm|stdout] log to file, shared memory or stdout (default: file)\n"
|
||||
" -L, --logdir=PATH path to log file directory (default: /var/log/maxscale)\n"
|
||||
" -A, --cachedir=PATH path to cache directory (default: /var/cache/maxscale)\n"
|
||||
" -B, --libdir=PATH path to module directory (default: /usr/lib64/maxscale)\n"
|
||||
" -C, --configdir=PATH path to configuration file directory (default: /etc/)\n"
|
||||
" -D, --datadir=PATH path to data directory, stored embedded mysql tables\n"
|
||||
" (default: /var/cache/maxscale)\n"
|
||||
" -c, --config-check validate configuration file and exit\n"
|
||||
" -d, --nodaemon enable running in terminal process\n"
|
||||
" -f, --config=FILE relative or absolute pathname of config file\n"
|
||||
" -l, --log=[file|shm|stdout] log to file, shared memory or stdout\n"
|
||||
" (default: file)\n"
|
||||
" -L, --logdir=PATH path to log file directory\n"
|
||||
" -A, --cachedir=PATH path to cache directory\n"
|
||||
" -B, --libdir=PATH path to module directory\n"
|
||||
" -C, --configdir=PATH path to configuration file directory\n"
|
||||
" -D, --datadir=PATH path to data directory,\n"
|
||||
" stored embedded mysql tables\n"
|
||||
" -E, --execdir=PATH path to the maxscale and other executable files\n"
|
||||
" (default: /usr/bin)\n"
|
||||
" -N, --language=PATH path to errmsg.sys file (default: /var/lib/maxscale)\n"
|
||||
" -P, --piddir=PATH path to PID file directory (default: /var/run/maxscale)\n"
|
||||
" -U, --user=USER run MaxScale as another user.\n"
|
||||
" The user ID and group ID of this user are used to run MaxScale.\n"
|
||||
" -N, --language=PATH path to errmsg.sys file\n"
|
||||
" -P, --piddir=PATH path to PID file directory\n"
|
||||
" -R, --basedir=PATH base path for all other paths\n"
|
||||
" -U, --user=USER user ID and group ID of specified user are used to\n"
|
||||
" run MaxScale\n"
|
||||
" -s, --syslog=[yes|no] log messages to syslog (default:yes)\n"
|
||||
" -S, --maxlog=[yes|no] log messages to MaxScale log (default: yes)\n"
|
||||
" -G, --log_augmentation=0|1 augment messages with the name of the function where\n"
|
||||
" the message was logged (default: 0). Primarily for \n"
|
||||
" development purposes.\n"
|
||||
" -G, --log_augmentation=0|1 augment messages with the name of the function\n"
|
||||
" where the message was logged (default: 0)\n"
|
||||
" -v, --version print version info and exit\n"
|
||||
" -V, --version-full print full version info and exit\n"
|
||||
" -?, --help show this help\n"
|
||||
, progname);
|
||||
"\n"
|
||||
"Defaults paths:\n"
|
||||
" config file: %s/%s\n"
|
||||
" configdir : %s\n"
|
||||
" logdir : %s\n"
|
||||
" cachedir : %s\n"
|
||||
" libdir : %s\n"
|
||||
" datadir : %s\n"
|
||||
" execdir : %s\n"
|
||||
" language : %s\n"
|
||||
" piddir : %s\n"
|
||||
"\n"
|
||||
"If '--basedir' is provided then all other paths, including the default\n"
|
||||
"configuration file path, are defined relative to that. As an example,\n"
|
||||
"if '--basedir /path/maxscale' is specified, then, for instance, the log\n"
|
||||
"dir will be '/path/maxscale/var/log/maxscale', the config dir will be\n"
|
||||
"'/path/maxscale/etc' and the default config file will be\n"
|
||||
"'/path/maxscale/etc/maxscale.cnf'.\n",
|
||||
progname,
|
||||
get_configdir(), default_cnf_fname,
|
||||
get_configdir(), get_logdir(), get_cachedir(), get_libdir(),
|
||||
get_datadir(), get_execdir(), get_langdir(), get_piddir());
|
||||
}
|
||||
|
||||
|
||||
@ -1135,6 +1153,61 @@ bool configure_signals(void)
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the directories of MaxScale relative to a basedir
|
||||
*
|
||||
* @param basedir The base directory relative to which the other are set.
|
||||
*
|
||||
* @return True if the directories could be set, false otherwise.
|
||||
*/
|
||||
bool set_dirs(const char *basedir)
|
||||
{
|
||||
bool rv = true;
|
||||
char *path;
|
||||
|
||||
if (rv && (rv = handle_path_arg(&path, basedir, "var/" MXS_DEFAULT_LOG_SUBPATH, true, false)))
|
||||
{
|
||||
set_logdir(path);
|
||||
}
|
||||
|
||||
if (rv && (rv = handle_path_arg(&path, basedir, "var/" MXS_DEFAULT_CACHE_SUBPATH, true, true)))
|
||||
{
|
||||
set_cachedir(path);
|
||||
}
|
||||
|
||||
if (rv && (rv = handle_path_arg(&path, basedir, MXS_DEFAULT_LIB_SUBPATH, true, false)))
|
||||
{
|
||||
set_libdir(path);
|
||||
}
|
||||
|
||||
if (rv && (rv = handle_path_arg(&path, basedir, MXS_DEFAULT_CONFIG_SUBPATH, true, false)))
|
||||
{
|
||||
set_configdir(path);
|
||||
}
|
||||
|
||||
if (rv && (rv = handle_path_arg(&path, basedir, "var/" MXS_DEFAULT_DATA_SUBPATH, true, false)))
|
||||
{
|
||||
set_datadir(path);
|
||||
}
|
||||
|
||||
if (rv && (rv = handle_path_arg(&path, basedir, MXS_DEFAULT_EXEC_SUBPATH, true, false)))
|
||||
{
|
||||
set_execdir(path);
|
||||
}
|
||||
|
||||
if (rv && (rv = handle_path_arg(&path, basedir, "var/" MXS_DEFAULT_LANG_SUBPATH, true, false)))
|
||||
{
|
||||
set_langdir(path);
|
||||
}
|
||||
|
||||
if (rv && (rv = handle_path_arg(&path, basedir, "var/" MXS_DEFAULT_PID_SUBPATH, true, true)))
|
||||
{
|
||||
set_piddir(path);
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* @mainpage
|
||||
* The main entry point into MaxScale
|
||||
@ -1304,7 +1377,6 @@ int main(int argc, char **argv)
|
||||
}
|
||||
break;
|
||||
case 'L':
|
||||
|
||||
if (handle_path_arg(&tmp_path, optarg, NULL, true, false))
|
||||
{
|
||||
set_logdir(tmp_path);
|
||||
@ -1381,6 +1453,17 @@ int main(int argc, char **argv)
|
||||
succp = false;
|
||||
}
|
||||
break;
|
||||
case 'R':
|
||||
if (handle_path_arg(&tmp_path, optarg, NULL, true, false))
|
||||
{
|
||||
succp = set_dirs(tmp_path);
|
||||
free(tmp_path);
|
||||
}
|
||||
else
|
||||
{
|
||||
succp = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'S':
|
||||
{
|
||||
@ -2252,7 +2335,7 @@ static int write_pid_file()
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool handle_path_arg(char** dest, char* path, char* arg, bool rd, bool wr)
|
||||
bool handle_path_arg(char** dest, const char* path, char* arg, bool rd, bool wr)
|
||||
{
|
||||
char pathbuffer[PATH_MAX + 2];
|
||||
char* errstr;
|
||||
|
@ -584,7 +584,7 @@ return_succ:
|
||||
{
|
||||
/** This releases memory of all created objects */
|
||||
logmanager_done_nomutex();
|
||||
LOG_ERROR("MaxScale Log: Error, initialization failed.\n");
|
||||
fprintf(stderr, "* Error: Initializing the log manager failed.\n");
|
||||
}
|
||||
return succ;
|
||||
}
|
||||
@ -1693,7 +1693,7 @@ static bool logfile_open_file(filewriter_t* fw,
|
||||
|
||||
if (fw->fwr_file == NULL)
|
||||
{
|
||||
LOG_ERROR("MaxScale Log: Error, opening log file %s failed.\n", lf->lf_full_file_name);
|
||||
// Error logged by skygw_file_init to stderr.
|
||||
rv = false;
|
||||
}
|
||||
|
||||
@ -2125,9 +2125,6 @@ static void filewriter_done(filewriter_t* fw, bool write_footer)
|
||||
{
|
||||
case RUN:
|
||||
CHK_FILEWRITER(fw);
|
||||
case INIT:
|
||||
fw->fwr_logmes = NULL;
|
||||
fw->fwr_clientmes = NULL;
|
||||
if (log_config.use_stdout)
|
||||
{
|
||||
skygw_file_free(fw->fwr_file);
|
||||
@ -2141,7 +2138,11 @@ static void filewriter_done(filewriter_t* fw, bool write_footer)
|
||||
|
||||
skygw_file_close(fw->fwr_file);
|
||||
}
|
||||
case INIT:
|
||||
fw->fwr_logmes = NULL;
|
||||
fw->fwr_clientmes = NULL;
|
||||
fw->fwr_state = DONE;
|
||||
break;
|
||||
case DONE:
|
||||
case UNINIT:
|
||||
default:
|
||||
|
@ -826,7 +826,9 @@ mon_status_changed(MONITOR_SERVERS* mon_srv)
|
||||
{
|
||||
/* Previous status is -1 if not yet set */
|
||||
return (mon_srv->mon_prev_status != -1
|
||||
&& mon_srv->mon_prev_status != mon_srv->server->status);
|
||||
&& mon_srv->mon_prev_status != mon_srv->server->status
|
||||
/** If the server is going into maintenance or coming out of it, don't trigger a state change */
|
||||
&& ((mon_srv->mon_prev_status | mon_srv->server->status) & SERVER_MAINT) == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -89,7 +89,6 @@ server_alloc(char *servname, char *protocol, unsigned short port)
|
||||
server->rlag = -2;
|
||||
server->master_id = -1;
|
||||
server->depth = -1;
|
||||
server->slaves = NULL;
|
||||
server->parameters = NULL;
|
||||
server->server_string = NULL;
|
||||
spinlock_init(&server->lock);
|
||||
|
@ -384,6 +384,9 @@ int serviceStartAllPorts(SERVICE* service)
|
||||
(void*) service, retry_after);
|
||||
MXS_NOTICE("Failed to start service %s, retrying in %d seconds.",
|
||||
service->name, retry_after);
|
||||
|
||||
/** This will prevent MaxScale from shutting down if service start is retried later */
|
||||
listeners = 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -50,12 +50,12 @@
|
||||
static int
|
||||
test1()
|
||||
{
|
||||
if (admin_verify("admin", "mariadb") == 0)
|
||||
if (admin_verify_inet_user("admin", "mariadb") == 0)
|
||||
{
|
||||
fprintf(stderr, "admin_verify: test 1.1 (default user) failed.\n");
|
||||
return 1;
|
||||
}
|
||||
if (admin_verify("bad", "user"))
|
||||
if (admin_verify_inet_user("bad", "user"))
|
||||
{
|
||||
fprintf(stderr, "admin_verify: test 1.2 (wrong user) failed.\n");
|
||||
return 1;
|
||||
@ -74,15 +74,15 @@ test1()
|
||||
static int
|
||||
test2()
|
||||
{
|
||||
char *err;
|
||||
const char *err;
|
||||
|
||||
if ((err = admin_add_user("user0")) != NULL)
|
||||
if ((err = admin_enable_linux_account("user0")) != NULL)
|
||||
{
|
||||
fprintf(stderr, "admin_add_user: test 2.1 (add user) failed, %s.\n", err);
|
||||
|
||||
return 1;
|
||||
}
|
||||
if (admin_add_user("user0") == NULL)
|
||||
if (admin_enable_linux_account("user0") == NULL)
|
||||
{
|
||||
fprintf(stderr, "admin_add_user: test 2.2 (add user) failed, duplicate.\n");
|
||||
|
||||
@ -90,7 +90,7 @@ test2()
|
||||
}
|
||||
|
||||
/* Deleting the last user is not forbidden so we expect this to succeed */
|
||||
if ((err = admin_remove_user("user0")) != NULL)
|
||||
if ((err = admin_disable_linux_account("user0")) != NULL)
|
||||
{
|
||||
fprintf(stderr, "admin_remove_user: test 2.3 (add user) failed, %s.\n", err);
|
||||
|
||||
@ -98,7 +98,7 @@ test2()
|
||||
}
|
||||
|
||||
/* Add the user back, for test5. */
|
||||
if ((err = admin_add_user("user0")) != NULL)
|
||||
if ((err = admin_enable_linux_account("user0")) != NULL)
|
||||
{
|
||||
fprintf(stderr, "admin_add_user: test 2.4 (add user) failed, %s.\n", err);
|
||||
|
||||
@ -120,37 +120,37 @@ test2()
|
||||
static int
|
||||
test3()
|
||||
{
|
||||
char *err;
|
||||
const char *err;
|
||||
|
||||
if ((err = admin_add_user("user1")) != NULL)
|
||||
if ((err = admin_enable_linux_account("user1")) != NULL)
|
||||
{
|
||||
fprintf(stderr, "admin_add_user: test 3.1 (add user) failed, %s.\n", err);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (admin_search_user("user1") == 0)
|
||||
if (admin_linux_account_enabled("user1") == 0)
|
||||
{
|
||||
fprintf(stderr, "admin_search_user: test 3.2 (search user) failed.\n");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (admin_search_user("user2") != 0)
|
||||
if (admin_linux_account_enabled("user2") != 0)
|
||||
{
|
||||
fprintf(stderr, "admin_search_user: test 3.3 (search user) failed, unexpeted user found.\n");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ((err = admin_remove_user("user1")) != NULL)
|
||||
if ((err = admin_disable_linux_account("user1")) != NULL)
|
||||
{
|
||||
fprintf(stderr, "admin_remove_user: test 3.4 (add user) failed, %s.\n", err);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (admin_search_user("user1"))
|
||||
if (admin_linux_account_enabled("user1"))
|
||||
{
|
||||
fprintf(stderr, "admin_search_user: test 3.5 (search user) failed - user was deleted.\n");
|
||||
|
||||
@ -173,13 +173,14 @@ test3()
|
||||
static int
|
||||
test4()
|
||||
{
|
||||
char *err, user[40], passwd[40];
|
||||
const char *err;
|
||||
char user[40], passwd[40];
|
||||
int i, n_users = 50;
|
||||
|
||||
for (i = 1; i < n_users; i++)
|
||||
{
|
||||
sprintf(user, "user%d", i);
|
||||
if ((err = admin_add_user(user)) != NULL)
|
||||
if ((err = admin_enable_linux_account(user)) != NULL)
|
||||
{
|
||||
fprintf(stderr, "admin_add_user: test 4.1 (add user) failed, %s.\n", err);
|
||||
|
||||
@ -190,7 +191,7 @@ test4()
|
||||
for (i = 1; i < n_users; i++)
|
||||
{
|
||||
sprintf(user, "user%d", i);
|
||||
if (admin_search_user(user) == 0)
|
||||
if (admin_linux_account_enabled(user) == 0)
|
||||
{
|
||||
fprintf(stderr, "admin_search_user: test 4.2 (search user) failed.\n");
|
||||
|
||||
@ -201,7 +202,7 @@ test4()
|
||||
for (i = 1; i < n_users; i++)
|
||||
{
|
||||
sprintf(user, "user%d", i);
|
||||
if ((err = admin_remove_user(user)) != NULL)
|
||||
if ((err = admin_disable_linux_account(user)) != NULL)
|
||||
{
|
||||
fprintf(stderr, "admin_remove_user: test 4.3 (add user) failed, %s.\n", err);
|
||||
|
||||
@ -221,16 +222,16 @@ test4()
|
||||
static int
|
||||
test5()
|
||||
{
|
||||
char *err;
|
||||
const char *err;
|
||||
|
||||
if ((err = admin_add_user("user")) != NULL)
|
||||
if ((err = admin_enable_linux_account("user")) != NULL)
|
||||
{
|
||||
fprintf(stderr, "admin_add_user: test 5.1 (add user) failed, %s.\n", err);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ((err = admin_remove_user("user0")) != NULL)
|
||||
if ((err = admin_disable_linux_account("user0")) != NULL)
|
||||
{
|
||||
fprintf(stderr, "admin_remove_user: test 5.2 (add user) failed, %s.\n", err);
|
||||
|
||||
|
@ -51,12 +51,16 @@ typedef struct admin_session
|
||||
#endif
|
||||
} ADMIN_session;
|
||||
|
||||
extern int admin_verify(char *, char *);
|
||||
extern char *admin_add_user(char *);
|
||||
extern int admin_search_user(char *);
|
||||
extern void dcb_PrintAdminUsers(DCB *dcb);
|
||||
extern const char *admin_enable_linux_account(const char *uname);
|
||||
extern const char *admin_disable_linux_account(const char *uname);
|
||||
extern bool admin_linux_account_enabled(const char *uname);
|
||||
|
||||
char* admin_remove_user(char* uname);
|
||||
extern const char *admin_add_inet_user(const char *uname, const char *password);
|
||||
extern const char *admin_remove_inet_user(const char *uname, const char *password);
|
||||
extern bool admin_inet_user_exists(const char *uname);
|
||||
|
||||
extern bool admin_verify_inet_user(const char *uname, const char *password);
|
||||
|
||||
extern void dcb_PrintAdminUsers(DCB *dcb);
|
||||
|
||||
#endif
|
||||
|
@ -21,19 +21,30 @@
|
||||
|
||||
EXTERN_C_BLOCK_BEGIN
|
||||
|
||||
// NOTE: If you make changes here, ensure they are compatible with the
|
||||
// situation in <root>/CMakeLists.txt, where directories are installed.
|
||||
#define MXS_DEFAULT_PID_SUBPATH "run/maxscale"
|
||||
#define MXS_DEFAULT_LOG_SUBPATH "log/maxscale"
|
||||
#define MXS_DEFAULT_DATA_SUBPATH "lib/maxscale"
|
||||
#define MXS_DEFAULT_LIB_SUBPATH "@MAXSCALE_LIBDIR@"
|
||||
#define MXS_DEFAULT_CACHE_SUBPATH "cache/maxscale"
|
||||
#define MXS_DEFAULT_LANG_SUBPATH "lib/maxscale"
|
||||
#define MXS_DEFAULT_EXEC_SUBPATH "@MAXSCALE_BINDIR@"
|
||||
#define MXS_DEFAULT_CONFIG_SUBPATH "etc"
|
||||
|
||||
/** Default file locations, configured by CMake */
|
||||
static const char* default_cnf_fname = "maxscale.cnf";
|
||||
static const char* default_configdir = "/etc";
|
||||
static const char* default_configdir = "/" MXS_DEFAULT_CONFIG_SUBPATH;
|
||||
/*< This should be changed to just /run eventually,
|
||||
* the /var/run folder is an old standard and the newer FSH 3.0
|
||||
* uses /run for PID files.*/
|
||||
static const char* default_piddir = "@MAXSCALE_VARDIR@/run/maxscale";
|
||||
static const char* default_logdir = "@MAXSCALE_VARDIR@/log/maxscale";
|
||||
static const char* default_datadir = "@MAXSCALE_VARDIR@/lib/maxscale";
|
||||
static const char* default_libdir = "@CMAKE_INSTALL_PREFIX@/@MAXSCALE_LIBDIR@";
|
||||
static const char* default_cachedir = "@MAXSCALE_VARDIR@/cache/maxscale";
|
||||
static const char* default_langdir = "@MAXSCALE_VARDIR@/lib/maxscale";
|
||||
static const char* default_execdir = "@CMAKE_INSTALL_PREFIX@/@MAXSCALE_BINDIR@";
|
||||
static const char* default_piddir = "@MAXSCALE_VARDIR@/" MXS_DEFAULT_PID_SUBPATH;
|
||||
static const char* default_logdir = "@MAXSCALE_VARDIR@/" MXS_DEFAULT_LOG_SUBPATH;
|
||||
static const char* default_datadir = "@MAXSCALE_VARDIR@/" MXS_DEFAULT_DATA_SUBPATH;
|
||||
static const char* default_libdir = "@CMAKE_INSTALL_PREFIX@/" MXS_DEFAULT_LIB_SUBPATH;
|
||||
static const char* default_cachedir = "@MAXSCALE_VARDIR@/" MXS_DEFAULT_CACHE_SUBPATH;
|
||||
static const char* default_langdir = "@MAXSCALE_VARDIR@/" MXS_DEFAULT_LANG_SUBPATH;
|
||||
static const char* default_execdir = "@CMAKE_INSTALL_PREFIX@/" MXS_DEFAULT_EXEC_SUBPATH;
|
||||
|
||||
static char* configdir = NULL;
|
||||
static char* logdir = NULL;
|
||||
|
@ -133,7 +133,6 @@ typedef enum
|
||||
|
||||
#define MONITOR_INTERVAL 10000 // in milliseconds
|
||||
#define MONITOR_DEFAULT_ID 1UL // unsigned long value
|
||||
#define MONITOR_MAX_NUM_SLAVES 20 //number of MySQL slave servers associated to a MySQL master server
|
||||
|
||||
/*
|
||||
* Create declarations of the enum for monitor events and also the array of
|
||||
|
@ -45,6 +45,7 @@
|
||||
*/
|
||||
|
||||
#define MAX_SERVER_NAME_LEN 1024
|
||||
#define MAX_NUM_SLAVES 128 /**< Maximum number of slaves under a single server*/
|
||||
|
||||
/**
|
||||
* The server parameters used for weighting routing decissions
|
||||
@ -99,7 +100,7 @@ typedef struct server
|
||||
SERVER_PARAM *parameters; /**< Parameters of a server that may be used to weight routing decisions */
|
||||
long master_id; /**< Master server id of this node */
|
||||
int depth; /**< Replication level in the tree */
|
||||
long *slaves; /**< Slaves of this node */
|
||||
long slaves[MAX_NUM_SLAVES]; /**< Slaves of this node */
|
||||
bool master_err_is_logged; /*< If node failed, this indicates whether it is logged */
|
||||
DCB *persistent; /**< List of unused persistent connections to the server */
|
||||
SPINLOCK persistlock; /**< Lock for adjusting the persistent connections list */
|
||||
|
@ -145,7 +145,7 @@ max_admin_auth_set_protocol_data(DCB *dcb, GWBUF *buf)
|
||||
dcb->data = (void *)session_data;
|
||||
|
||||
/* Check for existance of the user */
|
||||
if (admin_search_user(session_data->user))
|
||||
if (admin_linux_account_enabled(session_data->user))
|
||||
{
|
||||
session_data->validated = true;
|
||||
return 0;
|
||||
|
@ -13,12 +13,19 @@
|
||||
* Public License.
|
||||
*/
|
||||
|
||||
#define MAXADMIN_DEFAULT_SOCKET "/tmp/maxadmin.sock"
|
||||
#define MAXADMIN_DEFAULT_SOCKET "/tmp/maxadmin.sock"
|
||||
|
||||
#define MAXADMIN_CONFIG_DEFAULT_SOCKET_TAG_LEN 7
|
||||
#define MAXADMIN_CONFIG_DEFAULT_SOCKET_TAG "default"
|
||||
#define MAXADMIN_GETPWUID_BUF_LEN 255
|
||||
#define MAXADMIN_AUTH_REPLY_LEN 6
|
||||
#define MAXADMIN_FAILED_AUTH_MESSAGE "FAILED"
|
||||
#define MAXADMIN_CONFIG_DEFAULT_SOCKET_TAG "default"
|
||||
|
||||
#define MAXADMIN_AUTH_REPLY_LEN 6
|
||||
#define MAXADMIN_AUTH_FAILED_REPLY "FAILED"
|
||||
#define MAXADMIN_AUTH_SUCCESS_REPLY "OK----"
|
||||
|
||||
#define MAXADMIN_AUTH_USER_PROMPT "USER"
|
||||
#define MAXADMIN_AUTH_USER_PROMPT_LEN 4
|
||||
|
||||
#define MAXADMIN_AUTH_PASSWORD_PROMPT "PASSWORD"
|
||||
#define MAXADMIN_AUTH_PASSWORD_PROMPT_LEN 8
|
||||
|
||||
#endif
|
||||
|
@ -28,16 +28,17 @@
|
||||
#include <spinlock.h>
|
||||
#include <housekeeper.h>
|
||||
/**
|
||||
* The telnetd specific protocol structure to put in the DCB.
|
||||
* The maxscaled specific protocol structure to put in the DCB.
|
||||
*/
|
||||
typedef struct maxscaled
|
||||
typedef struct maxscaled
|
||||
{
|
||||
SPINLOCK lock; /**< Protocol structure lock */
|
||||
int state; /**< The connection state */
|
||||
char *username; /**< The login name of the user */
|
||||
SPINLOCK lock; /**< Protocol structure lock */
|
||||
int state; /**< The connection state */
|
||||
char *username; /**< The login name of the user */
|
||||
} MAXSCALED;
|
||||
|
||||
#define MAXSCALED_STATE_LOGIN 1 /**< Waiting for credentials */
|
||||
#define MAXSCALED_STATE_DATA 2 /**< User logged in */
|
||||
#define MAXSCALED_STATE_LOGIN 1 /**< Waiting for user */
|
||||
#define MAXSCALED_STATE_PASSWD 2 /**< Waiting for password */
|
||||
#define MAXSCALED_STATE_DATA 3 /**< User logged in */
|
||||
|
||||
#endif
|
||||
|
@ -547,7 +547,7 @@ static MONITOR_SERVERS *build_mysql51_replication_tree(MONITOR *mon)
|
||||
if (mysql_num_rows(result) > 0)
|
||||
{
|
||||
ismaster = true;
|
||||
while (nslaves < MONITOR_MAX_NUM_SLAVES && (row = mysql_fetch_row(result)))
|
||||
while (nslaves < MAX_NUM_SLAVES && (row = mysql_fetch_row(result)))
|
||||
{
|
||||
/* get Slave_IO_Running and Slave_SQL_Running values*/
|
||||
database->server->slaves[nslaves] = atol(row[0]);
|
||||
@ -835,13 +835,7 @@ monitorMain(void *arg)
|
||||
monitorDatabase(mon, ptr);
|
||||
|
||||
/* reset the slave list of current node */
|
||||
if (ptr->server->slaves)
|
||||
{
|
||||
MXS_FREE(ptr->server->slaves);
|
||||
}
|
||||
/* create a new slave list */
|
||||
ptr->server->slaves = (long *) MXS_CALLOC(MONITOR_MAX_NUM_SLAVES, sizeof(long));
|
||||
MXS_ABORT_IF_NULL(ptr->server->slaves);
|
||||
memset(&ptr->server->slaves, 0, sizeof(ptr->server->slaves));
|
||||
|
||||
num_servers++;
|
||||
|
||||
@ -1448,7 +1442,8 @@ static MONITOR_SERVERS *get_replication_tree(MONITOR *mon, int num_servers)
|
||||
master = getServerByNodeId(mon->databases, current->master_id);
|
||||
if (master && master->server && master->server->node_id > 0)
|
||||
{
|
||||
add_slave_to_master(master->server->slaves, MONITOR_MAX_NUM_SLAVES, current->node_id);
|
||||
add_slave_to_master(master->server->slaves, sizeof(master->server->slaves),
|
||||
current->node_id);
|
||||
master->server->depth = current->depth - 1;
|
||||
monitor_set_pending_status(master, SERVER_MASTER);
|
||||
handle->master = master;
|
||||
@ -1509,7 +1504,7 @@ static int add_slave_to_master(long *slaves_list, int list_size, long node_id)
|
||||
{
|
||||
if (slaves_list[i] == 0)
|
||||
{
|
||||
memcpy(&slaves_list[i], &node_id, sizeof(long));
|
||||
slaves_list[i] = node_id;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
5
server/modules/protocol/examples/CMakeLists.txt
Normal file
5
server/modules/protocol/examples/CMakeLists.txt
Normal file
@ -0,0 +1,5 @@
|
||||
install_script(cdc.py core)
|
||||
install_script(cdc_users.py core)
|
||||
install_script(cdc_last_transaction.py core)
|
||||
install_script(cdc_kafka_producer.py core)
|
||||
install_file(cdc_schema.go core)
|
@ -66,6 +66,8 @@ MODULE_INFO info =
|
||||
|
||||
static char *version_str = "V2.0.0";
|
||||
|
||||
#define GETPWUID_BUF_LEN 255
|
||||
|
||||
static int maxscaled_read_event(DCB* dcb);
|
||||
static int maxscaled_write_event(DCB *dcb);
|
||||
static int maxscaled_write(DCB *dcb, GWBUF *queue);
|
||||
@ -76,6 +78,100 @@ static int maxscaled_close(DCB *dcb);
|
||||
static int maxscaled_listen(DCB *dcb, char *config);
|
||||
static char *mxsd_default_auth();
|
||||
|
||||
static bool authenticate_unix_socket(MAXSCALED *protocol, DCB *dcb)
|
||||
{
|
||||
bool authenticated = false;
|
||||
|
||||
struct ucred ucred;
|
||||
socklen_t len = sizeof(struct ucred);
|
||||
|
||||
/* Get UNIX client credentials from socket*/
|
||||
if (getsockopt(dcb->fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == 0)
|
||||
{
|
||||
struct passwd pw_entry;
|
||||
struct passwd *pw_tmp;
|
||||
char buf[GETPWUID_BUF_LEN];
|
||||
|
||||
/* Fetch username from UID */
|
||||
if (getpwuid_r(ucred.uid, &pw_entry, buf, sizeof(buf), &pw_tmp) == 0)
|
||||
{
|
||||
GWBUF *username;
|
||||
|
||||
/* Set user in protocol */
|
||||
protocol->username = strdup(pw_entry.pw_name);
|
||||
|
||||
username = gwbuf_alloc(strlen(protocol->username) + 1);
|
||||
|
||||
strcpy(GWBUF_DATA(username), protocol->username);
|
||||
|
||||
/* Authenticate the user */
|
||||
if (dcb->authfunc.extract(dcb, username) == 0 &&
|
||||
dcb->authfunc.authenticate(dcb) == 0)
|
||||
{
|
||||
dcb_printf(dcb, MAXADMIN_AUTH_SUCCESS_REPLY);
|
||||
protocol->state = MAXSCALED_STATE_DATA;
|
||||
dcb->user = strdup(protocol->username);
|
||||
}
|
||||
else
|
||||
{
|
||||
dcb_printf(dcb, MAXADMIN_AUTH_FAILED_REPLY);
|
||||
}
|
||||
|
||||
gwbuf_free(username);
|
||||
|
||||
authenticated = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
MXS_ERROR("Failed to get UNIX user %ld details for 'MaxScale Admin'",
|
||||
(unsigned long)ucred.uid);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MXS_ERROR("Failed to get UNIX domain socket credentials for 'MaxScale Admin'.");
|
||||
}
|
||||
|
||||
return authenticated;
|
||||
}
|
||||
|
||||
|
||||
static bool authenticate_inet_socket(MAXSCALED *protocol, DCB *dcb)
|
||||
{
|
||||
dcb_printf(dcb, MAXADMIN_AUTH_USER_PROMPT);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool authenticate_socket(MAXSCALED *protocol, DCB *dcb)
|
||||
{
|
||||
bool authenticated = false;
|
||||
|
||||
struct sockaddr address;
|
||||
socklen_t address_len = sizeof(address);
|
||||
|
||||
if (getsockname(dcb->fd, &address, &address_len) == 0)
|
||||
{
|
||||
if (address.sa_family == AF_UNIX)
|
||||
{
|
||||
authenticated = authenticate_unix_socket(protocol, dcb);
|
||||
}
|
||||
else
|
||||
{
|
||||
authenticated = authenticate_inet_socket(protocol, dcb);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
char errbuf[STRERROR_BUFLEN];
|
||||
|
||||
MXS_ERROR("Could not get socket family of client connection: %s",
|
||||
strerror_r(errno, errbuf, sizeof(errbuf)));
|
||||
}
|
||||
|
||||
return authenticated;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The "module object" for the maxscaled protocol module.
|
||||
*/
|
||||
@ -161,10 +257,41 @@ static int maxscaled_read_event(DCB* dcb)
|
||||
{
|
||||
if (GWBUF_LENGTH(head))
|
||||
{
|
||||
if (maxscaled->state == MAXSCALED_STATE_DATA)
|
||||
switch (maxscaled->state)
|
||||
{
|
||||
SESSION_ROUTE_QUERY(dcb->session, head);
|
||||
dcb_printf(dcb, "OK");
|
||||
case MAXSCALED_STATE_LOGIN:
|
||||
{
|
||||
maxscaled->username = strndup(GWBUF_DATA(head), GWBUF_LENGTH(head));
|
||||
maxscaled->state = MAXSCALED_STATE_PASSWD;
|
||||
dcb_printf(dcb, MAXADMIN_AUTH_PASSWORD_PROMPT);
|
||||
gwbuf_free(head);
|
||||
}
|
||||
break;
|
||||
|
||||
case MAXSCALED_STATE_PASSWD:
|
||||
{
|
||||
char *password = strndup(GWBUF_DATA(head), GWBUF_LENGTH(head));
|
||||
if (admin_verify_inet_user(maxscaled->username, password))
|
||||
{
|
||||
dcb_printf(dcb, MAXADMIN_AUTH_SUCCESS_REPLY);
|
||||
maxscaled->state = MAXSCALED_STATE_DATA;
|
||||
}
|
||||
else
|
||||
{
|
||||
dcb_printf(dcb, MAXADMIN_AUTH_FAILED_REPLY);
|
||||
maxscaled->state = MAXSCALED_STATE_LOGIN;
|
||||
}
|
||||
gwbuf_free(head);
|
||||
free(password);
|
||||
}
|
||||
break;
|
||||
|
||||
case MAXSCALED_STATE_DATA:
|
||||
{
|
||||
SESSION_ROUTE_QUERY(dcb->session, head);
|
||||
dcb_printf(dcb, "OK");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -241,9 +368,9 @@ static int maxscaled_accept(DCB *listener)
|
||||
|
||||
while ((client_dcb = dcb_accept(listener, &MyObject)) != NULL)
|
||||
{
|
||||
MAXSCALED *maxscaled_protocol = NULL;
|
||||
MAXSCALED *maxscaled_protocol = (MAXSCALED *)calloc(1, sizeof(MAXSCALED));
|
||||
|
||||
if ((maxscaled_protocol = (MAXSCALED *)MXS_CALLOC(1, sizeof(MAXSCALED))) == NULL)
|
||||
if (!maxscaled_protocol)
|
||||
{
|
||||
dcb_close(client_dcb);
|
||||
continue;
|
||||
@ -252,52 +379,14 @@ static int maxscaled_accept(DCB *listener)
|
||||
maxscaled_protocol->username = NULL;
|
||||
maxscaled_protocol->state = MAXSCALED_STATE_LOGIN;
|
||||
|
||||
/* Get UNIX client credentials from socket*/
|
||||
if (getsockopt(client_dcb->fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == -1)
|
||||
bool authenticated = false;
|
||||
|
||||
if (!authenticate_socket(maxscaled_protocol, client_dcb))
|
||||
{
|
||||
MXS_ERROR("Failed to get UNIX socket credentials for 'MaxScale Admin'");
|
||||
dcb_close(client_dcb);
|
||||
free(maxscaled_protocol);
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
struct passwd pw_entry;
|
||||
struct passwd *pw_tmp;
|
||||
char buf[MAXADMIN_GETPWUID_BUF_LEN];
|
||||
|
||||
/* Fetch username from UID */
|
||||
if (!getpwuid_r(ucred.uid, &pw_entry, buf, sizeof(buf), &pw_tmp))
|
||||
{
|
||||
GWBUF *username;
|
||||
|
||||
/* Set user in protocol */
|
||||
maxscaled_protocol->username = MXS_STRDUP_A(pw_entry.pw_name);
|
||||
|
||||
username = gwbuf_alloc(strlen(maxscaled_protocol->username) + 1);
|
||||
|
||||
strcpy(GWBUF_DATA(username), maxscaled_protocol->username);
|
||||
|
||||
/* Authenticate the user */
|
||||
if (client_dcb->authfunc.extract(client_dcb, username) == 0 &&
|
||||
client_dcb->authfunc.authenticate(client_dcb) == 0)
|
||||
{
|
||||
dcb_printf(client_dcb, "OK----");
|
||||
maxscaled_protocol->state = MAXSCALED_STATE_DATA;
|
||||
client_dcb->user = MXS_STRDUP_A(maxscaled_protocol->username);
|
||||
}
|
||||
else
|
||||
{
|
||||
dcb_printf(client_dcb, "FAILED");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MXS_ERROR("Failed to get UNIX user %ld details for 'MaxScale Admin'",
|
||||
(unsigned long)ucred.uid);
|
||||
dcb_close(client_dcb);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
spinlock_init(&maxscaled_protocol->lock);
|
||||
client_dcb->protocol = (void *)maxscaled_protocol;
|
||||
@ -350,7 +439,7 @@ static int maxscaled_close(DCB *dcb)
|
||||
*/
|
||||
static int maxscaled_listen(DCB *listener, char *config)
|
||||
{
|
||||
char *socket_path;
|
||||
char *socket_path = NULL;
|
||||
|
||||
/* check for default UNIX socket */
|
||||
if (strncmp(config, MAXADMIN_CONFIG_DEFAULT_SOCKET_TAG, MAXADMIN_CONFIG_DEFAULT_SOCKET_TAG_LEN) == 0)
|
||||
@ -359,19 +448,8 @@ static int maxscaled_listen(DCB *listener, char *config)
|
||||
}
|
||||
else
|
||||
{
|
||||
socket_path = config;
|
||||
socket_path = config;
|
||||
}
|
||||
|
||||
/* check for UNIX socket path*/
|
||||
if (strchr(socket_path,'/') == NULL)
|
||||
{
|
||||
MXS_ERROR("Failed to start listening on '%s' with 'MaxScale Admin' protocol,"
|
||||
" only UNIX domain sockets are supported. Remove all 'port' and 'address'"
|
||||
" parameters from this listener and add 'socket=default' to enable UNIX domain sockets.", socket_path);
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return (dcb_listen(listener, socket_path, "MaxScale Admin") < 0) ? 0 : 1;
|
||||
}
|
||||
return (dcb_listen(listener, socket_path, "MaxScale Admin") < 0) ? 0 : 1;
|
||||
}
|
||||
|
@ -203,7 +203,7 @@ static int telnetd_read_event(DCB* dcb)
|
||||
{
|
||||
*t = 0;
|
||||
}
|
||||
if (admin_verify(telnetd->username, password))
|
||||
if (admin_verify_inet_user(telnetd->username, password))
|
||||
{
|
||||
telnetd_echo(dcb, 1);
|
||||
telnetd->state = TELNETD_STATE_DATA;
|
||||
|
@ -5,11 +5,6 @@ if(AVRO_FOUND)
|
||||
set_target_properties(avrorouter PROPERTIES LINK_FLAGS -Wl,-z,defs)
|
||||
target_link_libraries(avrorouter maxscale-common jansson ${AVRO_LIBRARIES} maxavro sqlite3 lzma)
|
||||
install_module(avrorouter core)
|
||||
install_script(cdc core)
|
||||
install_script(cdc_users core)
|
||||
install_script(cdc_last_transaction core)
|
||||
install_script(cdc_kafka_producer core)
|
||||
install_file(cdc_schema.go core)
|
||||
else()
|
||||
message(STATUS "Avro C libraries were not found, avrorouter will not be built.")
|
||||
endif()
|
||||
|
@ -223,8 +223,8 @@ struct subcommand showoptions[] = {
|
||||
"Show the status of the polling threads in MaxScale",
|
||||
{0, 0, 0} },
|
||||
{ "users", 0, telnetdShowUsers,
|
||||
"Show statistics and user names for the debug interface",
|
||||
"Show statistics and user names for the debug interface",
|
||||
"Show all maxadmin enabled Linux accounts and created maxadmin users",
|
||||
"Show all maxadmin enabled Linux accounts and created maxadmin users",
|
||||
{0, 0, 0} },
|
||||
{ NULL, 0, NULL, NULL, NULL,
|
||||
{0, 0, 0} }
|
||||
@ -449,6 +449,8 @@ static void enable_syslog();
|
||||
static void disable_syslog();
|
||||
static void enable_maxlog();
|
||||
static void disable_maxlog();
|
||||
static void enable_account(DCB *, char *user);
|
||||
static void disable_account(DCB *, char *user);
|
||||
|
||||
/**
|
||||
* * The subcommands of the enable command
|
||||
@ -536,6 +538,16 @@ struct subcommand enableoptions[] = {
|
||||
"Enable maxlog logging",
|
||||
{0, 0, 0}
|
||||
},
|
||||
{
|
||||
"account",
|
||||
1,
|
||||
enable_account,
|
||||
"Enable maxadmin usage for Linux user. E.g.:\n"
|
||||
" MaxScale> enable account alice",
|
||||
"Enable maxadmin usage for Linux user. E.g.:\n"
|
||||
" MaxScale> enable account alice",
|
||||
{ARG_TYPE_STRING, 0, 0}
|
||||
},
|
||||
{
|
||||
NULL,
|
||||
0,
|
||||
@ -634,6 +646,16 @@ struct subcommand disableoptions[] = {
|
||||
"Disable maxlog logging",
|
||||
{0, 0, 0}
|
||||
},
|
||||
{
|
||||
"account",
|
||||
1,
|
||||
disable_account,
|
||||
"Disable maxadmin usage for Linux user. E.g.:\n"
|
||||
" MaxScale> disable account alice",
|
||||
"Disable maxadmin usage for Linux user. E.g.:\n"
|
||||
" MaxScale> disable account alice",
|
||||
{ARG_TYPE_STRING, 0, 0}
|
||||
},
|
||||
{
|
||||
NULL,
|
||||
0,
|
||||
@ -688,32 +710,38 @@ struct subcommand failoptions[] = {
|
||||
};
|
||||
#endif /* FAKE_CODE */
|
||||
|
||||
static void telnetdAddUser(DCB *, char *);
|
||||
static void telnetdAddUser(DCB *, char *user, char *password);
|
||||
|
||||
/**
|
||||
* The subcommands of the add command
|
||||
*/
|
||||
struct subcommand addoptions[] = {
|
||||
{ "user", 1, telnetdAddUser,
|
||||
"Add a new user for the debug interface. E.g. add user john",
|
||||
"Add a new user for the debug interface. E.g. add user john",
|
||||
{ARG_TYPE_STRING, 0, 0} },
|
||||
{ "user", 2, telnetdAddUser,
|
||||
"Add insecure account for using maxadmin over the network. E.g.:\n"
|
||||
" MaxScale> add user bob somepass",
|
||||
"Add insecure account for using maxadmin over the network. E.g.:\n"
|
||||
" MaxScale> add user bob somepass",
|
||||
{ARG_TYPE_STRING, ARG_TYPE_STRING, 0} },
|
||||
{ NULL, 0, NULL, NULL, NULL,
|
||||
{0, 0, 0} }
|
||||
};
|
||||
|
||||
|
||||
static void telnetdRemoveUser(DCB *, char *);
|
||||
static void telnetdRemoveUser(DCB *, char *user, char *password);
|
||||
|
||||
/**
|
||||
* The subcommands of the remove command
|
||||
*/
|
||||
struct subcommand removeoptions[] = {
|
||||
{
|
||||
"user",
|
||||
1,
|
||||
2,
|
||||
telnetdRemoveUser,
|
||||
"Remove existing maxscale user. Example : remove user john",
|
||||
"Remove existing maxscale user. Example : remove user john",
|
||||
{ARG_TYPE_STRING, 0, 0}
|
||||
"Remove account for using maxadmin over the network. E.g.:\n"
|
||||
" MaxAdmin> remove user bob somepass",
|
||||
"Remove account for using maxadmin over the network. E.g.:\n"
|
||||
" MaxAdmin> remove user bob somepass",
|
||||
{ARG_TYPE_STRING, ARG_TYPE_STRING, 0}
|
||||
},
|
||||
{
|
||||
NULL, 0, NULL, NULL, NULL, {0, 0, 0}
|
||||
@ -1302,63 +1330,61 @@ reload_config(DCB *dcb)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new maxscale admin user
|
||||
* Add a new remote (insecure, over the network) maxscale admin user
|
||||
*
|
||||
* @param dcb The DCB for messages
|
||||
* @param user The user name
|
||||
* @param dcb The DCB for messages
|
||||
* @param user The user name
|
||||
* @param user The user password
|
||||
*/
|
||||
static void
|
||||
telnetdAddUser(DCB *dcb, char *user)
|
||||
telnetdAddUser(DCB *dcb, char *user, char *password)
|
||||
{
|
||||
char *err;
|
||||
const char *err;
|
||||
|
||||
if (admin_search_user(user))
|
||||
if (admin_inet_user_exists(user))
|
||||
{
|
||||
dcb_printf(dcb, "User %s already exists.\n", user);
|
||||
dcb_printf(dcb, "Account %s for remote (network) usage already exists.\n", user);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((err = admin_add_user(user)) == NULL)
|
||||
if ((err = admin_add_inet_user(user, password)) == NULL)
|
||||
{
|
||||
dcb_printf(dcb, "User %s has been successfully added.\n", user);
|
||||
dcb_printf(dcb, "Account %s for remote (network) usage has been successfully added.\n", user);
|
||||
}
|
||||
else
|
||||
{
|
||||
dcb_printf(dcb, "Failed to add new user. %s\n", err);
|
||||
dcb_printf(dcb, "Failed to add new remote account %s: %s.\n", user, err);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove a maxscale admin user
|
||||
* Remove a remote (insecure, over the network) maxscale admin user
|
||||
*
|
||||
* @param dcb The DCB for messages
|
||||
* @param user The user name
|
||||
* @param dcb The DCB for messages
|
||||
* @param user The user name
|
||||
* @param user The user password
|
||||
*/
|
||||
static void telnetdRemoveUser(
|
||||
DCB* dcb,
|
||||
char* user)
|
||||
static void telnetdRemoveUser(DCB *dcb, char *user, char *password)
|
||||
{
|
||||
char* err;
|
||||
const char* err;
|
||||
|
||||
if (!admin_search_user(user))
|
||||
if (!admin_inet_user_exists(user))
|
||||
{
|
||||
dcb_printf(dcb, "User %s doesn't exist.\n", user);
|
||||
dcb_printf(dcb, "Account %s for remote (network) usage does not exist.\n", user);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((err = admin_remove_user(user)) == NULL)
|
||||
if ((err = admin_remove_inet_user(user, password)) == NULL)
|
||||
{
|
||||
dcb_printf(dcb, "User %s has been successfully removed.\n", user);
|
||||
dcb_printf(dcb, "Account %s for remote (network) usage has been successfully removed.\n", user);
|
||||
}
|
||||
else
|
||||
{
|
||||
dcb_printf(dcb, "Failed to remove user %s. %s\n", user, err);
|
||||
dcb_printf(dcb, "Failed to remove remote account %s: %s\n", user, err);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Print the adminsitration users
|
||||
*
|
||||
@ -1367,7 +1393,6 @@ static void telnetdRemoveUser(
|
||||
static void
|
||||
telnetdShowUsers(DCB *dcb)
|
||||
{
|
||||
dcb_printf(dcb, "Administration interface users:\n");
|
||||
dcb_PrintAdminUsers(dcb);
|
||||
}
|
||||
|
||||
@ -1869,6 +1894,60 @@ disable_maxlog()
|
||||
mxs_log_set_maxlog_enabled(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable a Linux account
|
||||
*
|
||||
* @param dcb The DCB for messages
|
||||
* @param user The Linux user name
|
||||
*/
|
||||
static void
|
||||
enable_account(DCB *dcb, char *user)
|
||||
{
|
||||
const char *err;
|
||||
|
||||
if (admin_linux_account_enabled(user))
|
||||
{
|
||||
dcb_printf(dcb, "The Linux user %s has already been enabled.\n", user);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((err = admin_enable_linux_account(user)) == NULL)
|
||||
{
|
||||
dcb_printf(dcb, "The Linux user %s has successfully been enabled.\n", user);
|
||||
}
|
||||
else
|
||||
{
|
||||
dcb_printf(dcb, "Failed to enable the Linux user %s: %s\n", user, err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable a Linux account
|
||||
*
|
||||
* @param dcb The DCB for messages
|
||||
* @param user The Linux user name
|
||||
*/
|
||||
static void
|
||||
disable_account(DCB *dcb, char *user)
|
||||
{
|
||||
const char* err;
|
||||
|
||||
if (!admin_linux_account_enabled(user))
|
||||
{
|
||||
dcb_printf(dcb, "The Linux user %s has not been enabled.\n", user);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((err = admin_disable_linux_account(user)) == NULL)
|
||||
{
|
||||
dcb_printf(dcb, "The Linux user %s has successfully been disabled.\n", user);
|
||||
}
|
||||
else
|
||||
{
|
||||
dcb_printf(dcb, "Failed to disable the Linux user %s: %s\n", user, err);
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(FAKE_CODE)
|
||||
static void fail_backendfd(void)
|
||||
{
|
||||
|
@ -1016,12 +1016,18 @@ static BACKEND *get_root_master(BACKEND **servers)
|
||||
{
|
||||
if (servers[i] && (servers[i]->server->status & (SERVER_MASTER | SERVER_MAINT)) == SERVER_MASTER)
|
||||
{
|
||||
if (master_host && servers[i]->server->depth < master_host->server->depth)
|
||||
if (master_host == NULL)
|
||||
{
|
||||
master_host = servers[i];
|
||||
}
|
||||
else if (master_host == NULL)
|
||||
else if (servers[i]->server->depth < master_host->server->depth ||
|
||||
(servers[i]->server->depth == master_host->server->depth &&
|
||||
servers[i]->weight > master_host->weight))
|
||||
{
|
||||
/**
|
||||
* This master has a lower depth than the candidate master or
|
||||
* the depths are equal but this master has a higher weight
|
||||
*/
|
||||
master_host = servers[i];
|
||||
}
|
||||
}
|
||||
|
@ -1384,7 +1384,6 @@ static route_target_t get_route_target(ROUTER_CLIENT_SES *rses,
|
||||
}
|
||||
else
|
||||
{
|
||||
/** hints don't affect on routing */
|
||||
ss_dassert(trx_active ||
|
||||
(QUERY_IS_TYPE(qtype, QUERY_TYPE_WRITE) ||
|
||||
QUERY_IS_TYPE(qtype, QUERY_TYPE_MASTER_READ) ||
|
||||
|
Loading…
x
Reference in New Issue
Block a user