...
Error text can be expanded with runtime values. To use this functionality, the rule must return a tuple containing a Boolean and a list of arguments (see example below)
Configuration
Rules are categorized firstly by event type (e.g. AAI event, POA event), then by entity type or pre-configured index. The rules are defined in one or more text files using the suffix *.groovy which are stored by event type. For example, POMBA deployments use event type "poa-event" and rules for this event are stored in [validation-base-dir]/bundleconfig/etc/rules/poa-event/
...
- a rule name that is unique within the validation service
- a set of named attributes (arguments)
- a validation expression, written in the Groovy programming language, that uses the named attributes and evaluates to produce a Boolean value
- meta-data which will appear in the violation details, including
- category
- severity
- errorText
Examples
Simple Rule
...
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
Simpleentity Rule{ Collapse sourcerule {name 'POA-EVENT' indexing name{ indices 'vnfdefault-namerules' } category validation { 'INVALID_NAME' useRule description{ 'Invalid naming convention' name errorText'vnf-name' 'Invalid name - attribute does not match xxxxxnnnvbc (where x = alphanumeric and n = numeric)' severity 'MINOR'attributes 'context-list.sdc.vfList[*].name' } } } rule { name attributes 'vnf-name' validatecategory 'name != null && name.matches("[a-z,0-9]{5}[0-9]{3}vbc")' } |
Complex Rule Example
The following example defines a rule that :
- accepts two attributes
- uses expandable error text
- uses a triple-quoted
validate
section to allow multiple lines - defines multiple closures
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
rule { name 'NDCB-AAI-attribute-comparison' category 'Attribute Mismatch' description 'Verify that all attributes in Network-Discovery are the same as in AAI' errorText 'Error found with attribute "{0}"; value "{1}" does not exist in Network-Discovery' severity 'ERROR' attributes 'ndcbItems', 'aaiItems' validate ''' Closure<java.util.Map> getAttributes = { parsedData ->'INVALID_NAME' description 'Invalid naming convention' errorText 'Invalid name - attribute does not match xxxxxnnnvbc (where x = alphanumeric and n = numeric)' severity 'MINOR' attributes 'name' validate 'name != null && name.matches("[a-z,0-9]{5}[0-9]{3}vbc")' } |
Complex Rule
The following example defines a rule that :
- accepts two attributes
- uses expandable error text
- uses a triple-quoted
validate
section to allow multiple lines - defines multiple closures
Code Block | ||||
---|---|---|---|---|
| ||||
entity { name 'POA-EVENT' indexing { indices 'default-rules' } validation { useRule { name 'NDCB-AAI-attribute-comparison' java.util.Map attributeMap = new java.util.HashMap()attributes 'context-list.ndcb.vfList[*].vfModuleList[*]', 'context-list.aai.vfList[*].vfModuleList[*]' } } } def isAttributeDataQualityOk =rule { attribute -> name 'NDCB-AAI-attribute-comparison' category attribute.findResult{ k, v -> if(k.equals("dataQuality") ) {return v.get("status")}}.equals("ok") } def addToMap = { attrKey, attrValue -> java.util.Set values = attributeMap.get("$attrKey") 'Attribute Mismatch' description 'Verify that all attributes in Network-Discovery are the same as in AAI' errorText 'Error found with attribute "{0}"; value "{1}" does not exist in Network-Discovery' severity 'ERROR' attributes 'ndcbItems', 'aaiItems' validate ''' if(values == null) Closure<java.util.Map> getAttributes = { parsedData -> valuesjava.util.Map attributeMap = new java.util.HashSetHashMap() def isAttributeDataQualityOk attributeMap.put("$attrKey", values) = { attribute -> } attribute.findResult{ k, v values.add("$attrValue") -> if(k.equals("dataQuality") ) {return v.get("status")}}.equals("ok") } def addAttributeToMapaddToMap = { attrKey, attributeattrValue -> java.util.Set values = if(isAttributeDataQualityOk(attribute)attributeMap.get("$attrKey") { if(values == null) String{ key, value values = new attribute.each { k, v ->java.util.HashSet() attributeMap.put("$attrKey", values) if(k.equals("name")) {key = "$v"} } if(kvalues.equalsadd("value$attrValue")) {value = "$v"} } } def addAttributeToMap = { attribute -> addToMap("$key", "$value") if(isAttributeDataQualityOk(attribute)) { } String key, value } def processKeyValue =attribute.each { keyk, valuev -> if(value instanceof java.util.ArrayListk.equals("name")) {key = "$v"} if(keyk.equals("attributeListvalue")) {value = "$v"} } value.each { addToMap("$key", "$value") addAttributeToMap(it) } } def processKeyValue = { }key, value -> } else if(!(value instanceof groovyjava.json.internal.LazyMaputil.ArrayList) { if(key.equals("attributeList")) { // only add key-value attributes, skip the restvalue.each { addToMap("$key", "$value")addAttributeToMap(it) } } if(parsedData instanceof java.util.ArrayList) { parsedData.each { it.each { key, value -> processKeyValue(key, value) } } } else { parsedData.each { key, value -> processKeyValue(key, value) } } return attributeMap } def slurper = new groovy.json.JsonSlurper() java.util.Map ndcb = getAttributes(slurper.parseText(ndcbItems.toString())) java.util.Map aai = getAttributes(slurper.parseText(aaiItems.toString())) } else if(!(value instanceof groovy.json.internal.LazyMap)) { // only add key-value attributes, skip the rest addToMap("$key", "$value") } } if(parsedData instanceof java.util.ArrayList) { parsedData.each { it.each { key, value -> processKeyValue(key, value) } } } else { parsedData.each { key, value -> processKeyValue(key, value) } } return attributeMap } def slurper = new groovy.json.JsonSlurper() java.util.Map ndcb = getAttributes(slurper.parseText(ndcbItems.toString())) java.util.Map aai = getAttributes(slurper.parseText(aaiItems.toString())) boolean result = true List<String> details = new ArrayList<>(); ndcb.any{ ndcbKey, ndcbValueList -> def aaiValueList = aai.get("$ndcbKey") aaiValueList.each{ aaiValue -> if(!ndcbValueList.any{ it == "$aaiValue" }) { result = false details.add("$ndcbKey") details.add("$aaiValue") } } if(result == false) { // break out of 'any' loop return true } } return new Tuple2(result, details) ''' } |
Data-Dictionary rule
The following example defines a rule that uses the data-dictionary interfaced defined here.
By default, the URI template is configured as "/commonModelElements/{0}~{1}~1.0/validateInstance"
With the arguments used for calling validate() below, the resulting URL would be: [ddict-host:port]/commonModelElements/instance~vfModuleNetworkType~1.0/validateInstance
And the body would be: {"type" : "some-value"
}
Code Block | ||||
---|---|---|---|---|
| ||||
entity { name 'POA-EVENT' indexing { indices 'default-rules' } validation { useRule { name 'Data-Dictionary validate VF type' attributes 'context-list.ndcb.vfList[*].vfModuleList[*].networkList[*].type' } } } rule { name 'Data-Dictionary validate VF type' category 'INVALID_VALUE' description 'Validate all VF type values against data-dictionary' errorText 'VF type [{0}] failed data-dictionary validation: {1}' severity 'ERROR' attributes 'typeList' validate ''' boolean resultsuccess = true List<String> details = new ArrayList<>(); ndcbtypeList.any { ndcbKey, ndcbValueList -> def aaiValueList = aai.get("$ndcbKey"if(!success) { aaiValueList.each{ aaiValue -> if(!ndcbValueList.any{ it == "$aaiValue" }) {// break out of 'any' loop result =return false } details.add("$ndcbKey") def result = org.onap.aai.validation.ruledriven.rule.builtin.DataDictionary.validate("instance", details.add("$aaiValue"vfModuleNetworkType", "type", "$it") } if(!result.isEmpty()) { } success = false if(result == false) { // break out of 'any' loopdetails.add("$it") return truedetails.add("$result") } } return new Tuple2(resultsuccess, details) ''' } |
Entity Configuration
The entity
configuration element defines which rules are applied to a specific entity type. The configuration is comprised of the following properties:
type / indexing | if using type, the value is a unique name of the type of entity if using indexing, the value is a list of runtime indices extracted from the event; or a pre-configured default value |
validation | the set of rules to apply to this entity and (for each rule) the attributes to be read from the entity (in order to create the rule's arguments) |
The validation comprises a set of useRule
elements. Each specifies a rule to be applied to the entity.
...
This element is repeated within the validation
element as illustrated in the example below. The following properties may be defined:
name | (Mandatory) | The name of the rule to apply to this entity. This rule must be defined within a.groovy file stored in the rules directory.The referenced rule does not need to be defined in the same file as the entity . |
attributes | (Optional) | A comma-separated list of attribute(s) to extract from the entity. Each list item is a string storing a (JSON) path within the entity. The path is used to extract a value (or set of values) to be passed to the rule as an argument. Therefore the number of attributes defined must match with the number of attributes defined by the rule.If the attribute specifiers are omitted then the attribute paths are implicitly taken from the rule definition. |
Example entity
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
entity { type 'complex' validation { useRule { name 'CLLI' attributes 'physical-location-id' } useRule {name 'complex is related to 1 oam-network' } useRule { name 'if a customer is related to an oam-network then oam-network.network-name must match naming convention' attributes 'relationship-list.relationship[*]' } } } |
...
Rules are determined based on the incoming event's values
model-version-id | model-invariant-id | Rule |
---|---|---|
version-1 | invariant-1 | rule-1 |
version-2 | invariant-2 | rule-2 |
version-1 | invariant-2 | default-rule |
version-3 | invariant-3 | default-rule |
Configuration
The event type and attributes must be pre-configured. The attributes are defined as JSON path expression within an event entity.
...