[augeas-devel] [PATCH] Add Dhcpd lens

Francis Giraldeau francis.giraldeau at gmail.com
Sat Jan 22 19:40:05 UTC 2011


From: Francis Giraldeau <francis.giraldeau at usherbrooke.ca>

The Dhcpd lens is able to parse the configuration of ISC DHCP server, according
to the manual.

  * Most statements are supported
  * Sections can be nested
  * Options values are handled as list
  * Automatic quoting and unquoting of values

There is basic support for dhcp-eval statements.
---
 lenses/dhcpd.aug            |  399 +++++++++++++++++++++++++++++++++++++++++++
 lenses/tests/test_dhcpd.aug |  372 ++++++++++++++++++++++++++++++++++++++++
 tests/Makefile.am           |    1 +
 3 files changed, 772 insertions(+), 0 deletions(-)
 create mode 100644 lenses/dhcpd.aug
 create mode 100644 lenses/tests/test_dhcpd.aug

diff --git a/lenses/dhcpd.aug b/lenses/dhcpd.aug
new file mode 100644
index 0000000..75d020a
--- /dev/null
+++ b/lenses/dhcpd.aug
@@ -0,0 +1,399 @@
+(*
+Module: Dhcpd
+  BIND dhcp 3 server configuration module for Augeas
+
+Author: Francis Giraldeau <francis.giraldeau at usherbrooke.ca>
+
+About: Reference
+  Reference: manual of dhcpd.conf and dhcp-eval
+  Follow dhclient module for tree structure
+
+About: License
+    This file is licensed under the GPL.
+
+About: Lens Usage
+  Sample usage of this lens in augtool
+
+  Directive without argument.
+  Set this dhcpd server authoritative on the domain.
+  > clear /files/etc/dhcp3/dhcpd.conf/authoritative
+
+  Directives with integer or string argument.
+  Set max-lease-time to one hour:
+  > set /files/etc/dhcp3/dhcpd.conf/max-lease-time 3600
+
+  Options are declared as a list, even for single values.
+  Set the domain of the network:
+  > set /files/etc/dhcp3/dhcpd.conf/option/domain-name/1 example.org
+  Set two name server:
+  > set /files/etc/dhcp3/dhcpd.conf/option/domain-name-servers/1 foo.example.org
+  > set /files/etc/dhcp3/dhcpd.conf/option/domain-name-servers/2 bar.example.org
+
+  Create the subnet 172.16.0.1 with 10 addresses:
+  > clear /files/etc/dhcp3/dhcpd.conf/subnet[last() + 1]
+  > set /files/etc/dhcp3/dhcpd.conf/subnet[last()]/network 172.16.0.0
+  > set /files/etc/dhcp3/dhcpd.conf/subnet[last()]/netmask 255.255.255.0
+  > set /files/etc/dhcp3/dhcpd.conf/subnet[last()]/range/from 172.16.0.10
+  > set /files/etc/dhcp3/dhcpd.conf/subnet[last()]/range/to 172.16.0.20
+
+  Create a new group "foo" with one static host. Nodes type and address are ordered. 
+  > ins group after /files/etc/dhcp3/dhcpd.conf/subnet[network='172.16.0.0']/*[last()]
+  > set /files/etc/dhcp3/dhcpd.conf/subnet[network='172.16.0.0']/group[last()]/host foo
+  > set /files/etc/dhcp3/dhcpd.conf/subnet[network='172.16.0.0']/group[host='foo']/host/hardware/type "ethernet"
+  > set /files/etc/dhcp3/dhcpd.conf/subnet[network='172.16.0.0']/group[host='foo']/host/hardware/address "00:00:00:aa:bb:cc"
+  > set /files/etc/dhcp3/dhcpd.conf/subnet[network='172.16.0.0']/group[host='foo']/host/fixed-address 172.16.0.100
+
+About: Configuration files
+  This lens applies to /etc/dhcpd3/dhcpd.conf. See <filter>.
+*)
+
+module Dhcpd =
+
+autoload xfm
+
+(************************************************************************
+ *                           USEFUL PRIMITIVES
+ *************************************************************************)
+let dels (s:string)   = del s s
+let eol               = Util.eol
+let comment           = Util.comment
+let empty             = Util.empty
+let indent            = Util.indent
+let eos               = comment?
+
+(* Define separators *)
+let sep_spc           = del /[ \t]+/ " "
+let sep_osp           = del /[ \t]*/ ""
+let sep_scl           = del /[ \t]*;([ \t]*\n)*/ ";\n"
+let sep_obr           = del /[ \t]*\{([ \t]*\n)*/ " {\n"
+let sep_cbr           = del /[ \t]*\}([ \t]*\n)*/ "}\n"
+let sep_com           = del /[ \t\n]*,[ \t\n]*/ ", "
+let sep_slh           = del "\/" "/"
+let sep_col           = del ":" ":"
+let sep_eq            = del /[ \t]*=[ \t]*/ "="
+let scl               = del ";" ";"
+
+(* Define basic types *)
+let word              = /[A-Za-z0-9_.-]+(\[[0-9]+\])?/
+let ip                = Rx.ipv4
+
+(* Define fields *)
+
+(* borrowed from sysconfig.aug *)
+  (* Chars allowed in a bare string *)
+  let bchar = /[^ \t\n\"'\\{\}#,\(\)]|\\\\./
+  let qchar = /["']/  (* " *)
+
+  (* We split the handling of right hand sides into a few cases:
+   *   bare  - strings that contain no spaces, optionally enclosed in
+   *           single or double quotes
+   *   dquot - strings that contain at least one space or apostrophe,
+   *           which must be enclosed in double quotes
+   *   squot - strings that contain an unescaped double quote
+   *)
+  let bare = del qchar? "" . store (bchar+) . del qchar? ""
+  let dquot =
+    del qchar "\"" . store (bchar* . /[ \t']/ . bchar*)+ . del qchar "\""
+  let squot =
+    dels "'" . store ((bchar|/[ \t]/)* . "\"" . (bchar|/[ \t]/)*)+ . dels "'"
+
+let sto_to_spc        = store /[^\\#,;\{\}" \t\n]+|"[^\\#"\n]+"/
+let sto_to_scl        = store /[^ \t;][^;\n=]+[^ \t;]|[^ \t;=]+/
+
+let sto_number        = store /[0-9][0-9]*/
+
+(************************************************************************
+ *                         NO ARG STATEMENTS
+ *************************************************************************)
+
+let stmt_noarg_re     =   "authoritative"
+                        | "primary"
+                        | "secondary"
+
+let stmt_noarg        = [ indent
+                        . key stmt_noarg_re
+                        . sep_scl
+                        . eos ]
+
+(************************************************************************
+ *                         INT ARG STATEMENTS
+ *************************************************************************)
+
+let stmt_integer_re   = "default-lease-time"
+                      | "max-lease-time"
+                      | "min-lease-time"
+                      | /lease[ ]+limit/
+                      | "port"
+                      | /peer[ ]+port/
+                      | "max-response-delay"
+                      | "max-unacked-updates"
+                      | "mclt"
+                      | "split"
+                      | /load[ ]+balance[ ]+max[ ]+seconds/
+                      | "max-lease-misbalance"
+                      | "max-lease-ownership"
+                      | "min-balance"
+                      | "max-balance"
+                      | "adaptive-lease-time-threshold"
+                      | "dynamic-bootp-lease-length"
+                      | "local-port"
+                      | "min-sec"
+                      | "omapi-port"
+                      | "ping-timeout"
+                      | "remote-port"
+
+let stmt_integer      = [ indent
+                        . key stmt_integer_re
+                        . sep_spc
+                        . sto_number
+                        . sep_scl
+                        . eos ]
+
+(************************************************************************
+ *                         STRING ARG STATEMENTS
+ *************************************************************************)
+
+let stmt_string_re    = "ddns-update-style"
+                      | "ddns-updates"
+                      | "ddns-hostname"
+                      | "ddns-domainname"
+                      | "ddns-rev-domainname"
+                      | "log-facility"
+                      | "filename"
+                      | "server-name"
+                      | "fixed-address"
+                      | /failover[ ]+peer/
+                      | "use-host-decl-names"
+                      | "next-server"
+                      | "address"
+                      | /peer[ ]+address/
+                      | "type"
+                      | "file"
+                      | "algorithm"
+                      | "secret"
+                      | "key"
+                      | "include"
+                      | "hba"
+                      | "boot-unknown-clients"
+                      | "db-time-format"
+                      | "do-forward-updates"
+                      | "dynamic-bootp-lease-cutoff"
+                      | "get-lease-hostnames"
+                      | "infinite-is-reserved"
+                      | "lease-file-name"
+                      | "local-address"
+                      | "one-lease-per-client"
+                      | "pid-file-name"
+                      | "ping-check"
+                      | "server-identifier"
+                      | "site-option-space"
+                      | "stash-agent-options"
+                      | "update-conflict-detection"
+                      | "update-optimization"
+                      | "update-static-leases"
+                      | "use-host-decl-names"
+                      | "use-lease-addr-for-default-route"
+                      | "vendor-option-space"
+
+let stmt_string_tpl (l:lens) = [ indent
+                        . key stmt_string_re
+                        . sep_spc
+                        . l
+                        . sep_scl
+                        . eos ]
+
+let stmt_string  = stmt_string_tpl bare |stmt_string_tpl squot | stmt_string_tpl dquot
+
+(************************************************************************
+ *                         RANGE STATEMENTS
+ *************************************************************************)
+
+let stmt_range        = [ indent
+                        . key "range"
+                        . sep_spc
+                        . [ label "flag" . store /dynamic-bootp/ . sep_spc ]?
+                        . [ label "from" . store ip . sep_spc ]?
+                        . [ label "to" . store ip ]
+                        . sep_scl
+                        . eos ]
+
+(************************************************************************
+ *                         HARDWARE STATEMENTS
+ *************************************************************************)
+
+let stmt_hardware     = [ indent
+                        . key "hardware"
+                        . sep_spc
+                        . [ label "type" . store /ethernet|tokenring/ ]
+                        . sep_spc
+                        . [ label "address" . store /[a-fA-F0-9:-]+/ ]
+                        . sep_scl
+                        . eos ]
+
+(************************************************************************
+ *                         OPTION STATEMENTS
+ *************************************************************************)
+(* The general case is considering options as a list *)
+
+let stmt_option_code  = [ label "label" . store word . sep_spc ]
+                        . [ key "code" . sep_spc . store word ]
+                        . sep_eq
+                        . [ label "type" . store word ]
+
+
+let stmt_option_list  = counter "id"
+                        . ([ seq "id" . bare ] | [ seq "id" . dquot ] | [ seq "id" . squot ])
+                        . ( sep_com . ([ seq "id" . bare ] | [ seq "id" . dquot ] | [ seq "id" . squot ]))*
+
+let stmt_option_basic = [ key word . sep_spc . stmt_option_list ]
+let stmt_option_extra = [ key word . sep_spc . store /true|false/ . sep_spc . stmt_option_list ]
+
+let stmt_option_body = stmt_option_basic | stmt_option_extra
+
+let stmt_option1  = [ indent
+                        . key "option"
+                        . sep_spc
+                        . stmt_option_body
+                        . sep_scl
+                        . eos ]
+
+let stmt_option2  = [ indent
+                        . dels "option" . label "rfc-code"
+                        . sep_spc
+                        . stmt_option_code
+                        . sep_scl
+                        . eos ]
+
+let stmt_option = stmt_option1 | stmt_option2
+
+(************************************************************************
+ *                         SUBCLASS STATEMENTS
+ *************************************************************************)
+(* this statement is not well documented in the manual dhcpd.conf
+   we support basic use case *)
+
+let stmt_subclass = [ indent . key "subclass" . sep_spc .
+                      ([ label "name" . dquot ]|
+                       [ label "name" . squot ]|
+                       [ label "name" . bare ]) . sep_spc .
+                       [ label "value" . bare ] . sep_scl . eos ]
+
+(************************************************************************
+ *                         ALLOW/DENY STATEMENTS
+ *************************************************************************)
+(* We have to use special key for allow/deny members of
+  to avoid ambiguity in the put direction *)
+
+let allow_deny_re     = "unknown-clients"
+                      | /dynamic[ ]+bootp[ ]+clients/
+                      | /authenticated[ ]+clients/
+                      | /unauthenticated[ ]+clients/
+
+let stmt_secu_re      = "allow"
+                      | "deny"
+
+let del_allow = del /allow[ ]+members[ ]+of/ "allow members of"
+let del_deny  = del /deny[ \t]+members[ \t]+of/ "deny members of"
+
+let stmt_secu_tpl (l:lens) (s:string) =
+                  [ indent . l . sep_spc . label s . bare . sep_scl . eos ] |
+                  [ indent . l . sep_spc . label s . squot . sep_scl . eos ] |
+                  [ indent . l . sep_spc . label s . dquot . sep_scl . eos ]
+
+let stmt_secu         = [ indent . key stmt_secu_re . sep_spc .
+                          store allow_deny_re . sep_scl . eos ] |
+                        stmt_secu_tpl del_allow "allow-members-of" |
+                        stmt_secu_tpl del_deny "deny-members-of"
+
+(************************************************************************
+ *                         MATCH STATEMENTS
+ *************************************************************************)
+
+let sto_fct = store (word . /[ \t]*\([^)]*\)/)
+let sto_option = store (/option[ ]+/ . word)
+let sto_com = /[^ \t\n,\(\)][^,\(\)]*[^ \t\n,\(\)]|[^ \t\n,\(\)]+/ | word . /[ \t]*\([^)]*\)/
+let fct_re = "substring" | "binary-to-ascii"
+
+let fct_args = [ label "args" . dels "(" . counter "args" . sep_osp .
+                 ([ seq "args" . store sto_com ] . [ seq "args" . sep_com . store sto_com ]+) .
+                        sep_osp . dels ")" ]
+
+let stmt_match_if = [ dels "if" . sep_spc . store fct_re . sep_osp . label "function" . fct_args ] .
+                      sep_eq . ([ label "value" . bare ]|[ label "value" . squot ]|[ label "value" . dquot ])
+
+let stmt_match_pfv = [ label "function" . store "pick-first-value" . sep_spc .
+                       dels "(" . sep_osp .
+                       [ counter "opt" . label "args" .
+                         [ seq "opt" . store sto_com ] .
+                         [ sep_com . seq "opt" . store sto_com ]+ ] .
+                       dels ")" ]
+
+let stmt_match_tpl (l:lens) = [ indent . key "match" . sep_spc . l . sep_scl . eos ]
+
+let stmt_match = stmt_match_tpl (stmt_match_if | stmt_match_pfv )
+
+(************************************************************************
+ *                         BLOCK STATEMENTS
+ *************************************************************************)
+(* Blocks doesn't support comments at the end of the closing bracket *)
+
+let stmt_entry        =   stmt_secu
+                        | stmt_option
+                        | stmt_hardware
+                        | stmt_range
+                        | stmt_string
+                        | stmt_integer
+                        | stmt_noarg
+                        | stmt_match
+                        | stmt_subclass
+                        | empty
+                        | comment
+
+let stmt_block_noarg_re = "pool"
+                        | "group"
+                        | "allow-update"
+
+let stmt_block_noarg (body:lens)
+                        = [ indent
+                        . key stmt_block_noarg_re
+                        . sep_obr
+                        . body*
+                        . sep_cbr ]
+
+let stmt_block_arg_re = "host"
+                      | "class"
+                      | "shared-network"
+                      | /failover[ ]+peer/
+                      | "zone"
+                      | "key"
+
+let stmt_block_arg (body:lens)
+                      = [ indent
+                        . key stmt_block_arg_re
+                        . sep_spc
+                        . sto_to_spc
+                        . sep_obr
+                        . body*
+                        . sep_cbr ]
+
+let stmt_block_subnet (body:lens)
+                      = [ indent
+                        . key "subnet"
+                        . sep_spc
+                        . [ label "network" . store ip ]
+                        . sep_spc
+                        . [ key "netmask" . sep_spc . store ip ]
+                        . sep_obr
+                        . body*
+                        . sep_cbr ]
+
+let all_block (body:lens) =
+    let lns1 = stmt_block_subnet body in
+    let lns2 = stmt_block_arg body in
+    let lns3 = stmt_block_noarg body in
+    (lns1 | lns2 | lns3 | stmt_entry)
+
+let rec lns_staging = stmt_entry|all_block lns_staging
+let lns = (lns_staging)*
+
+let xfm = transform lns (incl "/etc/dhcp3/dhcpd.conf")
diff --git a/lenses/tests/test_dhcpd.aug b/lenses/tests/test_dhcpd.aug
new file mode 100644
index 0000000..01f7e0c
--- /dev/null
+++ b/lenses/tests/test_dhcpd.aug
@@ -0,0 +1,372 @@
+module Test_dhcpd = 
+
+let lns = Dhcpd.lns
+
+let conf = "#
+# Sample configuration file for ISC dhcpd for Debian
+#
+# Attention: If /etc/ltsp/dhcpd.conf exists, that will be used as
+# configuration file instead of this file.
+#
+# $Id: dhcpd.conf,v 1.1.1.1 2002/05/21 00:07:44 peloy Exp $
+#
+
+# The ddns-updates-style parameter controls whether or not the server will
+# attempt to do a DNS update when a lease is confirmed. We default to the
+# behavior of the version 2 packages ('none', since DHCP v2 didn't
+# have support for DDNS.)
+ddns-update-style none;
+
+# option definitions common to all supported networks...
+option domain-name \"example.org\";
+option domain-name-servers ns1.example.org, ns2.example.org;
+
+default-lease-time 600;
+max-lease-time 7200;
+
+# If this DHCP server is the official DHCP server for the local
+# network, the authoritative directive should be uncommented.
+authoritative;
+
+# Use this to send dhcp log messages to a different log file (you also
+# have to hack syslog.conf to complete the redirection).
+log-facility local7;
+
+# No service will be given on this subnet, but declaring it helps the 
+# DHCP server to understand the network topology.
+
+subnet 10.152.187.0 netmask 255.255.255.0 {
+}
+
+# This is a very basic subnet declaration.
+
+subnet 10.254.239.0 netmask 255.255.255.224 {
+  range 10.254.239.10 10.254.239.20;
+  option routers rtr-239-0-1.example.org, rtr-239-0-2.example.org;
+}
+
+# This declaration allows BOOTP clients to get dynamic addresses,
+# which we don't really recommend.
+
+subnet 10.254.239.32 netmask 255.255.255.224 {
+  range dynamic-bootp 10.254.239.40 10.254.239.60;
+  option broadcast-address 10.254.239.31;
+  option routers rtr-239-32-1.example.org;
+}
+
+# A slightly different configuration for an internal subnet.
+subnet 10.5.5.0 netmask 255.255.255.224 {
+  range 10.5.5.26 10.5.5.30;
+  option domain-name-servers ns1.internal.example.org;
+  option domain-name \"internal.example.org\";
+  option routers 10.5.5.1;
+  option broadcast-address 10.5.5.31;
+  default-lease-time 600;
+  max-lease-time 7200;
+}
+
+# Hosts which require special configuration options can be listed in
+# host statements.   If no address is specified, the address will be
+# allocated dynamically (if possible), but the host-specific information
+# will still come from the host declaration.
+
+host passacaglia {
+  hardware ethernet 0:0:c0:5d:bd:95;
+  filename \"vmunix.passacaglia\";
+  server-name \"toccata.fugue.com\";
+}
+
+# Fixed IP addresses can also be specified for hosts.   These addresses
+# should not also be listed as being available for dynamic assignment.
+# Hosts for which fixed IP addresses have been specified can boot using
+# BOOTP or DHCP.   Hosts for which no fixed address is specified can only
+# be booted with DHCP, unless there is an address range on the subnet
+# to which a BOOTP client is connected which has the dynamic-bootp flag
+# set.
+host fantasia {
+  hardware ethernet 08:00:07:26:c0:a5;
+  fixed-address fantasia.fugue.com;
+}
+
+# You can declare a class of clients and then do address allocation
+# based on that.   The example below shows a case where all clients
+# in a certain class get addresses on the 10.17.224/24 subnet, and all
+# other clients get addresses on the 10.0.29/24 subnet.
+
+#class \"foo\" {
+#  match if substring (option vendor-class-identifier, 0, 4) = \"SUNW\";
+#}
+
+shared-network 224-29 {
+  subnet 10.17.224.0 netmask 255.255.255.0 {
+    option routers rtr-224.example.org;
+  }
+  subnet 10.0.29.0 netmask 255.255.255.0 {
+    option routers rtr-29.example.org;
+  }
+  pool {
+    allow members of \"foo\";
+    range 10.17.224.10 10.17.224.250;
+  }
+  pool {
+    deny members of \"foo\";
+    range 10.0.29.10 10.0.29.230;
+  }
+}
+"
+
+test lns get "authoritative;" = { "authoritative" }
+test lns get "ddns-update-style none;" = { "ddns-update-style" = "none" }
+test lns get "option domain-name \"example.org\";" = 
+  { "option"
+    { "domain-name"
+      { "1" = "example.org" }
+    }
+  }
+
+test lns get "option domain-name-servers ns1.example.org, ns2.example.org;" =
+  { "option"
+    { "domain-name-servers"
+      { "1" = "ns1.example.org" }
+      { "2" = "ns2.example.org" }
+    }
+  }
+
+test lns get "default-lease-time 600;" = { "default-lease-time" = "600" }
+test lns get "range 10.254.239.60;" =   
+{ "range"
+    { "to" = "10.254.239.60" }
+  }
+
+test lns get "range dynamic-bootp 10.254.239.60;" = 
+  { "range"
+    { "flag" = "dynamic-bootp" }
+    { "to" = "10.254.239.60" }
+  }
+
+test lns get "range dynamic-bootp 10.254.239.40 10.254.239.60;" = 
+  { "range"
+    { "flag" = "dynamic-bootp" }
+    { "from" = "10.254.239.40" }
+    { "to" = "10.254.239.60" }
+  }
+
+test lns get "subnet 10.152.187.0 netmask 255.255.255.0 {}\n" = 
+  { "subnet"
+    { "network" = "10.152.187.0" }
+    { "netmask" = "255.255.255.0" }
+  }
+
+test lns get " pool {
+    pool {
+
+    }
+} 
+" = 
+  { "pool"
+    { "pool" }
+  }
+
+test lns get "group { host some-host {hardware ethernet 00:00:aa:bb:cc:dd;
+fixed-address 10.1.1.1;}}" = 
+  { "group"
+    { "host" = "some-host"
+      { "hardware"
+        { "type" = "ethernet" }
+        { "address" = "00:00:aa:bb:cc:dd" }
+      }
+      { "fixed-address" = "10.1.1.1" }
+    }
+  }
+
+test Dhcpd.stmt_secu get "allow members of \"foo\";" =  { "allow-members-of" = "foo" }
+test Dhcpd.stmt_option get "option voip-boot-server code 66 = string;" = 
+  { "rfc-code"
+    { "label" = "voip-boot-server" }
+    { "code" = "66" }
+    { "type" = "string" }
+  }
+
+test Dhcpd.lns get "authoritative;
+log-facility local7;
+ddns-update-style none;
+default-lease-time 21600;
+max-lease-time 43200;
+
+# Additional options for VOIP
+option voip-boot-server code 66 = string;
+option voip-vlan-id code 128 = string;
+" = 
+  { "authoritative" }
+  { "log-facility" = "local7" }
+  { "ddns-update-style" = "none" }
+  { "default-lease-time" = "21600" }
+  { "max-lease-time" = "43200"
+    { "#comment" = "Additional options for VOIP" }
+  }
+  { "rfc-code"
+    { "label" = "voip-boot-server" }
+    { "code" = "66" }
+    { "type" = "string" }
+  }
+  { "rfc-code"
+    { "label" = "voip-vlan-id" }
+    { "code" = "128" }
+    { "type" = "string" }
+  }
+
+
+test Dhcpd.lns get "
+option domain-name-servers 10.1.1.1, 10.11.2.1, 10.1.3.1;
+next-server 10.1.1.1;
+
+failover peer \"redondance01\" {
+         primary;
+         address 10.1.1.1;
+         port 647;
+         peer address 10.1.1.1;
+         peer port 647;
+         max-response-delay 20;
+         max-unacked-updates 10;
+         mclt 3600;         #comment.
+         split 128;         #comment.
+         load balance max seconds 3;
+       }
+" = 
+  {  }
+  { "option"
+    { "domain-name-servers"
+      { "1" = "10.1.1.1" }
+      { "2" = "10.11.2.1" }
+      { "3" = "10.1.3.1" }
+    }
+  }
+  { "next-server" = "10.1.1.1" }
+  { "failover peer" = "\"redondance01\""
+    { "primary" }
+    { "address" = "10.1.1.1" }
+    { "port" = "647" }
+    { "peer address" = "10.1.1.1" }
+    { "peer port" = "647" }
+    { "max-response-delay" = "20" }
+    { "max-unacked-updates" = "10" }
+    { "mclt" = "3600"
+      { "#comment" = "comment." }
+    }
+    { "split" = "128"
+      { "#comment" = "comment." }
+    }
+    { "load balance max seconds" = "3" }
+  }
+
+test Dhcpd.lns get "
+option CallManager code 150 = ip-address;
+option slp-directory-agent true 10.1.1.1, 10.2.2.2;
+option slp-service-scope true \"SLP-GLOBAL\";
+option nds-context \"EXAMPLE\";
+option nds-tree-name \"EXAMPLE\";
+" = 
+  {  }
+  { "rfc-code"
+    { "label" = "CallManager" }
+    { "code" = "150" }
+    { "type" = "ip-address" }
+  }
+  { "option"
+    { "slp-directory-agent" = "true"
+      { "1" = "10.1.1.1" }
+      { "2" = "10.2.2.2" }
+    }
+  }
+  { "option"
+    { "slp-service-scope" = "true"
+      { "1" = "SLP-GLOBAL" }
+    }
+  }
+  { "option"
+    { "nds-context"
+      { "1" = "EXAMPLE" }
+    }
+  }
+  { "option"
+    { "nds-tree-name"
+      { "1" = "EXAMPLE" }
+    }
+  }
+
+
+test Dhcpd.lns get "option voip-vlan-id \"VLAN=1234;\";" = 
+  { "option"
+    { "voip-vlan-id"
+      { "1" = "VLAN=1234;" }
+    }
+  }
+
+test Dhcpd.lns get "option domain-name \"x.example.com y.example.com z.example.com\";" = 
+  { "option"
+    { "domain-name"
+      { "1" = "x.example.com y.example.com z.example.com" }
+    }
+  }
+
+test Dhcpd.lns get "include \"/etc/dhcpd.master\";" = 
+  { "include" = "/etc/dhcpd.master" }
+
+test Dhcpd.fct_args get "(option dhcp-client-identifier, 1, 3)" = 
+  { "args"
+    { "1" = "option dhcp-client-identifier" }
+    { "2" = "1" }
+    { "3" = "3" }
+  }
+
+test Dhcpd.stmt_match get "match if substring (option dhcp-client-identifier, 1, 3) = \"RAS\";" = 
+  { "match"
+    { "function" = "substring" 
+      { "args" 
+        { "1" = "option dhcp-client-identifier" }  
+        { "2" = "1" }
+        { "3" = "3" }
+      }
+    }
+    { "value" = "RAS" }
+  }
+
+test Dhcpd.lns get "match pick-first-value (option dhcp-client-identifier, hardware);" = 
+  { "match"
+    { "function" = "pick-first-value"
+      { "args"  
+        { "1" = "option dhcp-client-identifier" }
+        { "2" = "hardware"  }
+      }
+    }
+  }
+
+test Dhcpd.fct_args get "(16, 32, \"\", substring(hardware, 0, 4))" = 
+  { "args"
+    { "1" = "16" }
+    { "2" = "32" }
+    { "3" = "\"\"" }
+    { "4" = "substring(hardware, 0, 4)" }
+  }
+
+test Dhcpd.stmt_match get "match if binary-to-ascii(16, 32, \"\", substring(hardware, 0, 4)) = \"1525400\";" = 
+  { "match"
+    { "function" = "binary-to-ascii"
+      { "args"
+        { "1" = "16" }
+        { "2" = "32" }
+        { "3" = "\"\"" }
+        { "4" = "substring(hardware, 0, 4)" }
+      }
+    }
+    { "value" = "1525400" }
+  }
+
+test Dhcpd.lns get "subclass \"allocation-class-1\" 1:8:0:2b:4c:39:ad;" = 
+  { "subclass" 
+    { "name" = "allocation-class-1" }
+    { "value" = "1:8:0:2b:4c:39:ad" }
+  }
+
+(* overall test *)
+test Dhcpd.lns put conf after rm "/x" = conf 
diff --git a/tests/Makefile.am b/tests/Makefile.am
index db4172f..fdb0d9f 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -27,6 +27,7 @@ lens_tests =			\
   lens-debctrl.sh		\
   lens-device_map.sh    \
   lens-dhclient.sh		\
+  lens-dhcpd.sh		\
   lens-dnsmasq.sh		\
   lens-dpkg.sh			\
   lens-dput.sh			\
-- 
1.7.1




More information about the augeas-devel mailing list