r2594 - eval_expression(): $silent arg to hide error messages
authorDenis Ovsienko <infrastation@yandex.ru>
Thu, 2 Apr 2009 13:44:25 +0000 (13:44 +0000)
committerDenis Ovsienko <infrastation@yandex.ru>
Thu, 2 Apr 2009 13:44:25 +0000 (13:44 +0000)
 - ibid: return NULL on error and pass returned NULL upwards w/o converting to FALSE
 - getSentencesFromLexems(): rename to getParseTreeFromLexems()
 - getParseTreeFromLexems(): move ACK/NAK decision making to a more general spotPayload()
 - getLexemsFromRackCode(): rename to getLexemsFromRawText()
 - getLexemsFromRawText(): add mock character for better scanner syncing
 - lexError3(): tell scanner state in error message
 - filterEntityList(): moved most of the code into new buildPredicateTable() and new judgeEntity()
 - getIPv4LBList(): new function makes use of the new code to enhance renderVServiceLBForm() and renderRSPoolLBForm()
 - trigger_lvsconfig(): replaced with trigger_isloadbalancer(), which uses IPV4LB_LISTSRC
 - renamed 'LVS config' tab to 'keepalived.conf'

ChangeLog
inc/code.php
inc/functions.php
inc/interface.php
inc/navigation.php
inc/triggers.php

index 83496f9..588a222 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -6,6 +6,7 @@
        new feature: external "httpd" user authentication
        new feature: validator in RackCode editor
        new feature: vendor sieve for stickers
+       new feature: RackCode expressions as source for load balancer lists
        update: display row name when listing objects. closes ticket 16 (by Aaron)
        update: ability to manage rows from the Rackspace page in addition to the Dictionary (by Aaron)
        update: IPv4 calculations were optimized for better speed
index 00d9606..9dc5e48 100644 (file)
@@ -3,24 +3,6 @@
  * This file implements lexical scanner and syntax analyzer for the RackCode
  * access configuration language.
  *
- * The language consists of the following lexems:
- *
- * LEX_LBRACE
- * LEX_RBRACE
- * LEX_DECISION
- * LEX_DEFINE
- * LEX_BOOLCONST
- * LEX_NOT
- * LEX_TAG
- * LEX_AUTOTAG
- * LEX_PREDICATE
- * LEX_BOOLOP
- * LEX_CONTEXT
- * LEX_CLEAR
- * LEX_INSERT
- * LEX_REMOVE
- * LEX_ON
- *
  */
 
 // Complain about martian char.
@@ -49,7 +31,7 @@ function lexError3 ($state, $ln = 'N/A')
        return array
        (
                'result' => 'NAK',
-               'load' => "Lexical error during '${state}' near line ${ln}"
+               'load' => "Lexical error in scanner state '${state}' near line ${ln}"
        );
 }
 
@@ -62,10 +44,31 @@ function lexError4 ($s, $ln = 'N/A')
        );
 }
 
-// Produce a list of lexems from the given text. Possible lexems are:
-function getLexemsFromRackCode ($text)
+/* Produce a list of lexems from the given text. Possible lexems are:
+ *
+ * LEX_LBRACE
+ * LEX_RBRACE
+ * LEX_DECISION
+ * LEX_DEFINE
+ * LEX_BOOLCONST
+ * LEX_NOT
+ * LEX_TAG
+ * LEX_AUTOTAG
+ * LEX_PREDICATE
+ * LEX_BOOLOP
+ * LEX_CONTEXT
+ * LEX_CLEAR
+ * LEX_INSERT
+ * LEX_REMOVE
+ * LEX_ON
+ *
+ */
+function getLexemsFromRawText ($text)
 {
        $ret = array();
+       // Add a mock character to aid in synchronization with otherwise correct,
+       // but short or odd-terminated final lines.
+       $text .= ' ';
        $textlen = mb_strlen ($text);
        $lineno = 1;
        $state = "ESOTSM";
@@ -248,6 +251,26 @@ function getLexemsFromRackCode ($text)
        return array ('result' => 'ACK', 'load' => $ret);
 }
 
