Files
namaste/classes/gasResourceManager.class.inc
gramps 373ebc8c93 Archive: Namaste PHP AMQP framework v1.0 (2017-2020)
952 days continuous production uptime, 40k+ tp/s single node.
Original corpo Bitbucket history not included — clean archive commit.
2026-04-05 09:49:30 -07:00

1559 lines
72 KiB
PHP

<?php
/**
* ResourceManager Class
*
* This singleton class is responsible for establishing, and maintaining, connections to external services.
*
* As of this writing, the following services are supported:
*
* - DynamoDB (Ddb)
* - Cache (memcached or redis)
* - RabbitMQ (SSL and Non-SSL)
* - Logger service (a secondary, off-zone nosql repo for storing log info)
* - Admin (RabbitMQ) - connection resource to the GA Admin Server
*
* When something in the framework needs to access a service, then they statically call the fetchResource() method
* in this class to obtain the resource object to the desired service.
*
* Successful requests will return a resource object, otherwise a null value is returned.
*
* @author mike@givingassistant.org
* @version 1.0
*
* HISTORY:
* ========
* 06-07-17 mks initial coding begins
* 06-29-17 mks CORE-458: mysql resource management added
* 09-12-17 mks CORE-561: rip out mysql stuffs and replace with PDO stuffs
* 03-02-18 mks CORE-680: deprecated trace logging
* 05-04-18 mks _INF-188: wh code
* 05-31-18 mks CORE-1011: update for new XML broker services configuration
* 07-10-18 mks CORE-773: replaced echo statements with consoleLog()
* 07-23-18 mks CORE-1097: refactoring for mongo admin resources (master, slave)
* 07-24-18 mks CORE-1099: deprecated mongo slave resource allocation and management
* 08-02-18 mks CORE-774: PHP7.2 exception handling
* 08-21-18 mks DB-49: fixed code where ReadPreference was being replaced with intval causing exception
* 08-22-18 mks DB-49: making console log the default output vector, positive milestone checks added
* 01-08-20 mks DB-150: PHP7.4 class member type-casting
* 04-05-20 mks ECI-136: commented-out some unused member variables
* 07-29-30 mks DB-156: tercero integration and support (mongodb)
*
*/
use Aws\DynamoDb\DynamoDbClient as DynamoDbClientAlias;
use MongoDB\Driver\Manager as ManagerAlias;
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Connection\AMQPSSLConnection;
class gasResourceManager {
private string $class;
private static ?string $location = null; // used in fetchResource() to determine which resource to init
private static ?gasResourceManager $instance = null;
private static string $certPath;
private static bool $debug = false;
private static string $certFile;
private static string $caFile;
private static bool $resourcesChecked = false;
private static string $myID = 'RESM: '; // resource manager tag for console error output
// IF YOU ADD NEW BROKER SERVICES, UPDATE THIS ARRAY!!!
private static array $brokerServices = [ CONFIG_BROKER_APPSERVER, BROKER_SEGUNDO, BROKER_TERCERO, BROKER_ADMIN ];
private static gacErrorLogger $logger;
public static ?array $cfgDdb;
public static ?array $cfgBroker;
public static ?array $cfgSegundo;
public static ?array $cfgTercero;
public static ?array $cfgAdmin;
public static ?array $cfgLogger;
public static ?array $cfgSecurity;
public static ?array $cfgCache;
public static ?array $cfgPDO;
public static ?array $cfgMongo;
public static bool $IPL = true;
public static bool $available = false;
public static bool $cacheAvailable = false;
public static bool $ddbAvailable = false;
// public static bool $rabbitAvailable = false;
public static bool $brokerAvailable = false;
public static bool $adminAvailable = false;
public static bool $segundoAvailable = false;
public static bool $terceroAvailable = false;
public static bool $PDOAvailable = false;
public static bool $PDOEnabled = false;
public static bool $PDOSlaveAvailable = false;
public static bool $PDOSlaveEnabled = false;
public static bool $mongoEnabled = false;
public static bool $mongoMasterAvailable = false;
// public static bool $mongoSlaveAvailable = false;
public static bool $mongoWHMasterAvailable = false;
// public static bool $mongoWHSlaveAvailable = false;
public static bool $mongoSegundoMasterAvailable = false;
public static bool $PDOWHMasterAvailable = false;
public static bool $PDOWHSlaveAvailable = false;
public static bool $mongoAdminMasterAvailable = false;
public static bool $mongoTerceroMasterAvailable = false;
// public static bool $mongoAdminSlaveAvailable = false;
private static ?DynamoDbClientAlias $resDdb = null;
private static ?gasCache $resCache = null;
private static ?AMQPStreamConnection $resBroker = null;
private static ?AMQPStreamConnection $resSegundo = null;
private static ?AMQPStreamConnection $resTercero = null;
private static ?AMQPStreamConnection $resAdmin = null;
private static ?PDO $resPDO = null;
private static ?PDO $resPDOSlave = null;
private static ?PDO $resPDOWHMaster = null;
private static ?PDO $resPDOWHSlave = null;
private static ?ManagerAlias $resMongoMaster = null;
private static ?ManagerAlias $resMongoWHMaster = null;
private static ?ManagerAlias $resMongoSegundoMaster = null;
private static ?ManagerAlias $resMongoAdminMaster = null;
private static ?ManagerAlias $resMongoTerceroMaster = null;
/**
* __construct() -- private method
*
* this is the class constructor which is invoked from the singleton() class method.
*
* the constructor reads the services configuration from the gasConfig class and breaks out the relevant
* configurations by service assigning the data to class variables.
*
* we also initially mark all of the resource services as unavailable so as to require explicit initialization
* to the respective service via the fetchResource() method.
*
* last, we generate a GUID and assign it to a local member variable - this is done more as an intellectual
* exercise, rather than anything useful for the framework, to show that PHP really can't do statics.
*
* @author mike@givingassistant.org
* @version 1.0
*
* HISTORY:
* ========
* 06-12-17 mks original coding
* 06-29-17 mks CORE-458: added mysql resources
* 09-12-17 mks CORE-561: replaced mysql resources with PDO resources
* 11-30-17 mks CORE-591: moved logger instantiation above resourceCheck() because that method
* calls debug logging
* 01-10-18 mks INF-147: fixed bug in PDO master resource check that prevented an error message from being
* logged on unsuccessful resource instantiation
* 05-31-18 mks CORE-1011: update for new XML broker services configuration
* 06-15-18 mks HOTFIX: better logging, ddb active checks
* 07-27-18 mks CORE-1108: logging update - use logger if available for info messages
*
*/
private function __construct()
{
if (static::$available)return;
register_shutdown_function(array($this, '__destruct'));
// NOTE: DURING IPL, LOGGER IS NOT YET INSTANTIATED SO ERROR/CHECKPOINT OUTPUT GOES TO CONSOLE
// die if we cannot access the global error or config objects
if (empty(gasConfig::$settings)) {
static::internalLog(sprintf(INFO_CKP_REACHED, basename(__FILE__), __LINE__, STRING_ERROR . ERROR_GCO_NA));
return;
}
static::$debug = intval(gasConfig::$settings[CONFIG_DEBUG]);
static::$debug = (static::$debug == 1 || static::$debug == 0) ? static::$debug : 0;
$this->class = get_class($this);
static::$cfgDdb = null;
static::$cfgBroker = null;
static::$cfgLogger = null;
static::$brokerAvailable = false;
static::$segundoAvailable = false;
static::$terceroAvailable = false;
static::$adminAvailable = false;
// copy the resource configuration blocks into their respective containers
if (!empty(gasConfig::$settings[CONFIG_DATABASE][CONFIG_DATABASE_DDB][CONFIG_DATABASE_DDB_APPSERVER])) {
static::$cfgDdb = gasConfig::$settings[CONFIG_DATABASE][CONFIG_DATABASE_DDB][CONFIG_DATABASE_DDB_APPSERVER];
} else {
static::internalLog(sprintf(INFO_CKP_REACHED, basename(__FILE__), __LINE__, ERROR_CONFIG_RESOURCE_404 . RESOURCE_DDB));
return;
}
if (!empty(gasConfig::$settings[CONFIG_SECURITY])) {
static::$cfgSecurity = gasConfig::$settings[CONFIG_SECURITY];
}
if (!empty(gasConfig::$settings[CONFIG_CACHE])) {
static::$cfgCache = gasConfig::$settings[CONFIG_CACHE];
}
if (!empty(gasConfig::$settings[CONFIG_BROKER_SERVICES])) {
static::$cfgBroker = gasConfig::$settings[CONFIG_BROKER_SERVICES];
static::$certPath = static::$cfgBroker[CONFIG_BROKER_CERT_PATH];
static::$certFile = static::$certPath . DIR_SSL_CLIENT . FILE_LOCAL_KEYCERT;
static::$caFile = static::$certPath . DIR_SSL_RMQ . FILE_CA_CERT;
}
if (!empty(gasConfig::$settings[CONFIG_DATABASE][CONFIG_DATABASE_PDO])) {
static::$cfgPDO = gasConfig::$settings[CONFIG_DATABASE][CONFIG_DATABASE_PDO];
static::$PDOEnabled = static::$cfgPDO[CONFIG_DATABASE_PDO_ENABLED];
}
if (!empty(gasConfig::$settings[CONFIG_DATABASE][CONFIG_DATABASE_MONGODB])) {
static::$cfgMongo = gasConfig::$settings[CONFIG_DATABASE][CONFIG_DATABASE_MONGODB];
static::$mongoEnabled = static::$cfgMongo[CONFIG_DATABASE_MONGODB_ENABLED];
}
if (!empty(gasConfig::$settings[CONFIG_BROKER_SERVICES][CONFIG_ADMIN])) {
static::$cfgAdmin = gasConfig::$settings[CONFIG_BROKER_SERVICES][CONFIG_ADMIN];
}
if (!empty(gasConfig::$settings[CONFIG_BROKER_SERVICES][CONFIG_BROKER_WH])) {
static::$cfgSegundo = gasConfig::$settings[CONFIG_BROKER_SERVICES][CONFIG_BROKER_WH];
}
// checkpoint
static::internalLog(INFO_CKP_CONFIG_LOADED);
try {
// all configs are loaded - instantiate the logger class for resourceCheck()
static::$logger = new gacErrorLogger();
// have to init the cache resource b/c PHP's faux statics...
@gasResourceManager::fetchResource(RESOURCE_CACHE);
// meat-n-taters part -- get/assign links to the external resources
if (!static::$resourcesChecked) {
static::resourceCheck();
static::$resourcesChecked = true;
}
static::internalLog(sprintf(INFO_CKP_REACHED, basename(__FILE__), __LINE__, 'resources checked and loaded'));
} catch (Throwable $t) {
static::internalLog(sprintf(INFO_CKP_REACHED, basename(__FILE__), __LINE__, ERROR_THROWABLE_EXCEPTION . COLON . $t->getMessage()));
return;
}
// validate the resource acquisitions and output the status of each
// DDB:
static::internalLog(sprintf(basename(__FILE__), __LINE__, 'Validating resources...'));
if (static::$cfgDdb[CONFIG_DATABASE_DDB_ENABLED] and !static::$ddbAvailable) {
static::$IPL = false;
$msg = sprintf(INFO_CKP_REACHED, basename(__FILE__), __LINE__, ERROR_RESOURCE_404 . RESOURCE_DDB);
static::internalLog($msg);
return;
} elseif (static::$cfgDdb[CONFIG_DATABASE_DDB_ENABLED]) {
static::internalLog(sprintf(INFO_CKP_REACHED, basename(__FILE__), __LINE__, RESOURCE_DDB . STUB_VALIDATED));
}
// Memcached:
if (!static::$cacheAvailable) {
static::$IPL = false;
$msg = sprintf(INFO_CKP_REACHED, basename(__FILE__), __LINE__, ERROR_RESOURCE_404 . RESOURCE_CACHE);
static::internalLog($msg);
} else {
static::internalLog(sprintf(INFO_CKP_REACHED, basename(__FILE__), __LINE__, RESOURCE_CACHE . STUB_VALIDATED));
}
// namaste primary "broker" is always required
if (!static::$brokerAvailable) {
static::$IPL = false;
$msg = sprintf(INFO_CKP_REACHED, basename(__FILE__), __LINE__, ERROR_RESOURCE_404 . RESOURCE_BROKER);
static::internalLog($msg);
return;
} else {
static::internalLog(sprintf(basename(__FILE__), __LINE__, RESOURCE_BROKER . STUB_VALIDATED));
}
// namaste admin broker is always required
if (!static::$adminAvailable) {
static::$IPL = false;
$msg = sprintf(INFO_CKP_REACHED, basename(__FILE__), __LINE__, ERROR_RESOURCE_404 . RESOURCE_ADMIN);
static::internalLog($msg);
return;
} else {
static::internalLog(sprintf(INFO_CKP_REACHED, basename(__FILE__), __LINE__, RESOURCE_ADMIN . STUB_VALIDATED));
}
// namaste secondary service is optional
if (gasConfig::$settings[RESOURCE_SEGUNDO] === 1 and !static::$segundoAvailable) {
static::$IPL = false;
$msg = sprintf(INFO_CKP_REACHED, basename(__FILE__), __LINE__, ERROR_RESOURCE_404 . RESOURCE_SEGUNDO);
static::internalLog($msg);
return;
} elseif (static::$segundoAvailable) {
static::internalLog(sprintf(INFO_CKP_REACHED, basename(__FILE__), __LINE__, RESOURCE_SEGUNDO . STUB_VALIDATED));
}
if (gasConfig::$settings[RESOURCE_TERCERO] === 1 and !static::$terceroAvailable) {
static::$IPL = false;
$msg = sprintf(INFO_CKP_REACHED, basename(__FILE__), __LINE__, ERROR_RESOURCE_404 . RESOURCE_TERCERO);
static::internalLog($msg);
return;
} elseif (static::$terceroAvailable) {
static::internalLog(sprintf(INFO_CKP_REACHED, basename(__FILE__), __LINE__, RESOURCE_TERCERO . STUB_VALIDATED));
}
// for PDO, it's required only if PDO driver for mariaDB has been enabled
if (static::$PDOEnabled and !static::$PDOAvailable) {
static::$IPL = false;
$msg = sprintf(INFO_CKP_REACHED, basename(__FILE__), __LINE__, ERROR_RESOURCE_404 . RESOURCE_PDO_MASTER);
static::internalLog($msg);
return;
} elseif (static::$PDOEnabled) {
static::internalLog(sprintf(INFO_CKP_REACHED, basename(__FILE__), __LINE__, RESOURCE_PDO_MASTER . STUB_VALIDATED));
}
// same for mongo master - it's required only if it's been enabled for the current configuration
if (static::$mongoEnabled and !static::$mongoMasterAvailable) {
static::$IPL = false;
$msg = sprintf(INFO_CKP_REACHED, basename(__FILE__), __LINE__, ERROR_RESOURCE_404 . RESOURCE_MONGO_MASTER);
static::internalLog($msg);
return;
} elseif (static::$mongoEnabled) {
static::internalLog(sprintf(INFO_CKP_REACHED, basename(__FILE__), __LINE__, RESOURCE_MONGO_MASTER . STUB_VALIDATED));
}
static::internalLog('Resource load completed successfully......');
}
/**
* resourceCheck() -- private static method
*
* this method is called from the constructor function (only) and validates all that all of the necessary resources
* are available by instantiation.
*
* by default, all of the resources are initialized to false (not available) -- once a resource is instantiated,
* then we toggle the class static resource-availability variable to true.
*
* there are no input parameters or returns.
* resources are immediately (and properly) de-allocated.
*
*
* @author mike@givingassistant.org
* @version 1.0
*
*
* HISTORY:
* ========
* 06-12-17 mks original coding
* 06-29-17 mks CORE-458: mysql resource support
* 07-02-17 mks CORE-464: mongo resource support
* 09-12-17 mks CORE-561: PDO resource support to replace mysql resource support
* 05-31-18 mks CORE-1011: update for new XML broker services configuration
* 07-17-18 mks _INF-134: added console log messages to help with deployment/first-run diagnostics
* 07-24-18 mks CORE-1099: deprecating mongo slave resource use
* 07-27-18 mks CORE-1108: console logging alternative (use logging if available)
*
*/
private static function resourceCheck()
{
try {
if (intval(gasConfig::$settings[CONFIG_DATABASE][CONFIG_DATABASE_DDB][CONFIG_DATABASE_DDB_ENABLED]) == 1) {
static::internalLog(INFO_FETCHING_RESOURCE . RESOURCE_DDB);
@static::fetchResource(RESOURCE_DDB);
}
static::internalLog(INFO_FETCHING_RESOURCE . RESOURCE_CACHE);
/** @var memcached $c */
@static::fetchResource(RESOURCE_CACHE);
if (!static::$cacheAvailable) {
consoleLog(static::$myID, CON_ERROR, ERROR_RESOURCE_404 . RESOURCE_CACHE);
}
if (static::$cfgPDO[CONFIG_DATABASE_PDO_ENABLED]) {
static::internalLog(INFO_FETCHING_RESOURCE . RESOURCE_PDO_MASTER);
@static::fetchResource(RESOURCE_PDO_MASTER);
if (!static::$PDOAvailable) {
consoleLog(static::$myID, CON_ERROR, ERROR_RESOURCE_404 . RESOURCE_PDO_MASTER);
}
}
if (static::$cfgMongo[CONFIG_DATABASE_MONGODB_ENABLED]) {
static::internalLog(INFO_FETCHING_RESOURCE . RESOURCE_MONGO_MASTER);
@static::fetchResource(RESOURCE_MONGO_MASTER);
if (!static::$mongoMasterAvailable)
consoleLog(static::$myID, CON_ERROR, ERROR_RESOURCE_404 . RESOURCE_MONGO_MASTER);
}
foreach (static::$brokerServices as $brokerService) {
if (isset(gasConfig::$settings[CONFIG_BROKER_SERVICES][$brokerService]) and is_array(gasConfig::$settings[CONFIG_BROKER_SERVICES][$brokerService])) {
static::internalLog(INFO_FETCHING_RESOURCE . $brokerService);
$r = static::fetchResource($brokerService);
if (!is_object($r)) {
$msg = ERROR_RESOURCE_404 . $brokerService;
if (!is_null(static::$logger) and static::$logger->available) {
static::$logger->fatal($msg);
}
consoleLog(static::$myID, CON_ERROR, '(' . __LINE__ . ')' . $msg);
}
unset($r);
}
}
} catch (Throwable $t) {
$hdr = basename(__METHOD__) . AT . __LINE__ . COLON;
$msg = ERROR_THROWABLE_EXCEPTION . COLON . $t->getMessage();
static::internalLog($hdr . $msg);
}
}
/**
* fetchResource() -- public static method
*
* this is the method used to fetch resources objects for the various end-points (nosql, rabbit, cache, etc.)
*
* the input parameter to the method defines, by constant, which resource to fetch.
*
* Because the various resources are varying types, (resource, object, http-connection, etc.), the responsibility
* on validating the return type is on the client. If the resource could not be attained, or the request was
* improper, then we're going to return a null value to the calling client.
*
* Programmer's Notes:
* -------------------
* This method is called from the constructor so, at first glance, it may seem odd that we're explicitly returning
* static variables within the class -- however, this method is also used within the framework for ad-hoc resource
* requests which is why we're explicitly returning the static. That, and also if PHP ever does truly support
* true/pure static classes, then we'll be ready.
*
* @author mike@givingassistant.org
* @version 1.0
*
* @param string $_resourceType
* @param string $_location -- only used by gacLogger class
* @return null|object
*
* HISTORY:
* ========
* 06-12-17 mks original coding (estimate)
* 07-13-17 mks updated for service locations
* 09-12-17 mks CORE-561: PDO driver replaces mysqli
* 04-11-18 mks _INF_188: refactored for segundo broker (warehousing), typeError trapping, better logging
* 07-24-18 mks CORE-1099: deprecated mongodb slave resource use
* 08-28-18 mks DB-50: lite initialization support
* 07-29-20 mks DB-156: tercero support
*
*/
public static function fetchResource(string $_resourceType, string $_location = ENV_APPSERVER): ?object
{
try {
switch ($_resourceType) {
case RESOURCE_DDB : // we do not return a resource - just a connection to the remote http service
static::getDdbResource();
return static::$resDdb;
break;
// RabbitMQ Broker Resources (not database)
case RESOURCE_ADMIN :
case RESOURCE_BROKER :
case RESOURCE_SEGUNDO :
case RESOURCE_TERCERO :
try {
return (static::getBrokerResource($_resourceType));
} catch (TypeError $t) {
$msg = sprintf(INFO_LOC, basename(__FILE__), __LINE__) . ERROR_TYPE_EXCEPTION;
static::internalLog($msg);
static::internalLog($t->getMessage());
}
break;
case RESOURCE_PDO_MASTER :
// check, for lite initialization, if the config has been loaded
if (empty(static::$cfgPDO) and !static::$resourcesChecked) {
static::$cfgPDO = gasConfig::$settings[CONFIG_DATABASE][CONFIG_DATABASE_PDO];
if (empty(static::$cfgPDO)) {
$msg = sprintf(INFO_LOC, basename(__FILE__), __LINE__) . ERROR_CONFIG_RESOURCE_404 . CONFIG_DATABASE_PDO;
static::internalLog($msg);
return null;
}
}
// resource must be "turned on" in the XML config otherwise reject the request
if (1 != static::$cfgPDO[CONFIG_DATABASE_PDO_APPSERVER][STRING_ENABLED]) {
$msg = sprintf(INFO_LOC, basename(__FILE__), __LINE__) . ERROR_RESOURCE_404 . RESOURCE_PDO_MASTER;
static::internalLog($msg);
return null;
}
if (is_object(static::$resPDO) and static::$PDOAvailable) return static::$resPDO;
try {
static::$resPDO = static::getPDOResourceMaster(ENV_APPSERVER);
static::$PDOAvailable = (is_null(static::$resPDO)) ? false : true;
return static::$resPDO;
} catch (TypeError $t) {
$msg = sprintf(INFO_LOC, basename(__FILE__), __LINE__) . ERROR_TYPE_EXCEPTION;
static::internalLog($msg);
static::internalLog($t->getMessage());
}
break;
case RESOURCE_WH_COOL_PDO_MASTER :
// resource must be "turned on" in the XML config otherwise reject the request
if (1 != static::$cfgPDO[CONFIG_COOL_STORAGE][STRING_ENABLED]) {
$msg = sprintf(INFO_LOC, basename(__FILE__), __LINE__) . ERROR_RESOURCE_404 . RESOURCE_WH_COOL_PDO_MASTER;
static::internalLog($msg);
return null;
}
try {
static::$resPDOWHMaster = static::getPDOResourceMaster(ENV_SEGUNDO);
static::$PDOWHMasterAvailable = (is_null(static::$resPDOWHMaster)) ? false : true;
return static::$resPDOWHMaster;
} catch (TypeError $t) {
$msg = sprintf(INFO_LOC, basename(__FILE__), __LINE__) . ERROR_TYPE_EXCEPTION;
static::internalLog($msg);
static::internalLog($t->getMessage());
}
break;
case RESOURCE_PDO_SECONDARY :
if (empty(static::$cfgPDO) and !static::$resourcesChecked) {
static::$cfgPDO = gasConfig::$settings[CONFIG_DATABASE][CONFIG_DATABASE_PDO];
if (empty(static::$cfgPDO)) {
$msg = sprintf(INFO_LOC, basename(__FILE__), __LINE__) . ERROR_CONFIG_RESOURCE_404 . CONFIG_DATABASE_PDO;
static::internalLog($msg);
return null;
}
}
// resource must be "turned on" in the XML config otherwise reject the request
if (1 != static::$cfgPDO[CONFIG_DATABASE_PDO_APPSERVER][STRING_ENABLED]) {
$msg = sprintf(INFO_LOC, basename(__FILE__), __LINE__) . ERROR_RESOURCE_404 . RESOURCE_PDO_SECONDARY;
static::internalLog($msg);
return null;
} elseif (isset(static::$cfgPDO[CONFIG_DATABASE_PDO_APPSERVER][CONFIG_DATABASE_PDO_USE_SECONDARY]) and 1 != static::$cfgPDO[CONFIG_DATABASE_PDO_APPSERVER][CONFIG_DATABASE_PDO_USE_SECONDARY]) {
$msg = sprintf(INFO_LOC, basename(__FILE__), __LINE__) . ERROR_PDO_SLAVE;
static::internalLog($msg);
return null;
}
if (is_object(static::$resPDOSlave) and static::$PDOSlaveAvailable) return static::$resPDOSlave;
try {
static::$resPDOSlave = static::getPDOResourceSlave(ENV_APPSERVER);
static::$PDOSlaveAvailable = (is_null(static::$resPDOSlave)) ? false : true;
return static::$resPDOSlave;
} catch (TypeError $t) {
$msg = sprintf(INFO_LOC, basename(__FILE__), __LINE__) . ERROR_TYPE_EXCEPTION;
static::internalLog($msg);
static::internalLog($t->getMessage());
}
break;
case RESOURCE_MONGO_MASTER :
try {
static::$location = $_location;
switch (static::$location) {
case ENV_APPSERVER :
static::getMongoResource(MONGO_MASTER);
return static::$resMongoMaster;
break;
case ENV_ADMIN :
static::getMongoResource(MONGO_ADMIN_MASTER);
return static::$resMongoAdminMaster;
break;
case ENV_SEGUNDO :
static::getMongoResource(MONGO_SEGUNDO_MASTER);
return static::$resMongoSegundoMaster;
break;
case ENV_TERCERO :
static::getMongoResource(MONGO_TERCERO_MASTER);
return static::$resMongoTerceroMaster;
break;
default :
$msg = sprintf(INFO_LOC, basename(__FILE__), __LINE__) . ERROR_ENV_INVALID . static::$location;
static::internalLog($msg);
return null;
break;
}
} catch (Throwable | TypeError $t) {
$msg = sprintf(INFO_LOC, basename(__FILE__), __LINE__) . ERROR_TYPE_EXCEPTION;
static::internalLog($msg);
static::internalLog($t->getMessage());
}
break;
case RESOURCE_CACHE :
static::$resCache = gasCache::singleton();
if (is_object(static::$resCache))
static::$cacheAvailable = true;
return static::$resCache;
break;
default:
$msg = sprintf(INFO_LOC, basename(__FILE__), __LINE__) . sprintf(ERROR_RESOURCE_TYPE_UNDEF, $_resourceType);
static::internalLog($msg);
break;
}
} catch (Throwable $t) {
$msg = sprintf(INFO_LOC, basename(__FILE__), $t->getLine()) . ERROR_THROWABLE_EXCEPTION;
static::internalLog($msg);
static::internalLog(sprintf(FAIL_RESOURCE_LOAD, $_resourceType, $_location));
static::internalLog($t->getMessage());
}
return null;
}
/**
* reconnect() -- public method
*
* This method requires one input parameter - the named resource that we'll attempt to reconnect to.
*
* When we lose a resource connection, we'll call this method so that we can log the dropped-resource event.
*
* todo: make logging a system event
*
* This is a pass-through method to fetchResource() (in this class); this method is the preferred method for
* re-establishing a dropped-resource connection because of the information logging.
*
*
* @author mike@givingassistant.org
* @version 1.0
*
* @param string $_resource -- a well-defined Namaste resource
* @return null|object -- returns the PDO resource on success, null on error
*
*
* HISTORY:
* ========
* 01-16-18 mks CORE-697: original coding
*
*/
public static function reconnect(string $_resource)
{
static::internalLog(sprintf(INFO_LOC, basename(__FILE__), __LINE__) . ERROR_PDO_RECONNECT);
try {
return (static::fetchResource($_resource));
} catch (TypeError $t) {
static::internalLog(sprintf(INFO_LOC,basename(__FILE__), __LINE__, ERROR_TYPE_EXCEPTION));
static::internalLog($t->getMessage());
}
return null;
}
/**
* getDdbResource() -- private method
*
* reads the current configuration for the no-sql (Dynamo DB) resource and, if not set, will open a (HTTP)
* connection to the resource as defined in the xml configuration.
*
* Calculated values are implicitly returned to the calling client via class member population.
*
*
* @author mike@givingassistant.org
* @version 1.0
*
*
* HISTORY:
* ========
* 06-12-17 mks original coding
*
*/
private static function getDdbResource()
{
$config = gasConfig::$settings[CONFIG_DATABASE][CONFIG_DATABASE_DDB][CONFIG_DATABASE_DDB_APPSERVER];
$endpoint = $config[CONFIG_DATABASE_DDB_DSN] . ':' . $config[CONFIG_DATABASE_DDB_PORT];
$region = $config[CONFIG_DATABASE_DDB_REGION];
$version = $config[CONFIG_DATABASE_DDB_VERSION];
$credentials = [
STRING_KEY => $config[CONFIG_DATABASE_DDB_KEY_ID],
STRING_SECRET => $config[CONFIG_DATABASE_DDB_ACCESS_KEY]
];
if (is_null(static::$resDdb)) {
try {
// connect to the aws sdk
$awsSDK = new Aws\Sdk([
STRING_ENDPOINT => $endpoint,
STRING_REGION => $region,
STRING_CREDS => $credentials,
STRING_VERSION => $version
]);
// todo -- what does this return and can we trap an error?
static::$ddbAvailable = true;
static::$resDdb = $awsSDK->createDynamoDb();
} catch (Aws\Exception\AwsException | Throwable $t) {
static::internalLog(sprintf(INFO_LOC, basename(__FILE__), __LINE__) . ERROR_DDB_INSTANTIATE);
static::internalLog($t->getMessage());
}
}
}
/**
* getBrokerResource() -- private static function
*
* this method initializes a connection to the RabbitMQ service as defined in the configuration file(s) and
* establishes the broker availability and resource.
*
* there is an input parameter to the method which is the name of which broker resource we're going to initialize.
* These tags are limited, at the time this method was written, to the following:
*
* BROKER -- unimaginative name which indicates the local, primary, namaste AMQP resource
* SEGUNDO -- the second (optional) namaste resource, a remote namaste instance
* TERCERO -- the third (optional) namaste resource, also a remote instance
* ADMIN -- the namaste administrative service which could also be a remote instance
*
* first step is to evaluate which configuration was passed so we can load the appropriate configuration (XML)
* in the correct container. If an invalid config was passed, record the error and return a Boolean(false).
*
* next, load-up the basic access-data values (x5) from the current configuration...
*
* next, check to see if the current configuration requires an SSL connection to the resource. If it does,
* make a quick check to ensure that the certificate files are accessible by namaste and then attempt to
* make the SSL connection.
*
* If the config does not require SSL, make a non-SSL connection. (Note: we're using AMQPStreamConnection for
* the first time instead of the generic AMQPConnection type.) Also, because of an inherent PHP bug, only
* pass-in the heartbeat and the keep-alive options for the non-SSL connection.
*
* todo -- see if the SSL heartbeat and keepalive bugs were fixed in PHP7.0
*
* the connect request is exception wrapped and will post a log message
*
* On successful connect, we mark the resource as available, otherwise error messages are sent and the resource
* is explicitly marked as unavailable prior to returning.
*
* PROGRAMMER NOTES:
* -----------------
* In previous incarnations of this module, there were explicit connector methods for each type of broker
* resource. This incarnation deprecates the separate methods as all four broker resources are now initialized
* by this single method. Progress.
*
*
* @param string $_config
* @return bool|AMQPStreamConnection|AMQPStreamConnection
*
*
* @author mike@givingassistant.org
* @version 1.0
*
* HISTORY:
* ========
* 06-12-17 mks original coding
* 05-31-18 mks CORE-1011: update for new XML broker services configuration
*
*/
private static function getBrokerResource(string $_config)
{
$rabbitResource = false;
$options = null;
$currentConfig = null;
if (!empty(gasConfig::$settings[CONFIG_BROKER_SERVICES][$_config])) {
switch ($_config) {
case CONFIG_BROKER_APPSERVER:
static::$cfgBroker = gasConfig::$settings[CONFIG_BROKER_SERVICES][$_config];
$currentConfig = static::$cfgBroker;
break;
case CONFIG_BROKER_WH:
static::$cfgSegundo = gasConfig::$settings[CONFIG_BROKER_SERVICES][$_config];
$currentConfig = static::$cfgSegundo;
break;
case CONFIG_BROKER_TERCERO:
static::$cfgTercero = gasConfig::$settings[CONFIG_BROKER_SERVICES][$_config];
$currentConfig = static::$cfgTercero;
break;
case CONFIG_ADMIN:
static::$cfgAdmin = gasConfig::$settings[CONFIG_BROKER_SERVICES][$_config];
$currentConfig = static::$cfgAdmin;
break;
default:
$msg = sprintf(basename(__FILE__), __LINE__) . ERROR_BROKER_TYPE_UNDEF . $_config;
static::internalLog($msg);
return false;
break;
}
} else {
$rabbitResource = false;
$msg = sprintf(INFO_LOC, basename(__FILE__), __LINE__) . ERROR_CONFIG_RESOURCE_404 . $_config;
static::internalLog($msg);
return $rabbitResource;
}
if (!empty($currentConfig)) { // ensure we have a broker configuration...
$user = $currentConfig[STRING_USER]; // and load the basic MQ connection params
$pass = $currentConfig[STRING_PASS];
$host = $currentConfig[STRING_HOST];
$port = $currentConfig[STRING_PORT];
$vhost = gasConfig::$settings[CONFIG_BROKER_SERVICES][CONFIG_BROKER_VHOST];
if (gasConfig::$settings[CONFIG_BROKER_SERVICES][CONFIG_BROKER_USE_SSL] and gasConfig::$settings[CONFIG_SECURITY][CONFIG_SECURITY_REQUIRES_TLS][CONFIG_SECURITY_REQUIRES_TLS_MQ] == 1) {
try {
// if the configuration requires SSL, make a secure connection to the resource.
if (!static::checkCerts()) return false;
// build the ssl-options array and request a broker resource from the resource manager
$ssl_opts = [STRING_CA_FILE => static::$caFile, STRING_LOCAL_CERT => static::$certFile];
$rabbitResource = new AMQPSSLConnection($host, $port, $user, $pass, $vhost, $ssl_opts, $options);
} catch (Throwable $t) {
$msg = sprintf(INFO_LOC, basename(__FILE__), __LINE__) . ERROR_RESOURCE_404 . RESOURCE_BROKER;
static::internalLog($msg);
static::internalLog($t->getMessage());
return false;
}
} else {
// non-ssl broker connection
try {
$tv = gasConfig::$settings[CONFIG_BROKER_SERVICES][CONFIG_BROKER_TIMER_VIOLATION];
$ka = gasConfig::$settings[CONFIG_BROKER_SERVICES][CONFIG_BROKER_KEEP_ALIVE];
$ka = ($ka === 1) ? true : false;
$hb = gasConfig::$settings[CONFIG_BROKER_SERVICES][CONFIG_BROKER_HEARTBEAT];
$rabbitResource = new AMQPStreamConnection($host, $port, $user, $pass, $vhost, false,
STRING_AMQPLAIN, null, LOCALE, $tv, $tv, null, $ka, $hb);
} catch (Throwable $t) {
$msg = sprintf(INFO_LOC, basename(__FILE__), __LINE__) . ERROR_RESOURCE_404 . RESOURCE_BROKER;
static::internalLog($msg);
static::internalLog($t->getMessage());
return false;
}
}
}
switch ($_config) {
case CONFIG_BROKER_APPSERVER:
static::$brokerAvailable = is_object($rabbitResource);
static::$resBroker = $rabbitResource;
break;
case CONFIG_BROKER_WH:
static::$segundoAvailable = is_object($rabbitResource);
static::$resSegundo = $rabbitResource;
break;
case CONFIG_BROKER_TERCERO:
static::$terceroAvailable = is_object($rabbitResource);
static::$resTercero = $rabbitResource;
break;
case CONFIG_ADMIN:
static::$adminAvailable = is_object($rabbitResource);
static::$resAdmin = $rabbitResource;
break;
}
return $rabbitResource;
}
/**
* checkCerts() -- private static method
*
* this method was embedded in the resource-initialization method for starting a broker -- since we can now
* use this code twice (once for the localhost broker and once for the localhost vault), it was broken out
* into a private method and improved a bit.
*
* basically the method just checks to see if the key-cert and ca files are accessible. if they both are,
* then the method will return a Boolean(true). If either, or both, are not available, then a Boolean(false)
* is return and an error message is written to the console and, if available, to the global log file.
*
* There are no inputs to the method.
*
* @author mike@givingassistant.org
* @version 1.0
*
* @return bool
*
*
* HISTORY:
* ========
* 06-26-15 mks original coding
*
*/
private static function checkCerts(): bool
{
$goodCerts = false;
$pass1 = false;
// verify that we can "see" the ssl client key-cert file
if (!file_exists(static::$certFile)) {
$msg = sprintf(INFO_LOC, basename(__FILE__), __LINE__) . STRING_ERROR . ERROR_FILE_404 . static::$certFile;
static::internalLog($msg);
} else {
$pass1 = true;
}
// verify that we can "see" the certificate-authority file
if (!file_exists(static::$caFile)) {
$msg = sprintf(INFO_LOC, basename(__FILE__), __LINE__) . STRING_ERROR . ERROR_FILE_404 . static::$caFile;
static::internalLog($msg);
} elseif ($pass1) {
$goodCerts = true;
}
return $goodCerts;
}
/**
* getPDOResourceMaster() -- private static method
*
* this method checks to see if a mysql-pdo master resource exists and, if not, creates one.
*
* errors encountered are logged as fatals and the PDO-Master availability is set to false, and the
* PDO resource is set to null.
*
*
* @author mike@givingassistant.org
* @version 1.0
*
* @param string $_which -- mandatory and must be defined as ENV_PRIME, ENV_SEGUNDO or ENV_TERCERO
* @return null|PDO
*
*
* HISTORY:
* --------
* 06-29-17 mks original coding
* 08-17-17 mks CORE-561: in with PDO, out with mysqli
* 05-07-18 mks _INF-188: WH support, PHP7 header, explicit return
*
*/
private static function getPDOResourceMaster(string $_which): ?PDO
{
$PDOResource = null;
$validEnvs = [ ENV_APPSERVER, ENV_SEGUNDO, ENV_TERCERO ];
if (!in_array($_which, $validEnvs)) {
$msg = sprintf(INFO_LOC, basename(__FILE__), __LINE__) . sprintf(ERROR_RESOURCE_TYPE_UNDEF, $_which);
static::internalLog($msg);
return null;
}
// exit if we've not loaded a the PDO configuration
if (empty(static::$cfgPDO)) {
$msg = sprintf(basename(__FILE__), __LINE__) . ERROR_CONFIG_RESOURCE_404 . RESOURCE_PDO_MASTER;
static::internalLog($msg);
return null;
}
// qualify and populate based on the environment requested
// "default" settings are for ENV_PRIME (namaste)
$namastePDO = static::$cfgPDO[CONFIG_DATABASE_PDO_APPSERVER][CONFIG_DATABASE_PDO_MASTER];
$db = 'dbname=' . gasConfig::$settings[CONFIG_ID][CONFIG_ID_ENV] . UDASH . $namastePDO[CONFIG_DATABASE_PDO_DB];
switch ($_which) {
case ENV_APPSERVER :
if (static::$resourcesChecked and static::$PDOAvailable and !is_null(static::$resPDO)) {
// if services are already flagged as available, query the PDO resource to confirm
static::$PDOAvailable = (is_null(static::$resPDO->getAttribute(PDO::ATTR_CONNECTION_STATUS))) ? false : true;
if (static::$PDOAvailable) return static::$resPDO;
}
break;
case ENV_SEGUNDO :
if (static::$PDOWHMasterAvailable and !is_null(static::$resPDOWHMaster)) {
static::$PDOWHMasterAvailable = (is_null(static::$resPDOWHMaster->getAttribute(PDO::ATTR_CONNECTION_STATUS))) ? false : true;
if (static::$PDOWHMasterAvailable) return static::$resPDOWHMaster;
}
$namastePDO = static::$cfgPDO[CONFIG_COOL_STORAGE][CONFIG_DATABASE_PDO_MASTER];
$db = 'dbname=' . gasConfig::$settings[CONFIG_ID][CONFIG_ID_ENV] . UDASH . $namastePDO[CONFIG_DATABASE_PDO_DB];
break;
}
$pdoAttributes = [
PDO::ATTR_EMULATE_PREPARES => false,
PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => 1,
// PDO::ATTR_PERSISTENT => true,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
];
// if we failed to verify the connection, we've marked the availability as false, fall-thru and attempt to reconnect
// if ((!static::$PDOAvailable) and static::$cfgPDO[CONFIG_DATABASE_PDO_APPSERVER][CONFIG_DATABASE_PDO_ENABLED]) {
$host = $namastePDO[CONFIG_DATABASE_PDO_HOSTNAME];
$user = $namastePDO[CONFIG_DATABASE_PDO_USERNAME];
$pass = $namastePDO[CONFIG_DATABASE_PDO_PASSWORD];
$port = $namastePDO[CONFIG_DATABASE_PDO_PORT];
// connect to mariaDB
$PDOConnectString = 'mysql:host=' . $host . COLON_NS . $port . SEMI . $db . SEMI . $namastePDO[CONFIG_DATABASE_PDO_CHARSET];
if (static::$debug) static::$logger->debug('PDO-Connection: ' . $PDOConnectString);
try {
$PDOResource = new PDO($PDOConnectString, $user, $pass, $pdoAttributes);
} catch (PDOException | Throwable $e) {
if (isset(static::$logger) and static::$logger->available) {
static::$logger->fatal(ERROR_PDO_CONNECT);
static::$logger->fatal($e->getMessage());
}
$msg = sprintf(INFO_LOC, basename(__FILE__), __LINE__) . ERROR_PDO_CONNECT;
static::internalLog($msg);
static::internalLog($e->getMessage());
return null;
}
// }
return $PDOResource;
}
/**
* getPDOResourceSlave() -- private static method
*
* this method checks to see if a mysql-pdo slave resource exists and, if not, creates one.
*
* errors encountered are logged as fatals and the PDO-Slave availability is set to false, and the
* PDO resource is set to null.
*
* https://secure.php.net/manual/en/mysqlnd-ms.quickstart.usage.php
*
*
* @author mike@givingassistant.org
* @version 1.0
*
* @param string $_env
* @return null|PDO
*
*
* HISTORY:
* --------
* 09-15-17 mks CORE-562: original coding
* 05-07-18 mks _INF-188: WH Support, PHP7 Header, explicit resource return
*
*/
private static function getPDOResourceSlave(string $_env = ENV_APPSERVER): ? PDO
{
$PDOResource = null;
$validEnvs = [ ENV_APPSERVER, ENV_SEGUNDO, ENV_TERCERO ];
if (!in_array($_env, $validEnvs)) {
$msg = sprintf(INFO_LOC, basename(__FILE__), __LINE__) . sprintf(ERROR_RESOURCE_TYPE_UNDEF, $_env);
static::internalLog($msg);
return null;
}
// exit if we've not loaded a the PDO configuration
if (empty(static::$cfgPDO)) {
$msg = sprintf(basename(__FILE__), __LINE__) . ERROR_CONFIG_RESOURCE_404 . RESOURCE_PDO_MASTER;
static::internalLog($msg);
static::$PDOSlaveAvailable = false;
static::$resPDOSlave = null;
return null;
}
// qualify and populate based on the environment requested
// "default" settings are for ENV_PRIME (namaste)
$slaveCFG = static::$cfgPDO[CONFIG_DATABASE_PDO_APPSERVER][CONFIG_DATABASE_PDO_SECONDARY];
$dbName = static::$cfgPDO[CONFIG_DATABASE_PDO_APPSERVER][CONFIG_DATABASE_PDO_MASTER][CONFIG_DATABASE_PDO_DB];
$db = 'dbname=' . gasConfig::$settings[CONFIG_ID][CONFIG_ID_ENV] . UDASH . $dbName;
switch ($_env) {
case ENV_APPSERVER :
if (static::$PDOSlaveAvailable and !is_null(static::$resPDOSlave)) {
// if services are already flagged as available, query the PDO resource to confirm
static::$PDOSlaveAvailable = (is_null(static::$resPDOSlave->getAttribute(PDO::ATTR_CONNECTION_STATUS))) ? false : true;
if (static::$PDOSlaveAvailable) return static::$resPDOSlave;
}
break;
case ENV_SEGUNDO :
if (static::$PDOWHSlaveAvailable and !is_null(static::$resPDOWHSlave)) {
static::$PDOWHSlaveAvailable = (is_null(static::$resPDOWHSlave->getAttribute(PDO::ATTR_CONNECTION_STATUS))) ? false : true;
if (static::$PDOWHSlaveAvailable) return static::$resPDOWHSlave;
}
$slaveCFG = static::$cfgPDO[CONFIG_COOL_STORAGE][CONFIG_DATABASE_PDO_SECONDARY];
$dbName = static::$cfgPDO[CONFIG_COOL_STORAGE][CONFIG_DATABASE_PDO_MASTER][CONFIG_DATABASE_PDO_DB];
$db = 'dbname=' . gasConfig::$settings[CONFIG_ID][CONFIG_ID_ENV] . UDASH . $dbName;
break;
}
$pdoAttributes = [
PDO::ATTR_EMULATE_PREPARES => false,
// PDO::ATTR_PERSISTENT => true,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::FETCH_ASSOC => PDO::FETCH_ASSOC
];
// check to see if the PDO read-slave service is enabled and, if so, connect.
$host = $slaveCFG[CONFIG_DATABASE_PDO_HOSTNAME];
$user = $slaveCFG[CONFIG_DATABASE_PDO_USERNAME];
$pass = $slaveCFG[CONFIG_DATABASE_PDO_PASSWORD];
$port = $slaveCFG[CONFIG_DATABASE_PDO_PORT];
$PDOConnectString = 'mysql:host=' . $host . COLON_NS . $port . SEMI . $db . SEMI . $slaveCFG[CONFIG_DATABASE_PDO_CHARSET];
if (static::$debug) static::$logger->debug('PDO-Connection: ' . $PDOConnectString);
try {
$PDOResource = new PDO($PDOConnectString, $user, $pass, $pdoAttributes);
} catch (PDOException | Throwable $e) {
if (isset(static::$logger) and static::$logger->available) {
static::$logger->fatal(ERROR_PDO_CONNECT);
static::$logger->fatal($e->getMessage());
} else {
static::internalLog(sprintf(basename(__FILE__), __LINE__) . ERROR_PDO_CONNECT);
static::internalLog($e->getMessage());
}
return null;
}
return $PDOResource;
}
/**
* getMongoResource() -- private method
*
* the method has an optional parameter which establishes the target resource as being either the mongo master
* (default) the mongo slave, the mongo WH master, or the mongo WH slave.
*
* A second, also optional, parameter is only used for warehousing resources -- specifies the level of
* warehousing (COOL, COLD, WARM, etc.) and this is used to ensure we're pulling the correct XML section
* from the configuration.
*
* this method checks to see if a mongo-configuration exists, and if so, has the mongo service been enabled. If
* the former is missing, or the latter is not enabled, then post an error and return;
*
* continue by scraping the XML configuration to build the connectivity string that we'll pass to the MongoDB
* Manager driver. Include the replSet configuration if enabled.
*
* We attempt to connect to the mongo service and store the connection resource as a class property object.
* The connection attempt is exception wrapped so any failures will be recorded.
*
* A successful connection will set the class property $resMongoMaster to the mongo resource whereas any error
* will result in this value being set to null.
*
* A successful connection will set the class property $mongoMasterAvailable to Boolean(true) whereas any error
* will result in this value being set to Boolean(false).
*
* Programmer's Notes:
* -------------------
* as of now, there's no support coded for SSL connectivity
* user passwords are located in the database specified in the XML file which may not be the current db
*
* the only difference between a master and a slave (read-only) connection for mongo is the readPreference
* setting in the $options array.
*
*
* @author mike@givingassistant.org
* @version 1.0
*
* @param string $_which -- indicates resource allocation for either master or slave defaulting to master
* @param string $_whLevel -- should be either COOL, COLD, or WARM or HOT
*
*
* HISTORY:
* ========
* 07-05-17 mks CORE-464: original coding
* 07-13-17 mks support for multiple locations (namaste, admin, etc.)
* updated exception handling for PHP 7
* 10-02-17 mks CORE-572: updated mongo read-preferences for 3.2.17, exception logging
* 11-27-17 mks CORE-635: sharded-cluster, repl-set, stand-alone instance connection options
* 05-05-18 mks _INF-188: support for mongo WH resources, converted error messaging to console log method
* 07-03-18 mks CORE-1053: testing a successful mongoDB connection
* 08-24-18 mks CORE-1097: refactoring mongo resources (RBAC, RP)
* 08-24-18 mks CORE-1099: deprecation mongodb slave resource handling
* 08-28-18 mks DB-50: lite configuration (no bootstrap) support
* 07-29-20 mks DB-156: tercero support
* 12-10-20 mks DB-180: support for segundo (non warehousing broker)
*
*/
private static function getMongoResource(string $_which = MONGO_MASTER, string $_whLevel = ''): void
{
// todo - system table for these variables
$validLocations = [ ENV_APPSERVER, ENV_ADMIN, ENV_SEGUNDO, ENV_TERCERO ];
$validWHLevels = [ WH_TYPE_COOL, WH_TYPE_HOT, WH_TYPE_WARM, WH_TYPE_COLD ];
$validWhich = [ MONGO_MASTER, MONGO_WH_MASTER, MONGO_SEGUNDO_MASTER, MONGO_ADMIN_MASTER, MONGO_TERCERO_MASTER ];
$errors = [];
// validate the which param
if (!in_array($_which, $validWhich)) {
$msg = sprintf(INFO_LOC, basename(__FILE__), __LINE__) . sprintf(ERROR_MONGO_RESOURCE_INVALID, $_which);
static::internalLog($msg);
return;
}
// is set, validate the wh-level
if (!empty($_whLevel) and !in_array($_whLevel, $validWHLevels)) {
$msg = sprintf(INFO_LOC, basename(__FILE__), __LINE__) . sprintf(ERROR_MONGO_WH_LEVEL_INVALID, $_whLevel);
static::internalLog($msg);
return;
}
// validate which mongo service location
if (!in_array(static::$location, $validLocations)) {
$msg = sprintf(INFO_LOC, basename(__FILE__), __LINE__) . sprintf(ERROR_MONGO_LOCATION_INVALID, static::$location);
static::internalLog($msg);
return;
}
// cannot continue without a mongo configuration -- if not set (lite initialization) then attempt to load
if (empty(static::$cfgMongo)) {
$msg = '';
// attempt to reload the config
if (!empty(gasConfig::$settings[CONFIG_DATABASE])) {
static::$cfgMongo = gasConfig::$settings[CONFIG_DATABASE][CONFIG_DATABASE_MONGODB];
if (empty(static::$cfgMongo))
$msg = sprintf(INFO_LOC, basename(__FILE__), __LINE__) . ERROR_CONFIG_RESOURCE_404 . RESOURCE_MONGO_MASTER;
} else {
$msg = sprintf(INFO_LOC, basename(__FILE__), __LINE__) . ERROR_CONFIG_RESOURCE_404 . RESOURCE_MONGO_MASTER;
}
if (strlen($msg)) {
static::internalLog($msg);
return;
}
}
// validate that the service config exists
if (empty(static::$cfgMongo[static::$location])) {
$msg = sprintf(INFO_LOC, basename(__FILE__), __LINE__) . sprintf(ERROR_MONGO_LOCATION_DNE, static::$location);
static::internalLog($msg);
return;
}
// validate that the requested env been enabled in the configuration
if (static::$location == ENV_SEGUNDO and $_which == MONGO_WH_MASTER) {
if (empty($_whLevel)) {
$msg = sprintf(INFO_LOC, basename(__FILE__), __LINE__) . ERROR_MONGO_WH_LEVEL_404;
static::internalLog($msg);
return;
}
switch ($_whLevel) {
case WH_TYPE_COOL :
static::$location = CONFIG_COOL_STORAGE;
break;
case WH_TYPE_COLD :
static::$location = CONFIG_COLD_STORAGE;
break;
case WH_TYPE_WARM :
static::$location = CONFIG_WARM_STORAGE;
break;
case WH_TYPE_HOT :
static::$location = CONFIG_HOT_STORAGE;
break;
}
}
if (intval(static::$cfgMongo[static::$location][CONFIG_DATABASE_MONGODB_ENABLED]) !== 1) {
$msg = sprintf(INFO_LOC, basename(__FILE__), __LINE__) . sprintf(ERROR_MONGO_NOT_ENABLED, static::$location);
static::internalLog($msg);
return;
}
// validate which mongo node to connect to and reset those resources
switch ($_which) {
case MONGO_MASTER :
static::$resMongoMaster = null;
static::$mongoMasterAvailable = false;
$service = ENV_APPSERVER;
break;
case MONGO_SEGUNDO_MASTER :
static::$resMongoSegundoMaster = null;
static::$mongoSegundoMasterAvailable = false;
$service = ENV_SEGUNDO;
break;
case MONGO_WH_MASTER :
static::$resMongoWHMaster = null;
static::$mongoWHMasterAvailable = false;
$service = ENV_SEGUNDO;
break;
case MONGO_ADMIN_MASTER :
if (intval(gasConfig::$settings[ENV_ADMIN][CONFIG_ACTIVE]) !== 1) {
if (isset(static::$logger) and static::$logger->available) {
$msg = sprintf(INFO_LOC, basename(__METHOD__), __LINE__) . ERROR_ADMIN_NOT_ENABLED;
static::internalLog($msg);
}
return;
}
if (static::$location != ENV_ADMIN) static::$location = ENV_ADMIN;
static::$resMongoAdminMaster = null;
static::$mongoAdminMasterAvailable = false;
$service = ENV_ADMIN;
break;
case MONGO_TERCERO_MASTER :
if (intval(gasConfig::$settings[CONFIG_BROKER_TERCERO][CONFIG_ACTIVE]) !== 1) {
if (isset(static::$logger) and static::$logger->available) {
$msg = sprintf(INFO_LOC, basename(__METHOD__), __LINE__) . ERROR_TERCERO_NOT_ENABLED;
static::internalLog($msg);
}
return;
}
if (static::$location != ENV_TERCERO) static::$location = ENV_TERCERO;
static::$resMongoTerceroMaster = null;
static::$mongoTerceroMasterAvailable = false;
$service = ENV_TERCERO;
break;
default :
$msg = sprintf(INFO_LOC, basename(__FILE__), __LINE__) . ERROR_SERVICE_404 . COLON . $_which;
static::internalLog($msg);
return;
break;
}
// todo: SSL connectivity option
// build the connect string to the mongo resource based on the requesting environment
try {
$mongoData = static::buildMongoDSN($service);
if (is_null($mongoData) or !is_array($mongoData)) {
$msg = sprintf(INFO_LOC, basename(__FILE__), __LINE__) . sprintf(ERROR_DATA_METHOD_404, 'buildMongoDSN');
static::internalLog($msg);
return;
}
$mongoDSN = $mongoData[STRING_DSN];
$authSource = (empty($mongoData[STRING_AUTH_SRC])) ? CONFIG_ADMIN : $mongoData[STRING_AUTH_SRC];
$mongoOptions = $mongoData[STRING_OPTIONS];
} catch (TypeError $t) {
$msg = sprintf(INFO_LOC, basename(__FILE__), __LINE__) . ERROR_TYPE_EXCEPTION;
static::internalLog($msg);
static::internalLog($t->getMessage());
return;
}
try {
/*
* connect to the named mongo resource - note that mongo manager will always return a resource - even if
* there is no database to connect to - so, once we connect, issue a simple ping query to the named db to
* validate the connection.
*/
$res = new MongoDB\Driver\Manager($mongoDSN, $mongoOptions);
if (!is_object($res)) {
consoleLog($res, CON_SYSTEM, ERROR_MONGO_CONNECT . COLON . $_which);
return;
}
$command = new MongoDB\Driver\Command(['ping' => 1]);
@$res->executeCommand($authSource, $command); // was "admin" for $authsource; ok to ping the target db
// @$res->executeCommand('admin', $command);
switch ($_which) {
case MONGO_MASTER :
static::$resMongoMaster = $res;
static::$mongoMasterAvailable = true;
break;
case MONGO_ADMIN_MASTER :
static::$resMongoAdminMaster = $res;
static::$mongoAdminMasterAvailable = true;
break;
case MONGO_SEGUNDO_MASTER :
static::$resMongoSegundoMaster = $res;
static::$mongoSegundoMasterAvailable = true;
break;
case MONGO_WH_MASTER :
static::$resMongoWHMaster = $res;
static::$mongoWHMasterAvailable = true;
break;
case MONGO_TERCERO_MASTER :
static::$resMongoTerceroMaster = $res;
static::$mongoTerceroMasterAvailable = true;
break;
}
} catch (MongoDB\Driver\Exception\ConnectionException |
MongoDB\Driver\Exception\InvalidArgumentException |
MongoDB\Driver\Exception\RuntimeException |
MongoDB\Driver\Exception\Exception |
Throwable $e) { // superclass for sslConnection and TimeoutException
$hdr = sprintf(INFO_LOC, basename(__METHOD__), __LINE__);
@handleExceptionMessaging($hdr, $e->getMessage(), $errors, true);
}
}
/**
* buildMongoDSN() -- private static method
*
* This method requires one input parameter: the name of the current environment requesting a resource. This
* is a well-defined (e.g.: constant) value and pre-validated by the invoking client.
*
* The method builds an associative array which is returned to the calling client and is divided into three
* elements:
*
* 1. The mongo DSN (connect string) under the key: STRING_DSN
* 2. The mongo Authentication Source DB under the key: STRING_AUTH_SRC
* 3. The mongo options array (which is, itself, another associative array)
*
* Depending on which environment we're attempting to connect to, (defined in $_which), we'll pull that XML config
* block out from the mongo-config super-block and use that configuration data to build the mongo data necessary
* to connect to the named mongo resource.
*
* Any errors raised in processing will cause an error message to be generated and a null value returned to the
* calling client.
*
*
* @author mike@givingassistant.org
* @version 1.0
*
* @param string $_which
* @return array|null
*
*
* HISTORY:
* ========
* 07-23-18 mks CORE-1097: original coding
*
*/
private static function buildMongoDSN(string $_which): ?array
{
$validReadPrefs = [
'primary',
'primaryPreferred',
'secondary',
'secondaryPreferred',
'nearest'
];
$skipRPConfig = false;
$mongoDSN = MONGO_DSN;
// return array
$ra = [
STRING_DSN => null,
STRING_AUTH_SRC => null,
STRING_OPTIONS => null
];
// default read-preference
$mongoOptions[CONFIG_DATABASE_MONGODB_RP] = MONGODB\Driver\ReadPreference::RP_PRIMARY;
// note: user passwords, by default, are located in the target namaste db and not in the admin db
// build the DSN based on the correct env (appServer, segundo, tercero or admin) configuration sub-block
$thisConfig = static::$cfgMongo[$_which];
// step 1 -- ensure that the service ($_which) is local and active...
// if (intval(gasConfig::$settings[$_which][CONFIG_IS_LOCAL]) !== 1) {
// static::internalLog(sprintf(ERROR_SERVICE_NOT_LOCAL, $_which));
// return null;
// } elseif (intval(gasConfig::$settings[$_which][CONFIG_ACTIVE]) !== 1) {
// $msg = sprintf(ERROR_SERVICE_NOT_ACTIVE, $_which);
// static::internalLog($msg);
// return null;
// }
if (intval(gasConfig::$settings[$_which][CONFIG_ACTIVE]) !== 1) {
$msg = sprintf(ERROR_SERVICE_NOT_ACTIVE, $_which);
static::internalLog($msg);
return null;
}
// the above commented-out block replaces the above code
// step 1a -- ensure that mongo is enabled on the current service
if (intval($thisConfig[CONFIG_DATABASE_MONGODB_ENABLED]) !== 1) {
// and that mongo is enabled for the service
$msg = sprintf(INFO_LOC, basename(__FILE__), __LINE__) . ERROR_LOCAL_SERVICE_404;
static::internalLog($msg);
return null;
}
// Step 2 -- if RBAC enabled, grab the user credentials and inject into mongo-DSN
if (intval($thisConfig[CONFIG_DATABASE_MONGODB_USE_AUTH]) === 1) {
$mongoDSN .= $thisConfig[CONFIG_DATABASE_MONGODB_USER]. COLON_NS;
$mongoDSN .= $thisConfig[CONFIG_DATABASE_MONGODB_PASSWORD] . AT;
$authSource = gasConfig::$settings[CONFIG_ID][CONFIG_ID_ENV] . UDASH . $thisConfig[CONFIG_DATABASE_MONGODB_AUTH_SOURCE];
$mongoOptions[CONFIG_DATABASE_MONGODB_AUTH_SOURCE] = $authSource;
$ra[STRING_AUTH_SRC] = $authSource;
}
// Step 3 -- evaluate the level of service in order of: sharded-cluster, replication-set, stand-alone-instance
if (intval($thisConfig[CONFIG_DATABASE_MONGODB_SHARDING][CONFIG_DATABASE_MONGODB_ENABLED]) === 1) {
// sharded cluster configuration
foreach($thisConfig[CONFIG_DATABASE_MONGODB_SHARDING][CONFIG_DATABASE_MONGODB_SHARDING_MONGOS_NODES] as $node) {
$mongoDSN .= $node . COMMA_NS;
}
$mongoDSN = rtrim($mongoDSN, COMMA_NS);
} elseif (intval($thisConfig[CONFIG_DATABASE_MONGODB_REPLSET][CONFIG_DATABASE_MONGODB_REPLSET_ENABLED]) === 1) {
// replication set configuration
$mongoOptions[MONGO_REPL_SET] = $thisConfig[CONFIG_DATABASE_MONGODB_REPLSET][CONFIG_DATABASE_MONGODB_REPLSET_NAME];
foreach ($thisConfig[CONFIG_DATABASE_MONGODB_REPLSET][CONFIG_DATABASE_MONGODB_REPLSET_DSN][CONFIG_DATABASE_MONGODB_ADMIN_REPLSET_SET] as $node) {
$mongoDSN .= $node . COMMA_NS;
}
$mongoDSN = rtrim($mongoDSN, COMMA_NS);
} else {
// stand-alone instance
$mongoDSN .= $thisConfig[CONFIG_DATABASE_MONGODB_HOST] . COLON_NS . $thisConfig[CONFIG_DATABASE_MONGODB_PORT];
$skipRPConfig = true;
}
if (isset(static::$logger) and static::$logger->available and gasConfig::$settings[CONFIG_DEBUG]) {
$msg = sprintf(INFO_LOC, basename(__FILE__), __LINE__) . 'MongoDSN (' . static::$location . '): ' . $mongoDSN;
static::internalLog($msg);
}
$ra[STRING_DSN] = $mongoDSN;
// Step 4: add options: heartbeat and read-preferences to the options array
// heartbeat:
$mongoOptions[CONFIG_DATABASE_MONGODB_HB] = intval($thisConfig[CONFIG_DATABASE_MONGODB_HB]);
// readPreference: if the current env supports the read-secondary, then set the read-preference to the value
// stored as secondaryReadPreference, otherwise use readPreference (primary)
$mongoOptions[CONFIG_DATABASE_MONGODB_RP] = (isset($thisConfig[CONFIG_DATABASE_PDO_USE_SECONDARY]) === 1)
? $thisConfig[CONFIG_DATABASE_MONGODB_USE_READ_SECONDARY]
: $thisConfig[CONFIG_DATABASE_MONGODB_RP];
// if not a single-node-instance of mongo, get the readPreference from XML config and override the default
if (!$skipRPConfig) {
// $slaveReadPreference = $thisConfig[CONFIG_DATABASE_MONGODB_SECONDARY_RP];
if (!in_array($thisConfig[CONFIG_DATABASE_MONGODB_RP], $validReadPrefs)) {
$msg = sprintf(INFO_LOC, basename(__FILE__), __LINE__) . sprintf(ERROR_MDB_INVALID_RP, $thisConfig[CONFIG_DATABASE_MONGODB_RP]);
static::internalLog($msg);
return null;
}
}
$ra[STRING_OPTIONS] = $mongoOptions;
return $ra;
}
/**
* internalLog() -- static private method
*
* This method requires a single input parameter - a string value which is the message to be logged.
*
* The method evaluates if the framework's logger resource is currently available and, if it is, publishes the
* message to the log file -- otherwise, the message is published to the namaste console.
*
* The method returns void.
*
*
* @author mike@givingassistant.org
* @version 1.0
*
* @param string $_msg
*
*
* HISTORY:
* ========
* 07-27-18 mks CORE-1108: initial coding
* 08-22-18 mks DB-49: consoleLog is default log vector -- add to mongodb log iff available
* 08-28-18 mks DB-49: squelching consoleLog output if client is a webApp, expanded exception trap
* 10-11-20 mks DB-168: ensuring logging is not engaged until after broker clients have started
*
*/
private static function internalLog(string $_msg): void
{
try {
if (gasResourceManager::$adminAvailable and isset(static::$logger) and isset(static::$logger->available) and static::$logger->available === true) {
static::$logger->info($_msg);
}
if (!(isset($_SERVER['HTTP_USER_AGENT']))) consoleLog(static::$myID, CON_SYSTEM, $_msg);
} catch (TypeError | Throwable $t) {
consoleLog(static::$myID, CON_ERROR, basename(__METHOD__) . AT . __LINE__ . COLON . $t->getMessage());
}
}
/**
* singleton() -- public static class
*
* method to instantiate the resourceManager singleton class
*
* note:
* -----
* it's the calling client's responsibility to check for the null return value in the member variable $instance.
*
* @author mike@givingassistant.org
* @version 1.0
*
* @return null
*
* HISTORY:
* ========
* 06-12-17 mks original coding
*
*/
public static function singleton()
{
if (static::$instance === null) {
$c = __CLASS__;
static::$instance = new $c();
}
return(static::$instance);
}
/**
* __destruct() -- public method
*
* explicitly close any open resources - note that this is the only place in the framework where a resource
* connection is explicitly closed.
*
* @author mike@givingassistant.org
* @version 1.0
*
*
* HISTORY:
* ========
* 06-12-17 mks original coding
*
*/
public function __destruct()
{
// As of PHP 5.3.10 destructors are not run on shutdown caused by fatal errors.
//
// destructor is registered shut-down function in constructor -- so any recovery
// efforts should go in this method.
}
/**
* __clone() -- public function
*
* Silently disallows cloning of the object
*
* @author mike@givingassistant.org
* @version 1.0
*
* @return null
*
* HISTORY:
* ========
* 06-12-17 mks original coding
*
*/
private function __clone()
{
return null;
}
}