Awight has uploaded a new change for review.
https://gerrit.wikimedia.org/r/233877
Change subject: Clean up orphan classes
......................................................................
Clean up orphan classes
Move classes into own files and out of maintenance scripts dir.
Change-Id: I67751d4c325cc9e52a9a0e23770451fcbb110a7e
---
M DonationInterface.php
A globalcollect_gateway/GlobalCollectOrphanRectifier.php
R globalcollect_gateway/orphan.adapter.php
M globalcollect_gateway/scripts/orphans.php
4 files changed, 243 insertions(+), 237 deletions(-)
git pull
ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/DonationInterface
refs/changes/77/233877/1
diff --git a/DonationInterface.php b/DonationInterface.php
index 388c2d5..920b2f0 100644
--- a/DonationInterface.php
+++ b/DonationInterface.php
@@ -74,8 +74,8 @@
$wgAutoloadClasses['GlobalCollectGatewayResult'] = __DIR__ .
'/globalcollect_gateway/globalcollect_resultswitcher.body.php';
$wgAutoloadClasses['GlobalCollectAdapter'] = __DIR__ .
'/globalcollect_gateway/globalcollect.adapter.php';
-$wgAutoloadClasses['GlobalCollectOrphanAdapter'] = __DIR__ .
'/globalcollect_gateway/scripts/orphan.adapter.php';
-$wgAutoloadClasses['GlobalCollectOrphanRectifier'] = __DIR__ .
'/globalcollect_gateway/scripts/orphans.php';
+$wgAutoloadClasses['GlobalCollectOrphanAdapter'] = __DIR__ .
'/globalcollect_gateway/orphan.adapter.php';
+$wgAutoloadClasses['GlobalCollectOrphanRectifier'] = __DIR__ .
'/globalcollect_gateway/GlobalCollectOrphanRectifier.php';
// Amazon
$wgAutoloadClasses['AmazonGateway'] = __DIR__ .
'/amazon_gateway/amazon_gateway.body.php';
diff --git a/globalcollect_gateway/GlobalCollectOrphanRectifier.php
b/globalcollect_gateway/GlobalCollectOrphanRectifier.php
new file mode 100644
index 0000000..e597ed1
--- /dev/null
+++ b/globalcollect_gateway/GlobalCollectOrphanRectifier.php
@@ -0,0 +1,226 @@
+<?php
+/**
+ * TODO: Generalize to all gateways, using hooks to implement polymorphism.
+ */
+
+use Predis\Connection\ConnectionException;
+
+class GlobalCollectOrphanRectifier {
+
+ protected $killfiles = array();
+ protected $order_ids = array();
+ protected $target_execute_time;
+ protected $max_per_execute; //only really used if you're going by-file.
+ protected $adapter;
+
+ public function __construct() {
+ // Have to turn this off here, until we know it's using the
user's ip, and
+ // not 127.0.0.1 during the batch process. Otherwise, we'll
immediately
+ // lock ourselves out when processing multiple charges.
+ global $wgDonationInterfaceEnableIPVelocityFilter;
+ $wgDonationInterfaceEnableIPVelocityFilter = false;
+
+ if ( !$this->getOrphanGlobal( 'enable' ) ){
+ $this->info( 'Orphan cron disabled. Have a nice day!' );
+ return;
+ }
+
+ $this->target_execute_time = $this->getOrphanGlobal(
'target_execute_time' );
+ $this->max_per_execute = $this->getOrphanGlobal(
'max_per_execute' );
+
+ // FIXME: Is this just to trigger batch mode?
+ $data = array(
+ 'wheeee' => 'yes'
+ );
+ $this->adapter = new
GlobalCollectOrphanAdapter(array('external_data' => $data));
+ $this->logger = DonationLoggerFactory::getLogger(
$this->adapter );
+ }
+
+ protected function keepGoing(){
+ $elapsed = $this->getProcessElapsed();
+ if ( $elapsed < $this->target_execute_time ) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * This will both return the elapsed process time, and echo something
for
+ * the cronspammer.
+ * @return int elapsed time since start in seconds
+ */
+ protected function getProcessElapsed(){
+ $elapsed = time() - $this->start_time;
+ $this->info( "Elapsed Time: {$elapsed}" );
+ return $elapsed;
+ }
+
+ protected function deleteMessage( $correlation_id, $queue ) {
+ DonationQueue::instance()->delete( $correlation_id, $queue );
+ }
+
+ protected function processOrphans() {
+ // TODO: Make this configurable.
+ // 20 minutes: this is exactly equal to something on
Globalcollect's side.
+ $time_buffer = 60*20;
+
+ $queue_pool = new CyclicalArray( $this->getOrphanGlobal(
'gc_cc_limbo_queue_pool' ) );
+ if ( $queue_pool->isEmpty() ) {
+ // FIXME: cheesy inline default
+ $queue_pool = new CyclicalArray(
GlobalCollectAdapter::GC_CC_LIMBO_QUEUE );
+ }
+
+ $this->info( "Slaying orphans..." );
+ $this->start_time = time();
+
+ //I want to be clear on the problem I hope to prevent with
this. Say,
+ //for instance, we pull a legit orphan, and for whatever
reason, can't
+ //completely rectify it. Then, we go back and pull more... and
that
+ //same one is in the list again. We should stop after one try
per
+ //message per execute. We should also be smart enough to not
process
+ //things we believe we just deleted.
+ $this->handled_ids = array();
+
+ while ( $this->keepGoing() && !$queue_pool->isEmpty() ) {
+ $current_queue = $queue_pool->current();
+ try {
+ $message = DonationQueue::instance()->peek(
$current_queue );
+
+ if ( !$message ) {
+ $this->info( "Emptied queue
[{$current_queue}], removing from pool." );
+ $queue_pool->dropCurrent();
+ continue;
+ }
+
+ $correlation_id = 'globalcollect-' .
$message['gateway_txn_id'];
+ if ( array_key_exists( $correlation_id,
$this->handled_ids ) ) {
+ // We already did this one, keep going.
It's fine to draw
+ // again from the same queue.
+ DonationQueue::instance()->delete(
$correlation_id, $current_queue );
+ continue;
+ }
+
+ // Check the timestamp to see if it's old
enough, and stop when
+ // we're below the threshold. Messages are
guaranteed to pop in
+ // chronological order.
+ $elapsed = $this->start_time - $message['date'];
+ if ( $elapsed < $time_buffer ) {
+ $this->info( "Exhausted new messages in
[{$current_queue}], removing from pool..." );
+ $queue_pool->dropCurrent();
+
+ continue;
+ }
+
+ // We got ourselves an orphan!
+ if ( $this->rectifyOrphan( $message ) ) {
+ $this->handled_ids[$correlation_id] =
'rectified';
+ } else {
+ $this->handled_ids[$correlation_id] =
'error';
+ }
+
+ // Throw out the message either way.
+ $this->deleteMessage( $correlation_id,
$current_queue );
+
+ // Round-robin the pool before we complete the
loop.
+ $queue_pool->rotate();
+ } catch ( ConnectionException $ex ) {
+ // Drop this server, for the duration of this
batch.
+ $this->error( "Queue server for
[$current_queue] is down! Ignoring for this run..." );
+ $queue_pool->dropCurrent();
+ }
+ }
+
+ //TODO: Make stats squirt out all over the place.
+ $rec = 0;
+ $err = 0;
+ foreach( $this->handled_ids as $id=>$whathappened ){
+ switch ( $whathappened ){
+ case 'rectified':
+ $rec += 1;
+ break;
+ case 'error':
+ $err += 1;
+ break;
+ }
+ }
+ $final = "\nDone! Final results: \n";
+ $final .= " $rec rectified orphans \n";
+ $final .= " $err errored out \n";
+ if ( isset( $this->adapter->orphanstats ) ){
+ foreach ( $this->adapter->orphanstats as $status =>
$count ) {
+ $final .= "\n Status $status = $count";
+ }
+ }
+ $final .= "\n Approximately " . $this->getProcessElapsed() . "
seconds to execute.\n";
+ $this->info( $final );
+ }
+
+ /**
+ * Uses the Orphan Adapter to rectify (complete the charge for) a
single orphan. Returns a boolean letting the caller know if
+ * the orphan has been fully rectified or not.
+ * @param array $data Some set of orphan data.
+ * @param boolean $query_contribution_tracking A flag specifying if we
should query the contribution_tracking table or not.
+ * @return boolean True if the orphan has been rectified, false if not.
+ */
+ protected function rectifyOrphan( $data, $query_contribution_tracking =
true ){
+ $data['order_id'] = $data['gateway_txn_id'];
+
+ $this->info( "Rectifying orphan: {$data['order_id']}" );
+ $rectified = false;
+
+ $normalized = DonationQueue::queueMessageToNormalized( $data );
+ $this->adapter->loadDataAndReInit( $normalized,
$query_contribution_tracking );
+ $results = $this->adapter->do_transaction( 'Confirm_CreditCard'
);
+ $message = $results->getMessage();
+ if ( $results->getCommunicationStatus() ){
+ $this->info( $data['contribution_tracking_id'] . ":
FINAL: " . $this->adapter->getValidationAction() );
+ $rectified = true;
+ } else {
+ $this->info( $data['contribution_tracking_id'] . ":
ERROR: " . $message );
+ if ( strpos( $message, "GET_ORDERSTATUS reports that
the payment is already complete." ) === 0 ){
+ $rectified = true;
+ }
+
+ //handles the transactions we've cancelled ourselves...
though if they got this far, that's a problem too.
+ $errors = $results->getErrors();
+ if ( !empty( $errors ) && array_key_exists( '1000001',
$errors ) ){
+ $rectified = true;
+ }
+
+ //apparently this is well-formed GlobalCollect for
"iono". Get rid of it.
+ if ( strpos( $message, "No processors are available." )
=== 0 ){
+ $rectified = true;
+ }
+ }
+
+ $this->info( $message );
+
+ return $rectified;
+ }
+
+ /**
+ * Gets the global setting for the key passed in.
+ * @param type $key
+ *
+ * FIXME: Reuse GatewayAdapter::getGlobal.
+ */
+ protected static function getOrphanGlobal( $key ){
+ global $wgDonationInterfaceOrphanCron;
+ if ( array_key_exists( $key, $wgDonationInterfaceOrphanCron ) ){
+ return $wgDonationInterfaceOrphanCron[$key];
+ } else {
+ return NULL;
+ }
+ }
+
+ protected function info( $msg ) {
+ $this->logger->info( $msg );
+ print( "{$msg}\n" );
+ }
+
+ protected function error( $msg ) {
+ $this->logger->error( $msg );
+ error_log( $msg );
+ }
+}
diff --git a/globalcollect_gateway/scripts/orphan.adapter.php
b/globalcollect_gateway/orphan.adapter.php
similarity index 95%
rename from globalcollect_gateway/scripts/orphan.adapter.php
rename to globalcollect_gateway/orphan.adapter.php
index ef11f2a..91b99d4 100644
--- a/globalcollect_gateway/scripts/orphan.adapter.php
+++ b/globalcollect_gateway/orphan.adapter.php
@@ -1,8 +1,8 @@
<?php
class GlobalCollectOrphanAdapter extends GlobalCollectAdapter {
- //Data we know to be good, that we always want to re-assert after a
load or an addData.
- //so far: order_id and the utm data we pull from contribution tracking.
+ //Data we know to be good, that we always want to re-assert after a
load or an addData.
+ //so far: order_id and the utm data we pull from contribution tracking.
protected $hard_data = array ( );
public static function getLogIdentifier() {
@@ -22,9 +22,9 @@
$unstaged += $this->unstage_data( $val, false );
} else {
if ( array_key_exists( $key, $this->var_map ) )
{
- //run the unstage data functions.
+ //run the unstage data functions.
$unstaged[$this->var_map[$key]] = $val;
- //this would be EXTREMELY bad to put in
the regular adapter.
+ //this would be EXTREMELY bad to put in
the regular adapter.
$this->staged_data[$this->var_map[$key]] = $val;
} else {
//$unstaged[$key] = $val;
@@ -67,7 +67,7 @@
);
foreach($utm_keys as $key){
$this->hard_data[$key] = $data[$key];
- }
+ }
}
$this->reAddHardData();
@@ -81,9 +81,9 @@
$this->stageData();
- //have to do this again here.
+ //have to do this again here.
$this->reAddHardData();
-
+
$this->revalidate();
}
@@ -94,7 +94,7 @@
private function reAddHardData() {
//anywhere else, and this would constitute abuse of the system.
- //so don't do it.
+ //so don't do it.
$data = $this->hard_data;
if ( array_key_exists( 'order_id', $data ) ) {
@@ -117,16 +117,16 @@
$data = array( );
- // if contrib tracking id is not already set, we need to insert
the data, otherwise update
+ // if contrib tracking id is not already set, we need to insert
the data, otherwise update
if ( $ctid ) {
$res = $db->select(
- 'contribution_tracking',
+ 'contribution_tracking',
array(
'utm_source',
'utm_campaign',
'utm_medium',
'ts'
- ),
+ ),
array( 'id' => $ctid )
);
foreach ( $res as $thing ) {
diff --git a/globalcollect_gateway/scripts/orphans.php
b/globalcollect_gateway/scripts/orphans.php
index 451df9b..b8c65d4 100644
--- a/globalcollect_gateway/scripts/orphans.php
+++ b/globalcollect_gateway/scripts/orphans.php
@@ -10,230 +10,10 @@
//If you get errors on this next line, set (and export) your MW_INSTALL_PATH
var.
require_once( "$IP/maintenance/Maintenance.php" );
-use Predis\Connection\ConnectionException;
-
-class GlobalCollectOrphanRectifier extends Maintenance {
-
- protected $killfiles = array();
- protected $order_ids = array();
- protected $target_execute_time;
- protected $max_per_execute; //only really used if you're going by-file.
- protected $adapter;
-
- public function execute() {
- // Have to turn this off here, until we know it's using the
user's ip, and
- // not 127.0.0.1 during the batch process. Otherwise, we'll
immediately
- // lock ourselves out when processing multiple charges.
- global $wgDonationInterfaceEnableIPVelocityFilter;
- $wgDonationInterfaceEnableIPVelocityFilter = false;
-
- if ( !$this->getOrphanGlobal( 'enable' ) ){
- $this->info( 'Orphan cron disabled. Have a nice day!' );
- return;
- }
-
- $this->target_execute_time = $this->getOrphanGlobal(
'target_execute_time' );
- $this->max_per_execute = $this->getOrphanGlobal(
'max_per_execute' );
-
- // FIXME: Is this just to trigger batch mode?
- $data = array(
- 'wheeee' => 'yes'
- );
- $this->adapter = new
GlobalCollectOrphanAdapter(array('external_data' => $data));
- $this->logger = DonationLoggerFactory::getLogger(
$this->adapter );
-
- //Now, actually do the processing.
- $this->processOrphans();
- }
-
- protected function keepGoing(){
- $elapsed = $this->getProcessElapsed();
- if ( $elapsed < $this->target_execute_time ) {
- return true;
- } else {
- return false;
- }
- }
-
- /**
- * This will both return the elapsed process time, and echo something
for
- * the cronspammer.
- * @return int elapsed time since start in seconds
- */
- protected function getProcessElapsed(){
- $elapsed = time() - $this->start_time;
- $this->info( "Elapsed Time: {$elapsed}" );
- return $elapsed;
- }
-
- protected function deleteMessage( $correlation_id, $queue ) {
- DonationQueue::instance()->delete( $correlation_id, $queue );
- }
-
- protected function processOrphans() {
- // TODO: Make this configurable.
- // 20 minutes: this is exactly equal to something on
Globalcollect's side.
- $time_buffer = 60*20;
-
- $queue_pool = new CyclicalArray( $this->getOrphanGlobal(
'gc_cc_limbo_queue_pool' ) );
- if ( $queue_pool->isEmpty() ) {
- // FIXME: cheesy inline default
- $queue_pool = new CyclicalArray(
GlobalCollectAdapter::GC_CC_LIMBO_QUEUE );
- }
-
- $this->info( "Slaying orphans..." );
- $this->start_time = time();
-
- //I want to be clear on the problem I hope to prevent with
this. Say,
- //for instance, we pull a legit orphan, and for whatever
reason, can't
- //completely rectify it. Then, we go back and pull more... and
that
- //same one is in the list again. We should stop after one try
per
- //message per execute. We should also be smart enough to not
process
- //things we believe we just deleted.
- $this->handled_ids = array();
-
- while ( $this->keepGoing() && !$queue_pool->isEmpty() ) {
- $current_queue = $queue_pool->current();
- try {
- $message = DonationQueue::instance()->peek(
$current_queue );
-
- if ( !$message ) {
- $this->info( "Emptied queue
[{$current_queue}], removing from pool." );
- $queue_pool->dropCurrent();
- continue;
- }
-
- $correlation_id = 'globalcollect-' .
$message['gateway_txn_id'];
- if ( array_key_exists( $correlation_id,
$this->handled_ids ) ) {
- // We already did this one, keep going.
It's fine to draw
- // again from the same queue.
- DonationQueue::instance()->delete(
$correlation_id, $current_queue );
- continue;
- }
-
- // Check the timestamp to see if it's old
enough, and stop when
- // we're below the threshold. Messages are
guaranteed to pop in
- // chronological order.
- $elapsed = $this->start_time - $message['date'];
- if ( $elapsed < $time_buffer ) {
- $this->info( "Exhausted new messages in
[{$current_queue}], removing from pool..." );
- $queue_pool->dropCurrent();
-
- continue;
- }
-
- // We got ourselves an orphan!
- if ( $this->rectifyOrphan( $message ) ) {
- $this->handled_ids[$correlation_id] =
'rectified';
- } else {
- $this->handled_ids[$correlation_id] =
'error';
- }
-
- // Throw out the message either way.
- $this->deleteMessage( $correlation_id,
$current_queue );
-
- // Round-robin the pool before we complete the
loop.
- $queue_pool->rotate();
- } catch ( ConnectionException $ex ) {
- // Drop this server, for the duration of this
batch.
- $this->error( "Queue server for
[$current_queue] is down! Ignoring for this run..." );
- $queue_pool->dropCurrent();
- }
- }
-
- //TODO: Make stats squirt out all over the place.
- $rec = 0;
- $err = 0;
- foreach( $this->handled_ids as $id=>$whathappened ){
- switch ( $whathappened ){
- case 'rectified' :
- $rec += 1;
- break;
- case 'error' :
- $err += 1;
- break;
- }
- }
- $final = "\nDone! Final results: \n";
- $final .= " $rec rectified orphans \n";
- $final .= " $err errored out \n";
- if ( isset( $this->adapter->orphanstats ) ){
- foreach ( $this->adapter->orphanstats as $status =>
$count ) {
- $final .= "\n Status $status = $count";
- }
- }
- $final .= "\n Approximately " . $this->getProcessElapsed() . "
seconds to execute.\n";
- $this->info( $final );
- }
-
- /**
- * Uses the Orphan Adapter to rectify (complete the charge for) a
single orphan. Returns a boolean letting the caller know if
- * the orphan has been fully rectified or not.
- * @param array $data Some set of orphan data.
- * @param boolean $query_contribution_tracking A flag specifying if we
should query the contribution_tracking table or not.
- * @return boolean True if the orphan has been rectified, false if not.
- */
- protected function rectifyOrphan( $data, $query_contribution_tracking =
true ){
- $data['order_id'] = $data['gateway_txn_id'];
-
- $this->info( "Rectifying orphan: {$data['order_id']}" );
- $rectified = false;
-
- $normalized = DonationQueue::queueMessageToNormalized( $data );
- $this->adapter->loadDataAndReInit( $normalized,
$query_contribution_tracking );
- $results = $this->adapter->do_transaction( 'Confirm_CreditCard'
);
- $message = $results->getMessage();
- if ( $results->getCommunicationStatus() ){
- $this->info( $data['contribution_tracking_id'] . ":
FINAL: " . $this->adapter->getValidationAction() );
- $rectified = true;
- } else {
- $this->info( $data['contribution_tracking_id'] . ":
ERROR: " . $message );
- if ( strpos( $message, "GET_ORDERSTATUS reports that
the payment is already complete." ) === 0 ){
- $rectified = true;
- }
-
- //handles the transactions we've cancelled ourselves...
though if they got this far, that's a problem too.
- $errors = $results->getErrors();
- if ( !empty( $errors ) && array_key_exists( '1000001',
$errors ) ){
- $rectified = true;
- }
-
- //apparently this is well-formed GlobalCollect for
"iono". Get rid of it.
- if ( strpos( $message, "No processors are available." )
=== 0 ){
- $rectified = true;
- }
- }
-
- $this->info( $message );
-
- return $rectified;
- }
-
- /**
- * Gets the global setting for the key passed in.
- * @param type $key
- *
- * FIXME: Reuse GatewayAdapter::getGlobal.
- */
- protected function getOrphanGlobal( $key ){
- global $wgDonationInterfaceOrphanCron;
- if ( array_key_exists( $key, $wgDonationInterfaceOrphanCron ) ){
- return $wgDonationInterfaceOrphanCron[$key];
- } else {
- return NULL;
- }
- }
-
- protected function info( $msg ) {
- $this->logger->info( $msg );
- print( "{$msg}\n" );
- }
-
- protected function error( $msg ) {
- $this->logger->error( $msg );
- error_log( $msg );
- }
+class OrphanMaintenance extends Maintenance {
+ $rectifier = new GlobalCollectOrphanRectifier();
+ $rectifier->processOrphans();
}
-$maintClass = 'GlobalCollectOrphanRectifier';
+$maintClass = 'OrphanMaintenance';
require_once RUN_MAINTENANCE_IF_MAIN;
--
To view, visit https://gerrit.wikimedia.org/r/233877
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I67751d4c325cc9e52a9a0e23770451fcbb110a7e
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/DonationInterface
Gerrit-Branch: master
Gerrit-Owner: Awight <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits