DroboFS : a php status page.

I recently bought a DroboFS for the storage needs of my lab. It is a nice piece of hardware but I was extremely surprised to see that the only way to configure it is the Drobo Dashboard, a Windows/Mac binary that must be plugged in the same network as the DroboFS (it sends broadcast packets to locate the DroboFS and it is impossible just to give it the IP address).

Why no web interface, such as the one on the Iomega StorCenter Ix2, a much cheaper network drive ?

A recent project to develop a webdashboard (by a user, not the company) exists, but to this day there is very little code available.

Based on his code, I wrote a PHP page (dirty code as I used PHP only once in the last 10 years) that displays the data I was able to get from the share configuration and status data of the Drobo.

edit:anonymized screenshots



I still cannot add users, allow them to change their own password, add a share and change user rights, but that’s a start !

Here is the source code in the hope it might be useful to someone else.

Known bug : if you did not define any users in your DroboFS the status display will fail.

To use it : activate DroboApps on your DroboFS (with Dashboard), install the Apache droboapp, create the file droboStatus.php in the /DroboApps/apache/www directory and fill it with the code that follows. To see the result, just go to http://droboIP:8080/droboStatus.php with your browser.

 

<html>
 <head>
   <title>PHP Test</title>
 </head>
 <body>
  <?php
/*
 droboStatus;php : a simple php page to display the status of a DroboFS
 Copyright (C) 2011 Manik Bhattacharjee - manik-listes@altern.org 

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU Affero General Public License as
 published by the Free Software Foundation, either version 3 of the
 License, or (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 GNU Affero General Public License for more details.

 You should have received a copy of the GNU Affero General Public License
 along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

/***************************** A short (and incomplete) XML parsing function **********************************************/
  function parseBasicXML(&$sourcexml, $markOpen = "DRIShareConfig") {
    //echo "<br>Parsing...<br>";
    $root = array();
    $index = 0;
        // Searching for markups
      while (preg_match("/([^<]*)<([\s\w\/]+)>/", $sourcexml, $matches, PREG_OFFSET_CAPTURE) ) {
          //echo "WHILE ";
          $index = $index + 1;
          // For each markup
          $content = $matches[1][0];
          $markup = $matches[2][0];
          //echo strlen($sourcexml);
          //echo "$markup<br/>";
          // Remove this part from the string
          $sourcexml = substr($sourcexml, strlen($markup) + 1 +  $matches[2][1]);
          //echo "Match : $markup\n";

            // 3 Cases : <Username>, </Username>, and <Username />
            // Entering : call the function recursively, it will return a new node
            if (preg_match("/^\s*(\w+)\s*$/", $markup, $mark)){
              //echo "New mark ".$mark[1]." <br>\n";
              $root[$mark[1].$index] = parseBasicXML($sourcexml, $mark[1]);
            }
            // Open/Closed markup (e.g. <br /> : add it as a child with no value
            elseif (preg_match("/^\s*(\w+)\s*\/$/", $markup, $mark)){
                  $root[$mark[1].$index] = "novalue";
                  //echo "OpenClosed mark ".$mark[1]."<br>";
            }
            // Closed Markup : if it really is our markOpen markup, return ! Otherwise, there is something very wrong !
            elseif (preg_match("/^\s*\/\s*(\w+)\s*$/", $markup, $mark)){
              if ($mark[1] === $markOpen){
                //echo "Closing mark ".$mark[1]." with content $content<br>";
                $root["content"] = $content;
                return $root;
              }else{
                echo "Unexpected input $lt;/".$mark[1].", whereas $markOpen was expected.";
              }

          }else{
            echo "Unidentified markup : $markup";
          }
      //echo "<br>";
    }
    return $root;
  }
/********************************END XML Parsing function********************************************************************/

/***************************** Sub Functions for Drobo configuration Analysis****************************************************************/
  function yesOrNo($val){
    if($val == 0){ return "No";}else{return "Yes";}
  }

  function userRights($r){
    if ($r == 0){ return "<font color=\"orange\">Read only</font>";}elseif($r == 1){ return "<font color=\"green\">Read/Write</font>";}else{return "Unknown rights !";}
  }

  function displayPassword($val){ return '***';}

  function displayUser($us){
    echo "<tr><td><font color = \"blue\"><b>".$us['Username1']['content']."</b></font></td><td>".yesOrNo($us['ValidPassword3']['content'])."</td><td>".yesOrNo($us['EncryptedPassword4']['content'])."</td><td><font color=\"gray\">".displayPassword($us['Password2']['content'])."</font></td></tr>\n";
  }

  function displayShare($sh){
// echo '<table border="1"><tr><td><b>ShareName<b></td><td>UserName</td><td>Rights</td><td>ShareState</td><td>TimeMachineEnabled</td><td>ShareMaxTMSizeGB</td><td>OldShareName</td></tr>'."\n";
    echo "<tr bgcolor=\"#aaaaaa\"><td><font color = \"blue\"><b>".$sh['ShareName1']['content']."</b></font></td><td></td><td></td><td>".$sh['ShareState2']['content']."</td><td>".yesOrNo($sh['TimeMachineEnabled3']['content'])."</td><td>".$sh['ShareMaxTMSizeGB4']['content']."</td><td>".$sh['OldShareName6']."</td></tr>\n";
    $su = $sh['ShareUsers5'];
    $index = 1;
    while($su["ShareUser$index"]){
      echo '<tr><td></td><td><font color = "blue">'.$su["ShareUser$index"]['ShareUsername1']['content'].'</font></td><td>'.userRights($su["ShareUser$index"]['ShareUserAccess2']['content']).'</td><td></td><td></td><td></td><td></td></tr>'."\n";
      $index = $index+1;
    }
    //echo "</table>\n";
  }