+// Take a parse tree and figure out if it is a valid payload or not.
+// Depending on that return either NULL or an array filled with the load
+// of that expression.
+function spotPayload ($text, $reqtype = 'SYNT_CODETEXT')
+{
+       $lex = getLexemsFromRawText ($text);
+       if ($lex['result'] != 'ACK')
+               return $lex;
+       $stack = getParseTreeFromLexems ($lex['load']);
+       // The only possible way to "accept" is to have sole starting
+       // nonterminal on the stack (and it must be of the requested class).
+       if (count ($stack) == 1 and $stack[0]['type'] == $reqtype)
+               return array ('result' => 'ACK', 'load' => $stack[0]['load']);
+       // No luck. Prepare to complain.
+       if ($lineno = locateSyntaxError ($stack))
+               return array ('result' => 'NAK', 'load' => "Syntax error for type '${reqtype}' near line ${lineno}");
+       // HCF!
+       return array ('result' => 'NAK', 'load' => "Syntax error for type '${reqtype}': empty text");
+}
+
 // Parse the given lexems stream into a list of RackCode sentences. Each such
 // sentence is a syntax tree, suitable for tag sequence evaluation. The final
 // parse tree may contain the following nodes:
@@ -264,7 +287,11 @@ function getLexemsFromRackCode ($text)
 //
 // After parsing the input successfully a list of SYNT_GRANT and SYNT_DEFINITION
 // trees is returned.
-function getSentencesFromLexems ($lexems)
+//
+// P.S. The above is true for input, which is a complete and correct RackCode text.
+// Other inputs may produce different combinations of lex/synt structures. Calling
+// function must check the parse tree itself.
+function getParseTreeFromLexems ($lexems)
 {
        $stack = array(); // subject to array_push() and array_pop()
        $done = 0; // $lexems[$done] is the next item in the tape
@@ -650,26 +677,21 @@ function getSentencesFromLexems ($lexems)
                }
                // The fact we execute here means, that no reduction or early shift
                // has been done. The only way to enter another iteration is to "shift"
-               // more, if possible. If the tape is empty, we are facing the
-               // "accept"/"reject" dilemma. The only possible way to "accept" is to
-               // have sole starting nonterminal on the stack (SYNT_CODETEXT).
+               // more, if possible. If shifting isn't possible due to empty input tape,
+               // we are facing the final "accept"/"reject" dilemma. In this case our
+               // work is done here, so return the whole stack to the calling function
+               // to decide depending on what it is expecting.
                if ($done < $todo)
                {
                        array_push ($stack, $lexems[$done++]);
                        continue;
                }
                // The moment of truth.
-               if (count ($stack) == 1 and $stack[0]['type'] == 'SYNT_CODETEXT')
-                       return array ('result' => 'ACK', 'load' => $stack[0]['load']);
-               // No luck. Prepare to complain.
-               if ($lineno = locateSyntaxError ($stack))
-                       return array ('result' => 'NAK', 'load' => 'Syntax error near line ' . $lineno);
-               // HCF
-               return array ('result' => 'NAK', 'load' => 'Syntax error: empty text');
+               return $stack;
        }
 }
 
-function eval_expression ($expr, $tagchain, $ptable)
+function eval_expression ($expr, $tagchain, $ptable, $silent = FALSE)
 {
        switch ($expr['type'])
        {
@@ -684,8 +706,9 @@ function eval_expression ($expr, $tagchain, $ptable)
                        $pname = $expr['load'];
                        if (!isset ($ptable[$pname]))
                        {
-                               showError ("Predicate '${pname}' is referenced before declaration", __FUNCTION__);
-                               return;
+                               if (!$silent)
+                                       showError ("Predicate '${pname}' is referenced before declaration", __FUNCTION__);
+                               return NULL;
                        }
                        return eval_expression ($ptable[$pname], $tagchain, $ptable);
                case 'LEX_BOOLCONST': // Evaluate a boolean constant.
@@ -696,11 +719,18 @@ function eval_expression ($expr, $tagchain, $ptable)
                                case 'false':
                                        return FALSE;
                                default:
-                                       showError ("Could not parse a boolean constant with value '${expr['load']}'", __FUNCTION__);
-                                       return; // should failure be harder?
+                                       if (!$silent)
+                                               showError ("Could not parse a boolean constant with value '${expr['load']}'", __FUNCTION__);
+                                       return NULL; // should failure be harder?
                        }
                case 'SYNT_NOTEXPR':
-                       return !eval_expression ($expr['load'], $tagchain, $ptable);
+                       $tmp = eval_expression ($expr['load'], $tagchain, $ptable);
+                       if ($tmp === TRUE)
+                               return FALSE;
+                       elseif ($tmp === FALSE)
+                               return TRUE;
+                       else
+                               return $tmp;
                case 'SYNT_BOOLOP':
                        $leftresult = eval_expression ($expr['left'], $tagchain, $ptable);
                        switch ($expr['subtype'])
@@ -714,11 +744,14 @@ function eval_expression ($expr, $tagchain, $ptable)
                                                return FALSE; // early failure
                                        return eval_expression ($expr['right'], $tagchain, $ptable);
                                default:
-                                       showError ("Cannot evaluate unknown boolean operation '${boolop['subtype']}'");
-                                       return;
+                                       if (!$silent)
+                                               showError ("Cannot evaluate unknown boolean operation '${boolop['subtype']}'");
+                                       return NULL;
                        }
                default:
-                       showError ("Evaluation error, cannot process expression type '${expr['type']}'", __FUNCTION__);
+                       if (!$silent)
+                               showError ("Evaluation error, cannot process expression type '${expr['type']}'", __FUNCTION__);
+                       return NULL;
                        break;
        }
 }
@@ -807,15 +840,14 @@ function gotClearanceForTagChain ($const_base)
        return FALSE;
 }
 
+// Top-level wrapper for most of the code in this file. Get a text, return a parse tree
+// (or error message).
 function getRackCode ($text)
 {
        if (!mb_strlen ($text))
                return array ('result' => 'NAK', 'load' => 'The RackCode text was found empty in ' . __FUNCTION__);
        $text = str_replace ("\r", '', $text) . "\n";
-       $lex = getLexemsFromRackCode ($text);
-       if ($lex['result'] != 'ACK')
-               return $lex;
-       $synt = getSentencesFromLexems ($lex['load']);
+       $synt = spotPayload ($text, 'SYNT_RACKCODE');
        if ($synt['result'] != 'ACK')
                return $synt;
        // An empty sentence list is semantically valid, yet senseless,
index 0da7676..5ef2e3d 100644 (file)
@@ -1903,28 +1903,63 @@ function dos2unix ($text)
        return str_replace ("\r\n", "\n", $text);
 }
 
-// Take a list of records and filter against given RackCode expression.
-// Return a new list with the items, for which the expression returned TRUE.
-function filterEntityList ($list_in, $realm, $expression)
+function buildPredicateTable ($parsetree)
 {
-       global $rackCode;
-       $list_out = $ptable = array();
-       foreach ($rackCode as $sentence)
+       $ret = array();
+       foreach ($parsetree as $sentence)
                if ($sentence['type'] == 'SYNT_DEFINITION')
-                       $ptable[$sentence['term']] = $sentence['definition'];
+                       $ret[$sentence['term']] = $sentence['definition'];
        // Now we have predicate table filled in with the latest definitions of each
        // particular predicate met. This isn't as chik, as on-the-fly predicate
        // overloading during allow/deny scan, but quite sufficient for this task.
+       return $ret;
+}
+
+// Take a list of records and filter against given RackCode expression. Return
+// the original list intact, if there was no filter requested, but return an
+// empty list, if there was an error.
+function filterEntityList ($list_in, $realm, $expression = array())
+{
+       if (!count ($expression))
+               return $list_in;
+       global $rackCode;
+       $list_out = array();
        foreach ($list_in as $item_key => $item_value)
-       {
-               $item_explicit_tags = loadEntityTags ($realm, $item_key);
-               $item_implicit_tags = getImplicitTags ($item_explicit_tags);
-               $item_autotags = generateEntityAutoTags ($realm, $item_key);
-               $item_tagchain = array_merge ($item_explicit_tags, $item_implicit_tags, $item_autotags);
-               if (eval_expression ($expression, $item_tagchain, $ptable))
+               if (TRUE === judgeEntity ($realm, $item_key, $expression, buildPredicateTable ($rackCode)))
                        $list_out[$item_key] = $item_value;
-       }
        return $list_out;
 }
 
+// Tell, if the given expression is true for the given entity.
+function judgeEntity ($realm, $id, $expression, $ptable)
+{
+       $item_explicit_tags = loadEntityTags ($realm, $id);
+       return eval_expression
+       (
+               $expression,
+               array_merge
+               (
+                       $item_explicit_tags,
+                       getImplicitTags ($item_explicit_tags),
+                       generateEntityAutoTags ($realm, $id)
+               ),
+               $ptable,
+               TRUE
+       );
+}
+
+function getIPv4LBList()
+{
+       $ret = getNarrowObjectList (array_keys (readChapter ('RackObjectType')));
+       $filtertext = getConfigVar ('IPV4LB_LISTSRC');
+       if (strlen ($filtertext))
+       {
+               $filter = spotPayload ($filtertext, 'SYNT_EXPR');
+               if ($filter['result'] != 'ACK')
+                       return array();
+               $ret = filterEntityList ($ret, 'object', $filter['load']);
+       }
+       return $ret;
+}
+
 ?>
