Push now also only does a differential push

- send uuids of events to be pushed together with timestamps to the other instance
- other instance removes events that are already up to date or locally created from the array
- sends the remaining uuids back
- first instance initiates the push of events that were not filtered out
pull/304/merge
iglocska 2014-08-12 11:54:00 +02:00
parent e6c28fe69a
commit 8b16f0cf18
4 changed files with 145 additions and 86 deletions

View File

@ -977,7 +977,7 @@ class EventsController extends AppController {
$fieldList = array(
'Event' => array('date', 'threat_level_id', 'analysis', 'info', 'published', 'uuid', 'from', 'distribution', 'timestamp'),
'Attribute' => array('event_id', 'category', 'type', 'value', 'value1', 'value2', 'to_ids', 'uuid', 'revision', 'distribution', 'timestamp', 'comment'),
'ShadowAttribute' => array('event_id', 'category', 'type', 'value', 'value1', 'value2', 'org', 'event_org', 'comment', 'event_uuid', 'deleted', 'to_ids')
'ShadowAttribute' => array('event_id', 'category', 'type', 'value', 'value1', 'value2', 'org', 'event_org', 'comment', 'event_uuid', 'deleted', 'to_ids', 'uuid')
);
$c = 0;
@ -1003,7 +1003,7 @@ class EventsController extends AppController {
// check if the exact proposal exists, if yes check if the incoming one is deleted or not. If it is deleted, remove the old proposal and replace it with the one marked for being deleted
// otherwise throw the new one away.
if (isset($this->request->data['ShadowAttribute'])) {
foreach ($this->request->data['ShadowAttribute'] as &$proposal) {
foreach ($this->request->data['ShadowAttribute'] as $k => &$proposal) {
$existingProposal = $this->Event->ShadowAttribute->find('first', array(
'recursive' => -1,
'conditions' => array(
@ -1011,14 +1011,14 @@ class EventsController extends AppController {
'category' => $proposal['category'],
'to_ids' => $proposal['to_ids'],
'type' => $proposal['type'],
'event_uuid' => $event_uuid,
'uuid' => $uuid
'event_uuid' => $proposal['event_uuid'],
'uuid' => $proposal['uuid']
)
));
if ($existingProposal['ShadowAttribute']['deleted'] == 1) {
$this->Event->ShadowAttribute->delete($existingProposal['ShadowAttribute']['id'], false);
} else {
unset($proposal);
unset($this->request->data['ShadowAttribute'][$k]);
}
}
}
@ -1029,6 +1029,7 @@ class EventsController extends AppController {
} else {
throw new MethodNotAllowedException();
}
if ($saveResult) {
// TODO RESTfull: we now need to compare attributes, to see if we need to do a RESTfull attribute delete
$message = 'Saved';
@ -2561,4 +2562,31 @@ class EventsController extends AppController {
throw new Exception(h($result['message']));
}
}
public function filterEventIdsForPush() {
if (!$this->userRole['perm_sync']) throw new MethodNotAllowedException('You do not have the permission to do that.');
if ($this->request->is('post')) {
$incomingIDs = array();
$incomingEvents = array();
foreach ($this->request->data as $event) {
$incomingIDs[] = $event['Event']['uuid'];
$incomingEvents[$event['Event']['uuid']] = $event['Event']['timestamp'];
}
$events = $this->Event->find('all', array(
'conditions' => array('Event.uuid' => $incomingIDs),
'recursive' => -1,
'fields' => array('Event.uuid', 'Event.timestamp', 'Event.locked'),
));
foreach ($events as $k => $v) {
if (!$v['Event']['timestamp'] < $incomingEvents[$v['Event']['uuid']]) {
unset($incomingEvents[$v['Event']['uuid']]);
continue;
}
if ($v['Event']['locked'] == 0) {
unset($incomingEvents[$v['Event']['uuid']]);
}
}
$this->set('result', array_keys($incomingEvents));
}
}
}

View File

@ -456,18 +456,21 @@ class Event extends AppModel {
* @throws InternalErrorException
*/
public function cleanupEventArrayFromXML(&$data) {
// Workaround for different structure in XML/array than what CakePHP expects
if (isset($data['Event']['Attribute']) && is_array($data['Event']['Attribute']) && count($data['Event']['Attribute'])) {
if (is_numeric(implode(array_keys($data['Event']['Attribute']), ''))) {
// normal array of multiple Attributes
$data['Attribute'] = $data['Event']['Attribute'];
} else {
// single attribute
$data['Attribute'][0] = $data['Event']['Attribute'];
$objects = array('Attribute', 'ShadowAttribute');
foreach ($objects as $object) {
// Workaround for different structure in XML/array than what CakePHP expects
if (isset($data['Event'][$object]) && is_array($data['Event'][$object]) && count($data['Event'][$object])) {
if (is_numeric(implode(array_keys($data['Event'][$object]), ''))) {
// normal array of multiple Attributes
$data[$object] = $data['Event'][$object];
} else {
// single attribute
$data[$object][0] = $data['Event'][$object];
}
}
unset($data['Event'][$object]);
}
unset($data['Event']['Attribute']);
return $data;
}
@ -587,49 +590,41 @@ class Event extends AppModel {
// LATER try to do this using a separate EventsController and renderAs() function
$xmlArray = array();
// rearrange things to be compatible with the Xml::fromArray()
$event['Event']['Attribute'] = $event['Attribute'];
unset($event['Attribute']);
$event['Event']['ShadowAttribute'] = $event['ShadowAttribute'];
unset($event['ShadowAttribute']);
if (isset($event['Attribute'])) {
$event['Event']['Attribute'] = $event['Attribute'];
unset($event['Attribute']);
}
// cleanup the array from things we do not want to expose
//unset($event['Event']['org']);
// remove value1 and value2 from the output
foreach ($event['Event']['Attribute'] as $key => &$attribute) {
// do not keep attributes that are private, nor cluster
if ($attribute['distribution'] < 2) {
unset($event['Event']['Attribute'][$key]);
continue; // stop processing this
if (isset($event['Event']['Attribute'])) {
foreach ($event['Event']['Attribute'] as $key => &$attribute) {
// do not keep attributes that are private, nor cluster
if ($attribute['distribution'] < 2) {
unset($event['Event']['Attribute'][$key]);
continue; // stop processing this
}
// Distribution, correct Connected Community to Community in Attribute
if ($attribute['distribution'] == 2) {
$attribute['distribution'] = 1;
}
// remove value1 and value2 from the output
unset($attribute['value1']);
unset($attribute['value2']);
// also add the encoded attachment
if ($this->Attribute->typeIsAttachment($attribute['type'])) {
$encodedFile = $this->Attribute->base64EncodeAttachment($attribute);
$attribute['data'] = $encodedFile;
}
// Passing the attribute ID together with the attribute could cause the deletion of attributes after a publish/push
// Basically, if the attribute count differed between two instances, and the instance with the lower attribute
// count pushed, the old attributes with the same ID got overwritten. Unsetting the ID before pushing it
// solves the issue and a new attribute is always created.
unset($attribute['id']);
}
// Distribution, correct Connected Community to Community in Attribute
if ($attribute['distribution'] == 2) {
$attribute['distribution'] = 1;
}
// remove value1 and value2 from the output
unset($attribute['value1']);
unset($attribute['value2']);
// also add the encoded attachment
if ($this->Attribute->typeIsAttachment($attribute['type'])) {
$encodedFile = $this->Attribute->base64EncodeAttachment($attribute);
$attribute['data'] = $encodedFile;
}
// Passing the attribute ID together with the attribute could cause the deletion of attributes after a publish/push
// Basically, if the attribute count differed between two instances, and the instance with the lower attribute
// count pushed, the old attributes with the same ID got overwritten. Unsetting the ID before pushing it
// solves the issue and a new attribute is always created.
unset($attribute['id']);
}
foreach ($event['Event']['ShadowAttribute'] as $k => &$v) {
if ($this->ShadowAttribute->typeIsAttachment($v['type'])) {
$encodedFile = $this->ShadowAttribute->base64EncodeAttachment($v);
$v['data'] = $encodedFile;
}
unset($v['id']);
unset($v['value1']);
unset($v['value2']);
}
// Distribution, correct All to Community in Event
if ($event['Event']['distribution'] == 2) {
$event['Event']['distribution'] = 1;
@ -640,6 +635,7 @@ class Event extends AppModel {
$eventsXml = $xmlObject->asXML();
// do a REST POST request with the server
$data = $eventsXml;
// LATER validate HTTPS SSL certificate
$this->Dns = ClassRegistry::init('Dns');
if ($this->Dns->testipaddress(parse_url($uri, PHP_URL_HOST))) {

View File

@ -335,46 +335,51 @@ class Server extends AppModel {
'Event.attribute_count >' => 0
), //array of conditions
'recursive' => -1, //int
'fields' => array('Event.id'), //array of field names
'fields' => array('Event.id', 'Event.timestamp', 'Event.uuid'), //array of field names
);
$eventIds = $eventModel->find('all', $findParams);
}
$eventCount = count($eventIds);
//debug($eventIds);
// now process the $eventIds to pull each of the events sequentially
if (!empty($eventIds)) {
$successes = array();
$fails = array();
$lowestfailedid = null;
foreach ($eventIds as $k => $eventId) {
$eventModel->recursive=1;
$eventModel->contain(array('Attribute'));
$event = $eventModel->findById($eventId['Event']['id']);
$event['Event']['locked'] = true;
$result = $eventModel->uploadEventToServer(
$event,
$this->data,
$HttpSocket);
if ('Success' === $result) {
$successes[] = $event['Event']['id'];
$eventUUIDsFiltered = $this->filterEventIdsForPush($id, $HttpSocket, $eventIds);
if (!empty($eventUUIDsFiltered)) {
$eventCount = count($eventUUIDsFiltered);
//debug($eventIds);
// now process the $eventIds to pull each of the events sequentially
if (!empty($eventIds)) {
$successes = array();
$fails = array();
$lowestfailedid = null;
foreach ($eventUUIDsFiltered as $k => $eventUuid) {
$eventModel->recursive=1;
$eventModel->contain(array('Attribute'));
$event = $eventModel->findByUuid($eventUuid);
$event['Event']['locked'] = true;
$result = $eventModel->uploadEventToServer(
$event,
$this->data,
$HttpSocket);
if ('Success' === $result) {
$successes[] = $event['Event']['id'];
} else {
$fails[$event['Event']['id']] = $result;
}
if ($jobId && $k%10 == 0) {
$job->saveField('progress', 100 * $k / $eventCount);
}
}
if (count($fails) > 0) {
// there are fails, take the lowest fail
$lastpushedid = min(array_keys($fails));
} else {
$fails[$event['Event']['id']] = $result;
}
if ($jobId && $k%10 == 0) {
$job->saveField('progress', 100 * $k / $eventCount);
// no fails, take the highest success
$lastpushedid = max($successes);
}
// increment lastid based on the highest ID seen
// Save the entire Server data instead of just a single field, so that the logger can be fed with the extra fields.
$this->data['Server']['lastpushedid'] = $lastpushedid;
$this->save($this->data);
}
if (count($fails) > 0) {
// there are fails, take the lowest fail
$lastpushedid = min(array_keys($fails));
} else {
// no fails, take the highest success
$lastpushedid = max($successes);
}
// increment lastid based on the highest ID seen
// Save the entire Server data instead of just a single field, so that the logger can be fed with the extra fields.
$this->data['Server']['lastpushedid'] = $lastpushedid;
$this->save($this->data);
}
if (!isset($successes)) $successes = null;
if (!isset($fails)) $fails = null;
@ -397,4 +402,32 @@ class Server extends AppModel {
return array($successes, $fails);
}
}
public function filterEventIdsForPush($id, $HttpSocket, $eventIds) {
foreach ($eventIds as $k => $event) {
unset($eventIds[$k]['Event']['id']);
}
$server = $this->read(null, $id);
if (null == $HttpSocket) {
App::uses('SyncTool', 'Tools');
$syncTool = new SyncTool();
$HttpSocket = $syncTool->setupHttpSocket($server);
}
$data = json_encode($eventIds);
$request = array(
'header' => array(
'Authorization' => $server['Server']['authkey'],
'Accept' => 'application/json',
'Content-Type' => 'application/json',
)
);
$uri = $server['Server']['url'] . '/events/filterEventIdsForPush';
$response = $HttpSocket->post($uri, $data, $request);
if ($response->code == '200') {
$uuidList = json_decode($response->body());
} else {
return false;
}
return $uuidList;
}
}

View File

@ -0,0 +1,2 @@
<?php
echo json_encode($result);