/**************************************End of Sub Functions for Drobo configuration Analysis**************************************************************/

/********************************** Drobo Configuration Analysis and display ******************************************************************/
  function displayNASconf($nc){

    // USERS
    echo "<h2><center>Users</center></h2>\n";
    echo '<center><table border="1"><tr><td><b>UserName</b></td><td>Valid Password</td><td>Encrypted Password</td><td width = "50">Password</td></tr>'."\n";
    $users = $nc["DRIShareConfig1"]["UserList2"];
    $index = 1;
    while($users["User$index"]){
      displayUser($users["User$index"]);
      $index = $index+1;
    }
    echo "</table></center>\n<br/><br/><br/><br/>\n";

    // SHARES
    echo "<h2><center>Shares</center></h2>\n";
    $shares = $nc["DRIShareConfig1"]['Shares3'];
    $index = 1;
    echo '<center><table border="1"><tr><td><b>ShareName<b></td><td>UserName</td><td>Rights</td><td>ShareState</td><td>TimeMachineEnabled</td><td>ShareMaxTMSizeGB</td><td>OldShareName</td></tr>'."\n";
    while($shares["Share$index"]){
      displayShare($shares["Share$index"]);
      echo "\n";
      $index = $index+1;
    }
    echo "</table></center>\n<br/><br/><br/><br/>\n";
  }

/*********************************** End of Drobo Configuration Analysis and display ************************************************/

/******************************** Sub Functions for Drobo Status Analysis *******************************************************************/
  function getCurrentStatus(){
    //echo '<form><textarea cols="120" rows="40">';
    $cfgServer = "localhost";
    $cfgPort    = 5000;
    $cfgTimeOut = 10;

    $drobofs = fsockopen($cfgServer, $cfgPort, $errno, $errstr, $cfgTimeOut);
    $statusxml = "";
    if (!$drobofs) {
      echo "Connexion failed : $errstr\n";
    } else {
      // read lines until end of "</ESATMUpdate>" is seen
      while (!feof($drobofs)) {
        $line = fgets($drobofs, 128);
        $statusxml = $statusxml.$line; 
        if (strpos($line, "</ESATMUpdate>") === 0){ break;}
      }
    }
    if ($drobofs) {
      fclose($drobofs);
    }

    return parseBasicXML($statusxml);
  }

  function simpleString($val){
    if ($val === 'novalue'){ return "undefined";}else{return $val['content'];}
  }

  function simpleMask($val){
    return base_convert($val['content'], 10, 2).'b';
  }

  // From http://code.google.com/p/drobowebdashboard/wiki/ESATMUpdate#ESATMUpdate/mStatus : should analyze the bitmask...
  function mStatus($val){
    //$ret = $val['content'].' = '.base_convert($val['content'], 10, 2).'b -> ';
    $ret = '';
    switch ($val['content']){
      case 32768:
        return $ret.'<font style="BACKGROUND-COLOR: green" color="white">Everything ok, or undergoing relayout</font>';
      case 32772:
        return $ret.'<font style="BACKGROUND-COLOR: yellow" color="black">Yellow capacity warning, replace first drive</font>';
      case 32774:
        return $ret.'<font style="BACKGROUND-COLOR: red" color="white">Red capacity warning, replace first drive</font>';
      case 32784:
        return $ret.'<font style="BACKGROUND-COLOR: red" color="white">Bad drive in 3rd bay</font>';
      case 33344:
        return $ret.'<font style="BACKGROUND-COLOR: orange" color="black">Rebuilding</font>';
    }
    $ret = $val['content'].' = '.base_convert($val['content'], 10, 2).'b -> ';
    return $ret.'<font color="red">UNKNOWN STATUS - please see http://code.google.com/p/drobowebdashboard/wiki/ESATMUpdate#ESATMUpdate/mStatus</font>';
  }

  function capacity($val){
    // Cannot just divide by 1024*1024*1024 because drobo's php does not support large numbers (above 2 Gb) : just remove the last digits for display
    $gb = substr($val['content'],0,-9);
    $tb = substr($val['content'],0,-12);
    return $gb.' Gb / '.$tb.' Tb.';
    // return $val['content'].' bytes / '.$gb.' Gb / '.$tb.' Tb.';
  }

  function emailConfig($val){
    if ($val['content'] == 0){ return "Email alerts <b>not</b> enabled ";} elseif($val['content'] == 1) { return "Email alerts enabled ";} else {return 'Email alert : <b>unknown</b> status';}
  }

  function mFirmwareFeatureStates($val){
    if($val['content'] == 6){ return '<b>Single redundancy</b>'; } elseif($val['content'] == 7){return '<b>Double redundancy</b>';}else{return '<b>Redundancy status unknown !</b>';}
  }

  function slotmStatus($val){
    //$ret = $val['content'].' = '.base_convert($val['content'], 10, 2).'b -> ';
    $ret="";
    switch ($val['content']){
      case 1:
        return $ret.'<font color="white" style="BACKGROUND-COLOR: red">Solid red light (add/upgrade drive immediately)</font>';
      case 2:
        return $ret.'<font color="black" style="BACKGROUND-COLOR: yellow">Solid yellow light (add/upgrade drive soon)</font>';
      case 3:
        return $ret.'<font color="white" style="BACKGROUND-COLOR: green">Solid green light (everything ok)</font>';
      case 4:
        return $ret.'<font color="yellow" style="BACKGROUND-COLOR: green">Blinking green/yellow light (relayout)</font>';
      case 128:
        return $ret.'<font color="gray">No light (empty slot)</font>';
      case 134:
        return $ret.'<font color="black" style="BACKGROUND-COLOR: red">Blinking red light (defective drive, replace immediately)</font>';
    }
    return $ret.'<font color="red">UNKNOWN STATUS - please see http://code.google.com/p/drobowebdashboard/wiki/ESATMUpdate#ESATMUpdate/mSlotsExp/n/mStatus</font>';
  }

