0
votes

I was wondering if you can point me in the right direction. I would like to write a rules-based system to audit a set of machines -- for example

  • Check configuration parameters to see if they were set correctly
  • Check state of machines to see if they are at acceptable ranges

The thing that I am struggling with is coming up with a grammar for the auditing rules so that I don't have to code defrules. I created some simple auditing rules but I am struggling with coming up with a generic form.

Here is my current attempt.

First, I am currently representing configuration parameters and running state with a single template (for a lack of imagination, I called it audit-fact) so that my auditing language can work for both configuration and state. I use the domain slot to specify configuration vs. state.

(deftemplate audit-fact 
    (slot domain (allowed-values config state))     ;; config vs. state
    (slot machine)                                  ;; machine to audit 
    (slot name)                                     ;; parameter or state to check/audit
    (slot value)                                    ;; parameter value 
    (slot already-checked (default FALSE))
)   

Next, I wrote a program to read in and assert the machines configuration and state as audit-facts. The following are sample configuration and state audit-facts

(assert (audit-fact 
    (domain config) 
    (machine drl-a-15_0) 
    (name os-version) (value 5.5))
)

(assert (audit-fact 
    (domain state) 
    (machine drl-a-15_0) 
    (name rpm) (value 5023))
)

Here is my current attempt at an auditing grammar/language ---

(deftemplate rule
    (slot domain) 
    (slot name) 
    (slot constraint (default ?NONE) (allowed-values should-remain-at-default should-equal should-be-between ignore-if-ends-with))
    (multislot values)
    (multislot reasons) 
    (multislot references) 
    (slot criticality (allowed-values critical info suggestion warning))
    (slot already-checked (default FALSE))
)

The following rule is used to check if a state or parameter is within a certain range

(assert (rule 
    (domain state)
    (name rpm) 
    (constraint should-be-between) 
    (values 5000 5500) 
    (criticality critical) 
    (reasons "Low values could cause engine to stall. Prolonged high value could cause engine to heat up") )
)

Next, I used the following rule to check that a state or parameter is equal to a specific value

(assert (rule 
    (domain config)
    (name os-version) 
    (constraint should-equal) 
    (values 5.5) 
    (criticality critical) 
    (reasons "OS version must be at 5.5 – no other levels are currently certified.") )
)

The following rules implements the equal and between checks

(defrule rule-should-eq
    (rule (domain ?d) (name ?n) (constraint should-equal) (values ?cv) (reasons ?r))
    ?p <- (audit-fact (domain ?d) (name ?n) (value ?v) (already-checked FALSE))
=>
    (if (eq ?v ?cv)
        then 
            (printout t "checking " ?d ": " ?n ". Passed "  crlf)
        else 
            (printout t "checking " ?d “: “ ?n " should be set to " ?cv ". " ?r ". Warning" crlf)
    )       
    (modify ?p (already-checked TRUE))  
)       

(defrule rule-should-be-between
    (rule (domain ?d) (name ?n) (constraint should-be-between) (values ?cv-low ?cv-high) (reasons ?r))
    ?p <- (audit-fact (domain ?d) (name ?n) (value ?v) (already-checked FALSE))
=>
    (if (and (>= ?v ?cv-low) (<= ?v ?cv-high))
        then 
            (printout t "checking " ?n ". Passed "  crlf)
        else 
            (printout t "checking " ?n " should be between " ?cv-low " and " ?cv-high ". " ?r ". Warning" crlf)
    )       
    (modify ?p (already-checked TRUE))  
)       

Using the above, I can implement simple checks -- e.g., must-be-set-to, must-be-between, must-not-equal, must-remain-at-default-value, must-be-higher-than, must-be-lower-than, etc.

But I can't think of an easy way to make the grammar handle multiple conditions with ands or ors, etc.

Question ---

  • is there a paper that describes a design pattern for auditing rules like above. I am currently studying the wine.clp in examples (CLIPS).
  • should I give up and just use the simple grammar for simple auditing rules and just use defrules for anything complicated -- e.g., if config-A is set to X, then check five other configs to make sure they are also set correctly, and then assert a check of the state. Once asserted, then check the state to be within a certain range.

Thanks in advance.

Bernie