index 40e4f78..f5dc054 100644 (file)
@@ -4335,7 +4335,7 @@ function renderRSPoolLBForm ($pool_id = 0)
        echo "<table cellspacing=0 cellpadding=5 align=center class=widetable>\n";
        printOpFormIntro ('addLB');
        echo "<tr valign=top><th>LB / VS</th><td class=tdleft>";
-       printSelect (getNarrowObjectList (explode (',', getConfigVar ('NATV4_PERFORMERS'))), 'object_id', NULL, 1);
+       printSelect (getIPv4LBList(), 'object_id', NULL, 1);
        printSelect ($vs_list, 'vs_id', NULL, 2);
        echo "</td><td>";
        printImageHREF ('add', 'Configure LB', TRUE, 5);
@@ -4386,7 +4386,7 @@ function renderVServiceLBForm ($vs_id = 0)
        echo "<table cellspacing=0 cellpadding=5 align=center class=widetable>\n";
        printOpFormIntro ('addLB');
        echo "<tr valign=top><th>LB / RS pool</th><td class=tdleft>";
-       printSelect (getNarrowObjectList (explode (',', getConfigVar ('NATV4_PERFORMERS'))), 'object_id', NULL, 1);
+       printSelect (getIPv4LBList(), 'object_id', NULL, 1);
        printSelect ($rsplist, 'pool_id', NULL, 2);
        echo "</td><td>";
        printImageHREF ('add', 'Configure LB', TRUE, 5);
index 443258c..1c91428 100644 (file)
@@ -125,7 +125,7 @@ $tab['object']['nat4'] = 'NATv4';
 $tab['object']['livevlans'] = 'Live VLANs';
 $tab['object']['snmpportfinder'] = 'SNMP port finder';
 $tab['object']['editrspvs'] = 'RS pools';
-$tab['object']['lvsconfig'] = 'LVS config';
+$tab['object']['lvsconfig'] = 'keepalived.conf';
 $tab['object']['autoports'] = 'AutoPorts';
 $tab['object']['tags'] = 'Tags';
 $tab['object']['files'] = 'Files';
@@ -148,8 +148,8 @@ $trigger['object']['ipv4'] = 'trigger_ipv4';
 $trigger['object']['nat4'] = 'trigger_natv4';
 $trigger['object']['livevlans'] = 'trigger_livevlans';
 $trigger['object']['snmpportfinder'] = 'trigger_snmpportfinder';
-$trigger['object']['editrspvs'] = 'trigger_natv4';
-$trigger['object']['lvsconfig'] = 'trigger_lvsconfig';
+$trigger['object']['editrspvs'] = 'trigger_isloadbalancer';
+$trigger['object']['lvsconfig'] = 'trigger_isloadbalancer';
 $trigger['object']['autoports'] = 'trigger_autoports';
 $trigger['object']['tags'] = 'trigger_tags';
 $ophandler['object']['rackspace']['updateObjectAllocation'] = 'updateObjectAllocation';
index dac51bf..28d3d99 100644 (file)
@@ -59,10 +59,18 @@ function trigger_emptyRackspace ()
        return (count (getRackRows()) == 0);
 }
 
-function trigger_lvsconfig ()
+function trigger_isloadbalancer ()
 {
        assertUIntArg ('object_id', __FUNCTION__);
-       return count (getRSPoolsForObject ($_REQUEST['object_id'])) > 0;
+       // Compile the same text, which was used for making the decision.
+       $filtertext = getConfigVar ('IPV4LB_LISTSRC');
+       if (!strlen ($filtertext))
+               return TRUE; // no restriction, always show the tab
+       $filter = spotPayload ($filtertext, 'SYNT_EXPR');
+       if ($filter['result'] != 'ACK')
+               return FALSE; // filter set, but cannot be used
+       global $rackCode;
+       return judgeEntity ('object', $_REQUEST['object_id'], $filter['load'], buildPredicateTable ($rackCode));
 }
 
 function trigger_ipv4 ()