/****************************** End of sub Functions for Drobo Status Analysis *********************************************/

/*********************************** Drobo Status Analysis and display ************************************************/

  function displayCurrentStatus($cs, $fullOrNot = 1){
    $params = array('mESAUpdateSignature1' => array('?', 'simpleString'),
             'mESAUpdateVersion2' => array('?', 'simpleString'),
             'mESAUpdateSize3' => array('?', 'simpleString'),
             'mESAID4' => array('? - serial number', 'simpleString'),
             'mSerial5' => array('Serial number of the device ', 'simpleString'),
             'mName6' => array('Name of the device', 'simpleString'),
             'mVersion7' => array('Firmware version', 'simpleString'),
             'mReleaseDate8' => array('Release date of the firmware ', 'simpleString'),
             'mArch9' => array('Hardware architecture', 'simpleString'),
             'mFirmwareFeatures10' => array('?', 'simpleString'),
             'mFirmwareTestFeatures11' => array('?', 'simpleString'),
             'mFirmwareTestState12' => array('?', 'simpleString'),
             'mFirmwareTestValue13' => array('?', 'simpleString'),
             'mStatus14' => array('Overall status of the device', 'mStatus'),
             'mRelayoutCount15' => array('? (Speculation: number of relayouts in the device\'s history)', 'simpleString'),
             'mTotalCapacityProtected16' => array('Total capacity', 'capacity'),
             'mUsedCapacityProtected17' => array('Used capacity', 'capacity'),
             'mFreeCapacityProtected18' => array('Free capacity', 'capacity'),
             'mTotalCapacityUnprotected19' => array('?', 'capacity'),
             'mUsedCapacityOS20' => array('?', 'capacity'),
             'mYellowThreshold21' => array('Threshold of yellow warning about capacity, in 100th of a percent', 'simpleString'),
             'mRedThreshold22' => array('Threshold of red warning about capacity, in 100th of a percent', 'simpleString'),
             'mUseUnprotectedCapacity23' => array('?', 'capacity'),
             'mRealTimeIntegrityChecking24' => array('?', 'simpleString'),
             'mStoredFirmwareTestState25' => array('?', 'simpleString'),
             'mStoredFirmwareTestValue26' => array('?', 'simpleString'),
             'mDiskPackID27' => array('?', 'simpleString'),
             'mDroboName28' => array('?', 'simpleString'),
             'mConnectionType29' => array('?', 'simpleString'),
             'mSlotCountExp30' => array('Maximum number of drive bays (slots)', 'simpleString'), 
             'mSlotsExp31' => array('Container tag for slots status', 'simpleString'),  // SUBFUNCTION
             'mLUNUpdates32' => array('Container tag for?', 'simpleString'), //SUBFUNCTION
             'mFirmwareFeatureStates33' => array('?', 'mFirmwareFeatureStates'),
             'mLUNCount34' => array('?', 'simpleString'),
             'mMaxLUNs35' => array('?', 'simpleString'),
             'mSledName36' => array('?', 'simpleString'),
             'mSledVersion37' => array('?', 'simpleString'),
             'mShareCount38' => array('?', 'simpleString'),
             'mShareInfo39' => array('?', 'simpleString'),
             'mSledStatus40' => array('?', 'simpleString'),
             'mSledSerial41' => array('?', 'simpleString'),
             'mDiskPackStatus42' => array('?', 'simpleString'),
             'DNASStatus43' => array('?', 'simpleString'),
             'DNASConfigVersion44' => array('?', 'simpleString'),
             'DNASDroboAppsShared45' => array('? (Speculation: indicates whether DroboApps are enabled)', 'simpleString'),
             'DNASDiskPackId46' => array('? (Speculation: unique ID for the disk pack) ', 'simpleString'),
             'DNASFeatureTable47' => array('?', 'simpleString'),
             'DNASEmailConfigEnabled48' => array('Indicates the state of email alerts', 'emailConfig'),
             'content' => array('?', 'simpleString')
);
    $cs = $cs['ESATMUpdate1'];

    //Display slots
    $slots = $cs['mSlotsExp31'];
    $index = 0;
    if ($fullOrNot){
      echo "<center><table border = \"1\">\n<tr><td><b>Slot number</b></td><td><b>Status</b></td><td><b>Capacity</b></td><td>Make</td><td>Model</td><td>ESAID</td></tr>\n";
    }else{
      echo "<center><table border = \"1\">\n<tr><td><b>Slot number</b></td><td><b>Status</b></td><td><b>Capacity</b></td></tr>\n";
    }
    while($slots['n'.$index.($index+1)]){
      $slot = $slots['n'.$index.($index+1)];
      if ($fullOrNot){
        echo '<tr><td>'.simpleString($slot[mSlotNumber1]).'</td><td>'.slotmStatus($slot[mStatus2]).'</td><td>'.capacity($slot[mPhysicalCapacity6]).'</td><td>'.simpleString($slot[mMake4]).'</td><td>'.simpleString($slot[mModel5]).'</td><td>'.simpleString($slot[mESAID3])."</td></tr>\n";
      }else{
        echo '<tr><td>'.simpleString($slot[mSlotNumber1]).'</td><td>'.slotmStatus($slot[mStatus2]).'</td><td>'.capacity($slot[mPhysicalCapacity6])."</td></tr>\n";
      }
      $index = $index+1;
    }
    echo "</table></center>\n";

    if ($fullOrNot){
      // Display all parameters
      echo "<br><table border = \"1\">\n<tr><td>VarName</td><td>Comment</td><td>Value</td></tr>\n";
      foreach ($cs as $key => $val) {
        if (!$params[$key][1]){
          echo "<br><br><br>UNKNOWN param for $key !<br><br><br>";
        }
        echo '<tr>';
        echo "<td>$key</td><td>".$params[$key][0].'</td><td>'.$params[$key][1]($val).'</td>';
        echo "</tr>\n";
      }
      echo "</table>\n";

    }else{ // Just display the comment and value for the most usefull items

      echo "<br><center><table border = \"1\">\n";
      $keys = array('mName6', 'mStatus14', 'mTotalCapacityProtected16', 'mUsedCapacityProtected17', 'mFreeCapacityProtected18', 'mVersion7', 'DNASEmailConfigEnabled48');
      foreach ($keys as $key){
        echo '<tr><td>'.$params[$key][0].'</td><td>'.$params[$key][1]($cs[$key])."</td></tr>\n";
      }
      echo "</table></center>\n";
    }

  }

/*********************************** End of Drobo Status Analysis and display ************************************************/

/****************************** THE PAGE CONTENT **********************************************************************/

  // Parameter : full details or not ?
  if($_GET["full"]){ $full = 1;} else {$full = 0;}
  if ($full == 0){echo '<h3><a href="droboStatus.php?full=1">Display full details</a></h3>';}else{echo '<h3><a href="droboStatus.php">Display summary</a></h3>';}

  // Get the shares configuration
  exec("cat /mnt/DroboFS/System/DNAS/configs/shares.conf", $output);
  // Make one string from it
  $output = implode(" ", $output);
  // Parse and display it
  $nasconf = parseBasicXML($output);
  displayNASconf($nasconf);

  // Get the status from port 5000 and display it
  echo '<h2><center>Drobo Current Status</center></h2>';
  displayCurrentStatus(getCurrentStatus(),$full);

  ?> 
 </body>
</html>
This entry was posted in TechTips. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

*