2
Side note: rule-should-eq can be omitted; use should-be-between with identical bounds. Sugar the error message. - laune
Have you investigated different approaches? If you have your machine data in XML, you could use facets and assertions in the XML Schema definition and a validating parser will do the trick. - You might also look into Java annotations for defining constraints on field values. IIRC, there's more than one library out there. - laune
Laune -- nice suggestion. I would like to evolve the auditor to go into more complex checks --- e.g., if configA=xx, then check that configB>n, configC=m, etc. or if configA is set, then check other configs are set a certain way and the state of the machines are within safety levels. Also, when I report the issues, I would like to print a sorted list -- e.g., by criticality, then parameter name. CLIPS or JESS seems to give me the flexibility. But I do like your suggestion though. Thanks. - Bernie Wong

2 Answers

1
votes

1) If statements on the RHS are a code smell: don't! 2) You don't need the already-checked flag since you aren't modifying these facts.

Rewriting the check against one machine attribute:

?p <- (Configuration (machine ?m)(param ?p)(value ?v))
(ConfigCheckRange (param ?p){?v < loBound || $v > hiBound})

(You may need another variants, e.g., ConfigCheckEnum is permitted values aren't in one interval.)

Checking parameter value combinations is more difficult since you have chosen to represent every parameter value by a separate fact. You'll have to

 ?p1 <- (Configuration (machine ?m)(param ?p1)(value ?v1))
 ?p2 <- (Configuration (machine ?m)(param ?p2)(value ?v2)
         {?p2 != ?p1))
(ConfigCheckCombi (param1 ?p1)(param2 ?p2)
      {lowBound1 <= ?v1 && ?v2 <= lowBound2}
      {?v2 < loBound2 || $v2 > hiBound2})

The right hand sides should merely register a violation in a (new) fact, let's call it Findings. One Findings contains the id to the machine and a list of (violated) ConfigCheck facts.

At the end (with a low-salience rule) you locate Findings facts and call the report method on it. (It may be more convenient to use some proper Java beans as facts.)

1
votes

Here's a method to evaluate arbitrary expressions from a generic rule:

CLIPS> 
(deffunction str-replace (?str ?rpl ?fnd)
   (if (eq ?fnd "") then (return ?str))
   (bind ?rv "")
   (bind ?i (str-index ?fnd ?str))
   (while ?i
      (bind ?rv (str-cat ?rv (sub-string 1 (- ?i 1) ?str) ?rpl))
      (bind ?str (sub-string (+ ?i (str-length ?fnd)) (str-length ?str) ?str))
      (bind ?i (str-index ?fnd ?str)))
   (bind ?rv (str-cat ?rv ?str)))
CLIPS>    
(deftemplate audit-fact 
    (slot domain)   
    (slot machine)                                   
    (slot name)                                   
    (slot value))
CLIPS>   
(deftemplate rule
    (slot domain) 
    (slot name) 
    (slot constraint)
    (multislot reasons) 
    (multislot references) 
    (slot criticality))
CLIPS> 
(deffacts initial
   (audit-fact 
      (domain config) 
      (machine drl-a-15_0) 
      (name os-version) 
      (value 5.4))
   (audit-fact 
      (domain state) 
      (machine drl-a-15_0) 
      (name rpm) 
      (value 5023))
   (rule 
      (domain state)
      (name rpm) 
      (constraint "(and (>= ?v 5000) (<= ?v 5500))")
      (criticality critical) 
      (reasons "Low values could cause engine to stall. Prolonged high value could cause engine to heat up."))
   (rule 
      (domain config)
      (name os-version) 
      (constraint "(eq ?v 5.5)")
      (criticality critical) 
      (reasons "OS version must be at 5.5 – no other levels are currently certified.")))
CLIPS> 
(defrule rule-check
   (rule (domain ?d) (name ?n) (constraint ?constraint) (reasons ?r))
   ?p <- (audit-fact (domain ?d) (name ?n) (value ?v))
   =>
   (bind ?constraint (str-replace ?constraint ?v "?v"))
   (if (eval ?constraint)
      then 
      (printout t "checking " ?d ": " ?n " Passed"  crlf)
      else 
      (printout t "checking " ?d ": " ?n " Warning : " ?r crlf)))
CLIPS> (reset)
CLIPS> (run)
checking config: os-version Warning : OS version must be at 5.5 – no other levels are currently certified.
checking state: rpm Passed
CLIPS>