I do not have a total solution and it does require some "outside influence" (ie: its not 100% modx based) but it works for most of my needs with a few gotchas along the way.
First gotchas.
------------------
The initial connection must (or it so appears) be mutable (ie: read and write). I initially had slave db (this is readonly but not technically locked down due to temp table needs) as the initial connection and master db (read/write) as secondary connection. However when writing to db the write went on the slave db (init conn) and not the master (secondary conn).
Second gotcha
-------------------
You cannot run MODX in a readonly mode. There is a loop condition that loops to find the mutable conn if current conn is mutable === false (ie: readonly). However if that is all you have then the while(){ loops forever } and takes everything with it.
This can be found in line 415 (ver2.2.7) of xpdo.class.php. The while loop will never end.
public function &getConnection(array $options = array()) {
$conn =& $this->connection;
$mutable = $this->getOption(xPDO::OPT_CONN_MUTABLE, $options, null);
if (!($conn instanceof xPDOConnection) || ($mutable !== null && (($mutable == true && !$conn->isMutable()) || ($mutable == false && $conn->isMutable())))) {
if (!empty($this->_connections)) {
shuffle($this->_connections);
$conn = reset($this->_connections);
## This is the LOOP ##
##################
while ($conn) {
if ($mutable !== null && (($mutable == true && !$conn->isMutable()) || ($mutable == false && $conn->isMutable()))) {
$conn = next($this->_connections);
continue;
}
$this->connection =& $conn;
break;
}
##################
} else {
$this->log(xPDO::LOG_LEVEL_ERROR, "Could not get a valid xPDOConnection", '', __METHOD__, __FILE__, __LINE__);
}
}
return $this->connection;
}
My Solution for now
----------------------
I have added my own include() into the config.inc.php file that dynamically changes the connection info.
NOTE(1): This configuration support a West/East Webserver as well as a West/East DB server all on separate virtual machines.
NOTE(2): This code is shared to give an idea of how it was achieved. We have additional "pinger" scripts and apache settings that are supporting things line $_SERVER[vars] and /tmp/westdown file creation
NOTE(3): Assuming you have a well cached site there is a chance that the site will work when the connection is set to "east" only (because it never actually connects). However /manager/ will of course hit that loop and choke. I solved this by taking manager offline (this is noted in 'East UP ON West' and 'East UP ON East') when in these conditions and REQUEST is for /manager/ we redirect to a offline.html file I created that is based on the manager index.html login page with the login formed removed and replaced by a offline message instead.
## Simulation tests
$sim_west_down = true;
$sim_east_down = false;
$sim_on_west = false;
$sim_on_east = false;
$pm_db_status['west'] = true;
$pm_db_status['east'] = true;
if(is_file('/tmp/westdown') || $sim_west_down){
$pm_db_status['west'] = false;
}
if(is_file('/tmp/eastdown') || $sim_east_down){
$pm_db_status['east'] = false;
}
## Am I West or East
switch($_SERVER['SERVER_ADDR']){
case '10.11.1.20':
$pm_webserver_local = 'west';
break;
case '10.11.2.20':
default:
$pm_webserver_local = 'east';
break;
}
if($sim_on_west){
$pm_webserver_local = 'west';
}
else if($sim_on_east){
$pm_webserver_local = 'east';
}
define('PM_DB_EAST',$pm_db_status['east']);
define('PM_DB_WEST',$pm_db_status['west']);
define('PM_WEBSERVER_LOCAL',$pm_webserver_local);
$rw_db = 'db.west.somedomain.net';
$rw_username = '####';
$rw_password = '??????';
$ro_db = 'db.east.somedomain.net';
$ro_username = '&&&&&';
$ro_password = '*******';
if(PM_WEBSERVER_LOCAL == 'west'){
if(PM_DB_WEST){
$conn_config = 'West UP ON West';
$database_server = $rw_db; // (READONLY)
$database_user = $rw_username;
$database_password = $rw_password;
$config_options = array(); // if on west and west is UP then everything stays on west on need one conn
}
else if (PM_DB_EAST){
$conn_config = 'East UP ON West';
$database_server = $ro_db; // (READONLY)
$database_user = $ro_username;
$database_password = $ro_password;
$config_options = array(
xPDO::OPT_CONN_MUTABLE => false,
xPDO::OPT_CONN_INIT => array(xPDO::OPT_CONN_MUTABLE => false),
xPDO::OPT_CONNECTIONS => array()
); // if on west and west is DOWN then everything goes to east is READONLY (OPT_CONN_MUTABLE => false)
if(substr_count($_SERVER['REQUEST_URI'],'/manager/') > 0){
header('Location:/manager/offline.html');
die;
}
}
else{ // Everything is down NOW WHAT.
}
}
else{ // we are on East OR other
if(PM_DB_WEST){
$conn_config = 'West UP ON East';
$database_server = $rw_db; // (READONLY)
$database_user = $rw_username;
$database_password = $rw_password;
$config_options = array(
xPDO::OPT_CONN_MUTABLE => true,
xPDO::OPT_CONN_INIT => array(xPDO::OPT_CONN_MUTABLE => true),
xPDO::OPT_CONNECTIONS => array(
array(
'dsn' => 'mysql:host=' . $ro_db . ';dbname=modx;charset=utf8',
'username' => $ro_username,
'password' => $ro_password,
'options' => array(
xPDO::OPT_CONN_MUTABLE => false,
),
'driverOptions' => array(),
) // if on East and West is UP then Write requests go to master (OPT_CONN_MUTABLE => true)
)
);
}
else if (PM_DB_EAST){
$conn_config = 'East UP ON East';
$database_server = $ro_db; // (READONLY)
$database_user = $ro_username;
$database_password = $ro_password;
$config_options = array(
xPDO::OPT_CONN_MUTABLE => false,
xPDO::OPT_CONN_INIT => array(xPDO::OPT_CONN_MUTABLE => false),
xPDO::OPT_CONNECTIONS => array()
); // if on west and west is DOWN then everything goes to east is READONLY (OPT_CONN_MUTABLE => false)
if(substr_count($_SERVER['REQUEST_URI'],'/manager/') > 0){
header('Location:/manager/offline.html');
die;
}
}
else{ // Everything is down NOW WHAT.
}
}