Search This Blog

Monday, April 22, 2013

Another XenServer - iscsi gotcha - problems connecting to an QNAP iSCSI target

Xenserver can decide (ie some how be set) to use Chap authentication on discovery
Chris Keim document this at 1bitatatime.blogspot.com.au.
I had this problem with a new Qnap 870 NAS I could connect using a NFS share, but not using iSCSI.

I wanted to use iSCSI as detailed in this post - as a particular disk with a lot of small files would generate errors and hang the Xenserver.

To diagnose
1) connect from another iSCSI initiator (e.g. Windows). Note you can connect, discover targets and connect to a LUN.
2) attempt from Xenserver - both XenCenter and the command line.
3) attempt to connect using iscsadm


[root@xennet2 ~]# iscsiadm  --mode discovery --type sendtargets --portal 192.168.40.2
iscsiadm: Login failed to authenticate with target
iscsiadm: discovery login to 192.168.40.2 rejected: initiator error (02/01), n                                         on-retryable, giving up
iscsiadm: Could not perform SendTargets discovery.

The clue is the error - "failed to authenticate with target". The Qnap NAS doesn't like the authentication passed during the discovery.

To fix - as documented in  Chris' blog, edit the st_config file - in /etc/iscsi/send_targets/192.168.40.2,3260
remove the lines
discovery.sendtargets.auth.username = <user>
discovery.sendtargets.auth.username = <password>

and add:
discovery.sendtargets.authmethod = none

To be persistent (after reboots) make sure /etc/iscsid.conf also has auth.method set to none for discovery.sendtargets (or is commented out).

Saturday, April 20, 2013

Symfony Project SfServermon - Post 8 Notification

Having got the server status in the previous post, we now handle any notifications resulting.


The notify() method in UpdaterStatus is called from ServerUpdate.
It can use the Entity Manager and configuration references created when UpdaterStatus was initalised.
The notification in phpServerMon and sfServermon support:
  • Email.
  • Logging.
  • SMS - implemented as a seperate bundle.
These are enabled (and checked for here, in the application configuration).
Also each server can be specified as supporting any of the 3 methods to notify:
  • Always - everytime the status is checked - as a 'heartbeat'.
  • If server is offline.
  • If server status has changed.
These are checked for first:
public function notify() {
if ($this->config['email_status'] == false && ! $this->config['config_status'] == false && $this->config['log_status'] == false) {
//nothing to log;
return;
}
$notify = false;
$logWarning = false;

switch($this->config['alert_type']) {
case 'always':
if($this->status_new == 'off') {
// server is offline. we are in error state.
$notify = true;
}
break;
case 'offline':
// only send a notification if the server goes down for the first time!
if($this->status_new == 'off' && $this->status_org == 'on') {
$notify = true;
}
break;
case 'status':
if($this->status_new != $this->status_org) {
// status has been changed!
$notify = true;
}
break;
}

if(!$notify && !$logWarning) {
return false;
}

Log Notification:

Log notification is simply created a new log entity and saving it to the database:
if($this->config['log_status']) {
$log = new MonitorLog();
$log->setServer($this->server);
$log->setType('status');
$log->setMessage('status');
$log->setDateTime(new \DateTime());
$log->setuserId(0);
$log->setMessage($this->server->parseMessage($this->status_new, 'sms'
, array('label'=>$this->server->getLabel()
, 'ip'=>$this->server->getIp()
, 'port'=>$this->server->getPort()
, 'error'=>$this->server->getError())
));
$this->em->persist($log);
$this->em->flush();

phpServerMon had the parseMessage in a utility class, however I have moved it to the server entity, as I thought it made sense for an entity to know how to generate a status message for itself.
The parseMessage method:
public function parseMessage($status, $type, $vars)
{
    $notifications = array(
      'off_sms' => 'Server \'%LABEL%\' is DOWN: ip=%IP%, port=%PORT%. Error=%ERROR%',
      'off_email_subject' => 'IMPORTANT: Server \'%LABEL%\' is DOWN',
      'off_email_body' => "Failed to connect to the following server:<br/><br/>Server: %LABEL%<br/>IP: %IP%<br/>Port: %PORT%<br/>Error: %ERROR%<br/>Date: %DATE%",
      'on_sms' => 'Server \'%LABEL%\' is RUNNING: ip=%IP%, port=%PORT%',
      'on_email_subject' => 'IMPORTANT: Server \'%LABEL%\' is RUNNING',
      'on_email_body' => "Server '%LABEL%' is running again:<br/><br/>Server: %LABEL%<br/>IP: %IP%<br/>Port: %PORT%<br/>Date: %DATE%",
      'warn_sms' => 'Server \'%LABEL%\' needs ATTENTION: ip=%IP%, port=%PORT%. Error=%ERROR%',
      'warn_email_subject' => 'IMPORTANT: Server \'%LABEL%\' need ATTENTION',
      'warn_email_body' => "check server load or disk usage:<br/><br/>Server: %LABEL%<br/>IP: %IP%<br/>Port: %PORT%<br/>Error: %ERROR%<br/>Date: %DATE%",
    );


    $message = '';
  
    //$message = sm_get_lang('notifications', $status . '_' . $type);
    $message=$notifications[$status . '_' . $type];

    if(!$message) {
      return $message;
    }
    $vars['date'] = date('Y-m-d H:i:s');
  
    foreach($vars as $k => $v) {
      $message = str_replace('%' . strtoupper($k) . '%', $v, $message);
    }
  
    return $this->message = $message;
  }

If course the vars associative array should be generated within the class, so the method becomes:


public function parseMessage($status, $type)
{
$vars = array('label'=>$this->getLabel()
, 'ip'=>$this->getIp()
, 'port'=>$this->getPort()
, 'error'=>$this->getError());

$message = '';

//$message = sm_get_lang('notifications', $status . '_' . $type);
$message=$this->notifications[$status . '_' . $type];

if(!$message) {
return $message;
}

$vars = array('label'=>$this->getLabel()
, 'ip'=>$this->getIp()
, 'port'=>$this->getPort()
, 'error'=>$this->getError());

$vars['date'] = date('Y-m-d H:i:s');

foreach($vars as $k => $v) {
$message = str_replace('%' . strtoupper($k) . '%', $v, $message);
}

return $this->message = $message;
}

Note - this is an initial version of the parseMessage method will be recoded as part of a future post to add internationalisation support.

Email Notification.

For email notification we will use Swiftmailerbundle - available as a common addition to Symfony installations.

The code for this:
 protected function notifyByEmail() {

      $message = \Swift_Message::newInstance();
   $message->setSubject($this->server->parseMessage($this->status_new, 'email_subject'))
      ->setFrom($this->config['email_from_email'])
      ->setTo('johnr@reidyint.com')
      ->setBody($this->server->parseMessage($this->status_new, 'email_body'));
       
   $this->mailer->send($message);
}


This version has a hard coded recipient email address, the users to notify are specified in the users table - which is document in a future post in this series.
This needs a reference to the mailer. One was added to the service configuration for ServerUpdate:

services:
  server_update:
    class: JMPR\ServerMonBundle\Update\ServerUpdate
    arguments: [ @doctrine.orm.entity_manager, @mailer ]

And the constructor was updated:
class ServerUpdate
{
protected $em;
protected $mailer;

 public function __construct($em, $mailer)
   {
       $this->em = $em;
       $this->mailer = $mailer;
   }

A reference is then passed to the constructor for the UpdaterStatus class.


SMS Notification - SMS Messages.

The third type of notification supported are SMS text messages. This functionality is implemented as a seperate bundle and documented in the next post.

Friday, April 12, 2013

Reading Exported SugarCRM Modules

I recently had the need to re-create exported SugarCRM modules.
I had exported the modules from a Sugar Online instance with the plan to import them into a hosted SugarCRM instance.
However more time than I expected had passed and version details changed, so the modules would not import.
The modules were not heavily customised, so I decided to look at the exported module files and re-create them.
I wrote a php cli script that give the path to an exported extract module, will list the fields:

John-Reidys-MacBook-2:$ php dumpfields.php Jobs2012_12_19_190139
Modules found:
JOB_Jobs
read module JOB_Jobs: 
module: JOB_Jobs
fields count: 13
    date client_advised_date                    
checkbox completed                             1
    date completed_date                         
 varchar estimated_time                 255
 varchar estimated_time_code             20
currency invoice_amount                  26
currency currency_id                     36
    date invoice_date                           
dropdown job_status                     
[complete->"complete" domain_not_ready->"domain not ready" in_production->"in production" incomplete->"incomplete" on_hold->"on hold" ready->"ready" ]
    date loaded_to_search_date                  
    date loaded_to_server_date                  
    date start_date                             
     int job_number                             


The code does the following:
Checks the arguments passed:

if ($argc == 1 ) {
echo "Usage: {$argv[0]} <module-path> <module-name>\n";
exit(1);
}
$modulePath=$argv[1];
if (!file_exists($modulePath) || is_file($modulePath) ) {
echo "{$argv[0]}: bad module-path {$modulePath}\n";
exit(1);
}

$basedir = $modulePath.'/SugarModules/';
$moduleDir=$basedir.'/modules/';

if (!file_exists($basedir) || is_file($basedir) 
     || !file_exists($moduleDir) || is_file($moduleDir) ) {
echo "{$argv[0]}: bad structure cannot find SugarModules or modules sub directories  module-path: {$modulePath}\n";
exit(1);
}

This is an example of a cli php script - which can be very useful for php programers who need to do scripting.

Argument Handling.

1 or 2 arguments are expected - the path to the module package and the module itself.
The module path is checked and then the expected sub folders in the module.
Then if the module name is not specified, they are listed and the user asked if they want to read the first module:

$moduleName='';
if ($argc == 2) {
  echo "Modules found:\n";
  $handle=opendir($moduleDir);
while (false !== ($entry = readdir($handle))) {
   if (!is_file($entry) && $entry != '.' && $entry != '..') {
    echo "$entry\n";
    if (! $moduleName) $moduleName = $entry;
   }
}
if ($moduleName) {
echo "read module {$moduleName}: ";
 $handle = fopen ("php://stdin","r");
 $line = fgets($handle);

 if(trim($line) != 'yes' && trim($line) != 'y' && trim($line) !=''){
    echo "ABORTING!\n";
    exit(0);
 }

} else {
  exit(1);
}
}
Entering 'y', 'yes' or enter will continue.

Read the Vardefs

Next the variable definitions for the module itself are read.

require $basedir.'modules/'.$moduleName.'/vardefs.php';
require $basedir.'modules/'.$moduleName.'/language/en_us.lang.php';
require $basedir.'language/application/en_us.lang.php';

class VardefManager{
    static $custom_disabled_modules = array();
    static $linkFields;

    /**
     * this method is called within a vardefs.php file which extends from a SugarObject.
     * It is meant to load the vardefs from the SugarObject.
     */
    static function createVardef($module, $object, $templates = array('default'), $object_name = false)
    {
}
}




The included class and static method - createVarDef is invoked by the vardefs.php, this currently does nothing, however the following code could be moved inside it.

Print the Fields

The fields and some of the details are then printed:


$fields = $dictionary[$moduleName]['fields'];
printf("module: %s\n", $moduleName);
$maxfieldlen=0;
foreach($fields as $fieldname => $fielddef) $maxfieldlen = strlen($fieldname) > $maxfieldlen ? strlen($fieldname) : $maxfieldlen;
echo "fields count: ". count($fields)."\n";
$fieldNameFmt="-{$maxfieldlen}.{$maxfieldlen}";
foreach($fields as $fieldname => $fielddef) {
switch($fielddef['type']) {
case 'date':
case 'datetime':
case 'bool':
case 'int':
  printf("%8.8s %{$fieldNameFmt}s %8.8s %8.8s", $fielddef['type']=='bool'?'checkbox':$fielddef['type'], $fieldname, $fielddef['required'], isset($fielddef['default'])?isset($fielddef['default']) : '');
break;
case 'enum':
  printf("%8.8s %{$fieldNameFmt}s %8.8s ", 'dropdown', $fieldname, $fielddef['required']);
  $enumList = $app_list_strings[$fielddef['options']];
$maxEnumValuelen=$maxEnumLablelen=0;
foreach($enumList as $enumValue => $enumLabel) {
$maxEnumValuelen = strlen($enumValue) > $maxEnumValuelen ? strlen($enumValue) : $maxEnumValuelen;
$maxEnumLablelen = strlen($enumLabel) > $maxEnumLablelen ? strlen($enumLabel) : $maxEnumLablelen;
  }
  printf("\n\t[");
$enumValueFmt=""; //"{$maxEnumValuelen}.{$maxEnumValuelen}";
$enumLableFmt="-";//"-{$maxEnumLablelen}.{$maxEnumLablelen}";
  foreach($enumList as $enumValue => $enumLabel) {
    $enumLabelpp = "\"${enumLabel}\"";
printf("%{$enumValueFmt}s->%{$enumLableFmt}s ", $enumValue, $enumLabelpp);
}
  printf("]");
  
break;
default:
  printf("%8.8s %{$fieldNameFmt}s %8.8s %3.3d", $fielddef['type'], $fieldname, $fielddef['required'],isset($fielddef['len'])?$fielddef['len']:$fielddef['size']);
break;
}

echo "\n";
}


This could be extended and made smarter to print more information, however for my purposes it was enough.

The complete script.



<?php

if ($argc == 1 ) {
echo "Usage: {$argv[0]} <module-path> <module-name>\n";
exit(1);
}
$modulePath=$argv[1];
if (!file_exists($modulePath) || is_file($modulePath) ) {
echo "{$argv[0]}: bad module-path {$modulePath}\n";
exit(1);
}

$basedir = $modulePath.'/SugarModules/';
$moduleDir=$basedir.'/modules/';

if (!file_exists($basedir) || is_file($basedir) 
     || !file_exists($moduleDir) || is_file($moduleDir) ) {
echo "{$argv[0]}: bad structure cannot find SugarModules or modules sub directories  module-path: {$modulePath}\n";
exit(1);
}

$moduleName='';
if ($argc == 2) {
  echo "Modules found:\n";
  $handle=opendir($moduleDir);
while (false !== ($entry = readdir($handle))) {
   if (!is_file($entry) && $entry != '.' && $entry != '..') {
    echo "$entry\n";
    if (! $moduleName) $moduleName = $entry;
   }
}
if ($moduleName) {
echo "read module {$moduleName}: ";
 $handle = fopen ("php://stdin","r");
 $line = fgets($handle);

 if(trim($line) != 'yes' && trim($line) != 'y' && trim($line) !=''){
    echo "ABORTING!\n";
    exit(0);
 }

} else {
  exit(1);
}
}


require $basedir.'modules/'.$moduleName.'/vardefs.php';
require $basedir.'modules/'.$moduleName.'/language/en_us.lang.php';
require $basedir.'language/application/en_us.lang.php';

class VardefManager{
    static $custom_disabled_modules = array();
    static $linkFields;

    /**
     * this method is called within a vardefs.php file which extends from a SugarObject.
     * It is meant to load the vardefs from the SugarObject.
     */
    static function createVardef($module, $object, $templates = array('default'), $object_name = false)
    {
}
}



$fields = $dictionary[$moduleName]['fields'];
printf("module: %s\n", $moduleName);
$maxfieldlen=0;
foreach($fields as $fieldname => $fielddef) $maxfieldlen = strlen($fieldname) > $maxfieldlen ? strlen($fieldname) : $maxfieldlen;
echo "fields count: ". count($fields)."\n";
$fieldNameFmt="-{$maxfieldlen}.{$maxfieldlen}";
foreach($fields as $fieldname => $fielddef) {
switch($fielddef['type']) {
case 'date':
case 'datetime':
case 'bool':
case 'int':
  printf("%8.8s %{$fieldNameFmt}s %8.8s %8.8s", $fielddef['type']=='bool'?'checkbox':$fielddef['type'], $fieldname, $fielddef['required'], isset($fielddef['default'])?isset($fielddef['default']) : '');
break;
case 'enum':
  printf("%8.8s %{$fieldNameFmt}s %8.8s ", 'dropdown', $fieldname, $fielddef['required']);
  $enumList = $app_list_strings[$fielddef['options']];
$maxEnumValuelen=$maxEnumLablelen=0;
foreach($enumList as $enumValue => $enumLabel) {
$maxEnumValuelen = strlen($enumValue) > $maxEnumValuelen ? strlen($enumValue) : $maxEnumValuelen;
$maxEnumLablelen = strlen($enumLabel) > $maxEnumLablelen ? strlen($enumLabel) : $maxEnumLablelen;
  }
  printf("\n\t[");
$enumValueFmt=""; //"{$maxEnumValuelen}.{$maxEnumValuelen}";
$enumLableFmt="-";//"-{$maxEnumLablelen}.{$maxEnumLablelen}";
  foreach($enumList as $enumValue => $enumLabel) {
    $enumLabelpp = "\"${enumLabel}\"";
printf("%{$enumValueFmt}s->%{$enumLableFmt}s ", $enumValue, $enumLabelpp);
}
  printf("]");
  
break;
default:
  printf("%8.8s %{$fieldNameFmt}s %8.8s %3.3d", $fielddef['type'], $fieldname, $fielddef['required'],isset($fielddef['len'])?$fielddef['len']:$fielddef['size']);
break;
}

echo "\n";
}
?>



Wednesday, March 27, 2013

Symfony Project sfservermon Post 7 - Update Details - UpdaterServer class


Post 6 discussed the high level ServerUpdate class, this post finally documents the details of the UpdaterStatus class.

UpdaterStatus Class

This class in particular has about 90% of its code excellent taken from the original phpServerMon:

The class has the following methods:

  • Constructor - which saves references to the Entity manager, configuration and logger
  • getStatus - which calls update to update the server status.
  • update - which switches based on server type service or website to get the server status. It will recall itself recursively - to max runs to retry a connetion attempt.
  • updateService - checks a service server type
  • updateWebsite - checks a website server type.
  • notify - sends the required notification.
The first 3 methods are straightforward:
public function __construct($em, $configuration, $monitorLog)
{
    $this->em = $em;
    $this->config = $configuration;
    $this->monitorLog = $monitorLog;
}

public function setServer($server, $status_org) {
$this->clearResults();
$this->server = $server;
$this->status_org = $status_org;
}

/**
 * Get the new status of the selected server.
 *  If the update has not been performed yet it will do so first
 *
 * @return string
 */
public function getStatus() {

if(!$this->server) {
return false;
}
if(!$this->status_new) {
$this->update(3);
}
return $this->status_new;
}

public function update($max_runs=2)
{
switch($this->server->getType()) {
case 'service':
$result = $this->updateService($max_runs);
break;
case 'website':
$result = $this->updateWebsite($max_runs);
break;
}
return $result;
}



update Service Method

The updateService method checks a service -for example a database port on a server.
It requires a server tcp/udp port number and an ip address/hostname.
This method - and updateWebsite also record the response time.


protected function updateService($max_runs, $run = 1) {
// save response time
$time = explode(' ', microtime());
$starttime = $time[1] + $time[0];

@$fp = fsockopen ($this->server->getIp(), $this->server->getPort(), $errno, $errstr, 10);
Try to open a socket connection to the port using fsockopen , the last parameter is a timeout value - this should probably be stored in the server configuration, here it is 10 seconds.

$time = explode(" ", microtime());
$endtime = $time[1] + $time[0];
$this->rtime = ($endtime - $starttime);


$this->status_new = ($fp === false) ? 'off' : 'on';
$this->error = $errstr;
// add the error to the server array for when parsing the messages
$this->server->setError($this->error);
Was the socket opened? Check and save the setting, also save the error string.

@fclose($fp);

// check if server is available and rerun if asked.
if($this->status_new == 'off' && $run < $max_runs) {
return $this->updateService($max_runs, $run + 1);
} 
If failed call again. return $this->status_new;

}

update Website Method.

This uses curl to opena connection to the website, it then checks the http status code.
An error can happen if
  • There is no response.
  • An unreadable response
  • Code is not as expected: HTTP/1.1 200 OK

protected function updateWebsite($max_runs, $run = 1) {
// save response time
$time = explode(' ', microtime());
$starttime = $time[1] + $time[0];

as before, record the start time.

$ch = curl_init();
curl_setopt ($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt ($ch, CURLOPT_URL, $this->server->getIp());
curl_setopt ($ch, CURLOPT_CONNECTTIMEOUT, 10);
curl_setopt ($ch, CURLOPT_TIMEOUT, 10);
curl_setopt ($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.11) Gecko/20071127 Firefox/2.0.0.11');

// We're only interested in the header, because that should tell us plenty!
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_NOBODY, true);
Create and initialise a curl object, we are only interested in the headers.

$headers = curl_exec ($ch);
curl_close ($ch);
Attempt to connect, and save the headers.
$time = explode(" ", microtime());
$endtime = $time[1] + $time[0];
$this->rtime = ($endtime - $starttime);
Record the time taken.
// the first line would be the status code..
$status_code = strtok($headers, "\r\n");
// keep it general
// $code[1][0] = status code
// $code[2][0] = name of status code
preg_match_all("/[A-Z]{2,5}\/\d\.\d\s(\d{3})\s(.*)/", $status_code, $code_matches);

Check and examine the http status.

if(empty($code_matches[0])) {
// somehow we dont have a proper response.
$this->error = 'no response from server';
$this->server->setError($this->error);
$this->status_new = 'off';
} else {
$code = $code_matches[1][0];
$msg = $code_matches[2][0];

// All status codes starting with a 4 or higher mean trouble!
if(substr($code, 0, 1) >= '4') {
$this->error = $code . ' ' . $msg;
$this->server->setError($this->error);
$this->status_new = 'off';
} else {
$this->status_new = 'on';
}
}

// check if server is available and rerun if asked.
if($this->status_new == 'off' && $run < $max_runs) {
return $this->updateWebsite($max_runs, $run + 1);
}


Recall if necessary

return $this->status_new;
}


The next post discussed the notification after a server status check.


Symfony Project sfservermon Post 6 - Update server status.

Updating the Server status.

The previous post allow us to create a new updater class and run it from the command line:


[johnr@sentos4 webserver]$ app/console servermon:update

It also talked about the initialisation of the ServerUpdate class using dependency injection as a Symfony service.

This post documents the actual update process, and involves 2 classes:
  • ServerUpdate - which uses the UpdaterStatus class to check the status for all required servers.
  • UpdaterStatus - updates a status for one particular server, it also provides a notify method to do the required notification.
At first thought much of the ServerUpdate code could be included in the UpdateCommand class - however the Symfony documentation for the console class reccommends that the command class do very little other than check any required parameters - for this application at least this is a very good reason - as an update can be initiated from the update action in the default Controller.

Much of this code is a straight port (read copy and paste) from the excellent code in phpServerMon application.

ServerUpdate Class.

This class has an update method: called from the updateCommand or the updateAction:


$serverUpdate =  $this->container = 
     $this->getApplication()->getKernel()->getContainer()->get('server_update');
$serverUpdate->update($serverLabel);

It does the following:
  1. Creates an array $servers[] of the required servers to update
  2. Reads the application configuration.
  3. Creates a Monitor log class.
  4. Creates a new updaterStatus class.
  5. Loops through each server: 
Reading the Required Servers:
If a server label is specified read that one or read all active servers:

if ($serverLabel) {
$servers = $this->em->getRepository('JMPRServerMonBundle:MonitorServers')->findByLabel($serverLabel);
} else {
$servers = $this->em->getRepository('JMPRServerMonBundle:MonitorServers')->findByActive('yes');
}

Getting the Configuration and Creating the required Classes.


//get Configuration.
$configuration = $this->em->getRepository('JMPRServerMonBundle:MonitorConfig')->getConfig();

$monitorLog = $this->em->getRepository('JMPRServerMonBundle:MonitorLog');

$updater = new UpdaterStatus($this->em, $configuration, $monitorLog);


The configuration data requires helper class in its repository: - getConfig.
The configuration is stored (persisted) as a series of database records - 1 record for each configuration setting, but is used throughout the application as an associative array:

public function getConfig() {
$configArray=array();
$configs = $this->findAll();
foreach($configs as $config) {
$configArray[$config->getKey()] = $config->getValue();
}
return $configArray;
}

Main Server loop:

This is where the work happens:
  1. Saving the old status
  2. Calling UpdateStatus to updating it
  3. Notifying and
  4. Updating the server entity


(1) $status_org = $server->getStatus();

// remove the old status from the array to avoid confusion between the new and old status
$server->setStatus('unknown');

(2) $updater->setServer($server, $status_org);

//check server status
$status_new = $updater->getStatus();

(3) //notify the nerds of applicable
$updater->notify();

//update server status
(4) $server->setLastCheck(new \DateTime('now'));
$server->setStatus($status_new);
$server->setError($updater->getError());
$server->setRtime($updater->getRtime());

if ($status_new = 'on') {
$server->setLastOnline($server->getLastCheck());
}
$this->em->persist($server);
$this->em->flush();

The next post goes into details on the Updater Status class.