Testvar Constraints
Testvar Constraints
This section describes a new testvar property constraints
for use in
calls to buddy::testvar_define
in a testpath’s configuration
dictionary (config.dic
):
buddy::testvar_define TESTVAR {
type { ... }
description {
...
}
constraints {
SCRIPT...
}
}
A constraint property is a Tcl script which is evaluated inside a special namespace which contains commands to make testvar validation easier. Constraints can be written to always apply, apply only if a testvar is defined/undefined, or apply only if a testvar does/doesn’t have certain values.
Testvar constraints are validated immediately after testvar type
validation occurs (see the type
property
in Testvar Properties).
Constraint validation is applied to each defined testvar group
(i.e. main
, wan2
, lan2
, etc.). For each defined group, the
constraint validation code iterates over each testvar defined in the
configuration dictionary. The testvar is skipped if the testvar is
not relevant in the current testvar group (see
the
relevant-groups testvar
property) or if the testvar does not have a constraints
property.
Otherwise, the value of the constraints
property is evaluated as Tcl
script. If the evaluated script raises an error, the error is caught
and the reason is displayed to the user and no further validation is
performed.
The following pseudo-code summarizes the above paragraph:
proc buddy::testvar_constraints_process { } {
foreach g [ defined_testvar_groups ] {
foreach tv [ testvars_defined_in_config_dic ] {
if { ![ group_is_relevant $tv $g ] } { continue }
if { ![ testvar_has_constraints_property $tv ] } { continue }
if { [ catch { eval $constraint_property } reason ] } {
puts "Error during configuration file test constraint validation"
puts "$reason"
}
}
}
}
Conditional Commands
Since they are commands like any other Tcl commands, conditional
commands can be combined in complex boolean expressions using Tcl’s
&&
and ||
operators. The example below shows how to use
conditions:
buddy::testvar_define TESTVAR TYPE {
constraints {
# true if lanIp exists and lanMask's value is 255.255.255.0
if { [ exists lanIp ] && [ values lanMask 255.255.255.0 ] } {
CONSTRAINTS...
}
# true if lanIp is undefined or lanIp's value is 192.168.1.1
if { ![ exists lanIp ] || [ values lanIp 192.168.1.1 ] } {
CONSTRAINTS...
}
}
}
The following commands can be used inside a constraint property to apply constraints based on certain conditions:
Returns true if a testvar is defined
exists TESTVAR
Returns true if a testvar exists and has certain values
values TESTVAR VALUES...
Constraint Commands
Constraint commands impose restrictions on testvars which must be true for the configuration file to be considered valid. A constraint can restrict a testvar to a specific data type, to be defined/undefined, or to have certain values.
Constraint commands raise a Tcl exception if their restrictions are
not met. The reason string accompanying the exception provides an
error message suitable for displaying to the user (using the
config_error
command, see below). If not caught earlier,
cdrouter-cli
will catch the exception, display an error message and
abort. The example below shows how to use constraint commands:
buddy::testvar_define TESTVAR TYPE {
constraints {
# fail if testvar acsAuth/acsCertPath exists
must_not_exist acsAuth acsCertPath
if { [ values lanMask 255.255.255.0 ] } {
# fail if lanIp is undefined
must_exist lanIp
# fail if lanIp is not 192.168.1.1 or 192.168.1.2
valid_values lanIp 192.168.1.1 192.168.1.2
}
}
}
Value tokens (VALUE
/VALUES...
) wrapped in curly braces are matched
as Tcl regular expressions using the regexp
command. For example:
if { [ values lanInterface {eth[13]} eth8 ] } {
invalid_values wanIspAssignIp "192.168.200.99" {192.168.2.*\.2}
}
if testvar lanInterface
is set to eth1
, eth3
or eth8
, the
invalid_values
constraint will be evaluated. Similarly, an error
will be thrown if the invalid_values
constraint is evaluated and the
testvar wanIspAssignIp
has a value of 192.168.200.99
,
192.168.200.112
or 192.168.20.2
.
Below are special commands which can be used inside a constraint property:
must_be
Fail if testvar is not one of the given data types.
must_be TESTVAR TYPES...
must_not_be
Fail if testvar is any of the given types.
must_not_be TESTVAR TYPES...
The must_be
and must_not_be
constraints use the same data type
names as the type
testvar property. For example, the following constraint fails if
dnsIp
is not an IPv4 address:
must_be dnsIp IPv4
must_exist
Fail if any testvar doesn’t exist
must_exist TESTVARS...
must_not_exist
Fail if any testvar exists
must_not_exist TESTVARS...
valid_values
Fail if testvar doesn’t have certain values
valid_values TESTVAR VALUES...
invalid_values
Fail if testvar has certain values
invalid_values TESTVAR VALUES...
unique_values
Ail if all testvar values are not unique
unique_values TESTVARS...
same_values
Fail if all testvar values are not the same
same_values TESTVARS...
unique_networks
Fail if all networks are not unique
unique_networks [ ADDR/MASK | ADDR/LEN | TESTVAR/TESTVAR | TESTVAR ]...
same_networks
Fail if all networks are not the same
same_networks [ ADDR/MASK | ADDR/LEN | TESTVAR/TESTVAR | TESTVAR ]...
file_readable
Fail if file does not exist or is not readable
file_readable FILE
file_max_size
Fail if file does not exist or is greater than given max. Max is
bytes by default, append k
/kb
, m
/mb
or g
/gb
for kilobytes,
megabytes or gigabytes, respectively
file_max_size FILE MAX
directory_exists
Fail if path does not exist or is not a directory
directory_exists PATH
valid_address
Fails if testvar’s value is not an address in the network defined by
the given testvar property. Property should take the form lan/wan [ address/prefix-len ]
. Also fails if any LAN network overlaps with
any WAN network and vice versa. If not given, PROPERTY-NAME
defaults to network
. If PROPERTY-NAME
is dynamic
, network spec
is instead taken from the optional DYNAMIC-SPEC
argument.
valid_address TESTVAR [ PROPERTY-NAME/dynamic ] [ DYNAMIC-SPEC ]
valid_addresses
Fails if testvar’s value is not an address in one the networks defined by the given testvar property.
valid_addresses TESTVAR [ PROPERTY-NAME / dynamic DYNAMIC-SPEC ]...
valid_mac
Fails if testvar’s MAC address value is not unique across all other
testvars with a type
property of mac-address
.
valid_mac TESTVAR
valid_root_cert
Fails if the certificate file given by testvar CERT
cannot be loaded
as a root certificate.
valid_root_cert CERT
valid_ssl_cipher
Fails if SSL cipher CIPHER
is not valid for the given SSL protocol
version SSL-PROTO
.
valid_ssl_cipher CIPHER SSL-PROTO
For example, the following constraint would fail if the SSL cipher
RC4-SHA
is not valid when using SSL version 3
:
valid_ssl_cipher RC4-SHA sslv3
See testvars acsCipherSuite
and acsSslVersion
for valid CIPHER
and SSL-PROTO
values, respectively.
valid_user_cert
Fails if the certificate file given by testvar CERT
cannot be loaded
as a user certificate. If given, the value of testvars PASSWORD
and
KEY
specify the password and key file to use, respectively.
valid_user_cert CERT [ PASSWORD ] [ KEY ]
Testvar Group Commands
A testvar group can be explicitly specified in testvar tokens
(TESTVAR
/TESTVARS...
) using the syntax GROUP:TESTVAR
. For
example:
buddy::testvar_define acsIp keyword-list {
constraints {
# true is testvar acsIp in testvar group wan2 is defined
if { [ exists wan2:acsIp ] } {
# fails if testvar acsAuth is undefined
must_exist acsAuth
}
}
}
the testvar token wan2:acsIp
in the exists
condition will evaluate
to the value of the acsIp
testvar in the wan2
testvar group. To
explicitly specify the default testvar group, use the group name
main
. The following additional commands are useful when writing
constraints involving testvar groups:
group
Returns name of testvar group currently being validated
group
groups
Returns a list of all defined group names. If optional exclusive
argument is given, return all names except the testvar group currently
being validated. Defaults to inclusive
.
groups [ exclusive ] [ TYPE ]
where TYPE
is one of:
Type | Description |
---|---|
wan-groups | main or wan<N> |
lan-groups | main or lan<N> |
wan-groups-mp | wan<N> |
lan-groups-mp | lan<N> |
smb-groups | smbuser<N> |
ftp-groups | ftpuser<N> |
cwmp-groups | cwmp_profile<N> |
tr069-groups | tr069_device<N> |
snmp-groups | snmpuser<N> |
group_is
Returns true if group is a given TYPE
. Matching can be done on a
different group name than the one currently being validated by
specifying optional GROUP
argument.
group_is TYPE [ GROUP ]
where TYPE
is one of:
Type | Description |
---|---|
wan-group | main or wan<N> |
lan-group | main or lan<N> |
wan-group-mp | wan<N> |
lan-group-mp | lan<N> |
smb-group | smbuser<N> |
ftp-group | ftpuser<N> |
cwmp-group | cwmp_profile<N> |
tr069-group | tr069_device<N> |
snmp-group | snmpuser<N> |
The plural form of each group type is also accepted and is treated
identically to the singular form. For example, wan-groups
is the
same as wan-group
and lan-groups-mp
is the same as lan-group-mp
.
If TYPE
does not match any of the above values, it is assumed to be
a regular expression and will be matched against GROUP
as a regular
expression using Tcl’s regexp
command.
group_exists
Returns true if given group name exists
group_exists GROUP
Wildcard Testvar Commands
A few commands exist to manage wildcard testvars:
is_wildcard
Returns true if testvar is a wildcard testvar
is_wildcard TESTVAR
wildcards
Returns sorted list of defined wildcard endings
wildcards TESTVAR
wildcard_values
Returns list of the values for instances of a wildcard testvar
wildcard_values TESTVAR
The example below verifies that the defined instances of the
virtualTcpServicePort
wildcard testvar in testvar group wan2
start
at 0
and are monotonically increasing:
if { [ is_wildcard wan2:virtualTcpServicePort ] } {
set prev -1
foreach ending [ wildcards wan2:virtualTcpServicePort ] {
if { [ expr $ending != ($prev+1) ] } {
config_error "virtualTcpServicePort must start at 0 and be monotonically increasing"
}
incr prev
}
}
Additional Commands
In addition to condition and constraint commands, there are a few additional commands available:
While processing testvar constraints, the name of the testvar being
defined by buddy::testvar_define
will be returned by the this
command:
this
Returns name of testvar currently being defined. If optional first
argument is value
, instead return the value of the testvar currently
being defined. If first argument is value
, optional second argument
can be a wildcard ending in which case the value returned will be the
value of the given wildcard instance of the testvar being defined.
this [ value ] [ ENDING ]
For example:
buddy::testvar_define acsAuth keyword-list {
constraints {
# true if testvar acsAuth is defined
if { [ exists [ this ] ] } {
# fails if testvar acsCertPath is undefined
must_exist acsCertPath
# fails if testvar acsAuth is not null, basic or digest
valid_values [ this ] null basic digest
}
}
}
before the exists
condition is evaluated, the this
invocation will
be replaced with acsAuth
. Similarly, before the valid_values
constraint is evaluated, the this
invocation will be replaced with
acsAuth
.
valid
Returns true if script does not fail
valid SCRIPT [ VARNAME ]
Constraint commands can be used as conditionals by wrapping them with
a call to the valid
command. If the script does not raise an
exception, the valid
command will save the script’s return value to
the second argument (if provided) and return true. If the script does
raise an exception, the valid
command will catch it, save the reason
to the second argument (if provided), and return false. For example:
buddy::testvar_define TESTVAR TYPE {
constraints {
# true if lanIp is *not* an IPv6 address
if { ![ valid { [ must_be lanIp IPv6 ] } ] } {
# fail if supportsIPv6's value is yes
invalid_values supportsIPv6 yes
}
}
}
If the lanIp
testvar is not an IPv6 address, the must_be
constraint command will throw an error which will be caught by the
valid
command causing the invalid_values
constraint to be
evaluated.
tv
Returns the value of TESTVAR
, or DEFAULT
if undefined
tv TESTVAR [ DEFAULT ]
The tv
command can be useful when a constraint requires runtime
values that cannot be hardcoded. For example:
buddy::testvar_define acsIp keyword-list {
constraints {
if { [ exists [ this ] ] } {
valid_values acsConnReqUrl "http://[ this value ]:8080/cwmp"
if { [ exists main:acsInterface ] } {
unique_values "[ tv main:acsInterface ]:wanVlanId" main:wanVlanId
}
must_exist wanIspIp wanIspMask
if { [ ip_same_network [ tv wanIspIp ] [ tv wanIspMask ] [ this value ] ] == 0 } {
config_error "testvar [ display [ this ] ] can not be on the WAN network, \
please choose a different IP address."
}
}
}
}
display
Return a printable (withProperCaps
) string of testvars
display TESTVARS...
config_error
Raises an error including the line number of the testvar being
defined. If optional final argument is print
, print REASON
but
don’t raise an error. If optional final argument is ret
, don’t
raise an error and simply return REASON
prefixed with line number
information.
config_error REASON [ error/print/ret ]
config_help
If called before a constraint command, the text will be shown to the
user in addition to the generic error message generated by the
constraint command. This command can be called multiple times, in
which case each call appends its text to the text of the previous
invocation. Constraint commands clear the help text after they
return. If the optional second argument is inhibit
, the generic
error message generated by the constraint command will not be shown.
config_help TEXT [ inhibit ]
cdrouter_addon
Returns true if the expansion (ipv6
, snmp
, storage
, etc) is enabled
both in license file and with the appropriate testvar (supportsIPv6
,
supportsSNMP
, supportsStorage
, etc).
cdrouter_addon ADDON