CORD-1181 include DHCP reservations when harvesting from DHCP
Change-Id: I5e148a5d92f275455681033d402b413beef8a537
diff --git a/harvester/Dockerfile b/harvester/Dockerfile
index f0233ec..4cdc692 100644
--- a/harvester/Dockerfile
+++ b/harvester/Dockerfile
@@ -22,7 +22,7 @@
RUN go build -o /service/entry-point gerrit.opencord.com/maas/harvester
LABEL org.label-schema.name="harvester" \
- org.label-schema.description="Provides DHCP havesting and insertion into DNS" \
+ org.label-schema.description="Provides DHCP harvesting and insertion into DNS" \
org.label-schema.vcs-url="https://gerrit.opencord.org/maas" \
org.label-schema.vendor="Open Networking Laboratory" \
org.label-schema.schema-version="1.0"
diff --git a/harvester/Dockerfile.release b/harvester/Dockerfile.release
index b6d636d..1089ddc 100644
--- a/harvester/Dockerfile.release
+++ b/harvester/Dockerfile.release
@@ -19,7 +19,7 @@
ADD entry-point /service/entry-point
LABEL org.label-schema.name="harvester" \
- org.label-schema.description="Provides DHCP havesting and insertion into DNS" \
+ org.label-schema.description="Provides DHCP harvesting and insertion into DNS" \
org.label-schema.vcs-url="https://gerrit.opencord.org/maas" \
org.label-schema.vendor="Open Networking Laboratory" \
org.label-schema.schema-version="1.0"
diff --git a/harvester/harvester.go b/harvester/harvester.go
index e0314e6..3a16817 100644
--- a/harvester/harvester.go
+++ b/harvester/harvester.go
@@ -33,26 +33,27 @@
// application application configuration and internal state
type application struct {
- Port int `default:"4246" desc:"port on which the service will listen for requests"`
- Listen string `default:"0.0.0.0" desc:"IP on which the service will listen for requests"`
- LogLevel string `default:"warning" envconfig:"LOG_LEVEL" desc:"log output level"`
- LogFormat string `default:"text" envconfig:"LOG_FORMAT" desc:"format of log messages"`
- DHCPLeaseFile string `default:"/harvester/dhcpd.leases" envconfig:"DHCP_LEASE_FILE" desc:"lease file to parse for lease information"`
- OutputFile string `envconfig:"OUTPUT_FILE" desc:"name of file to output discovered lease in bind9 format"`
- OutputFormat string `default:"{{.ClientHostname}}\tIN A {{.IPAddress}}\t; {{.HardwareAddress}}" envconfig:"OUTPUT_FORMAT" desc:"specifies the single entry format when outputing to a file"`
- VerifyLeases bool `default:"true" envconfig:"VERIFY_LEASES" desc:"verifies leases with a ping"`
- VerifyTimeout time.Duration `default:"1s" envconfig:"VERIFY_TIMEOUT" desc:"max timeout (RTT) to wait for verification pings"`
- VerifyWithUDP bool `default:"false" envconfig:"VERIFY_WITH_UDP" desc:"use UDP instead of raw sockets for ping verification"`
- QueryPeriod time.Duration `default:"30s" envconfig:"QUERY_PERIOD" desc:"period at which the DHCP lease file is processed"`
- QuietPeriod time.Duration `default:"2s" envconfing:"QUIET_PERIOD" desc:"period to wait between accepting parse requests"`
- RequestTimeout time.Duration `default:"10s" envconfig:"REQUEST_TIMEOUT" desc:"period to wait for processing when requesting a DHCP lease database parsing"`
- RNDCUpdate bool `default:"false" envconfig:"RNDC_UPDATE" desc:"determines if the harvester reloads the DNS servers after harvest"`
- RNDCAddress string `default:"127.0.0.1" envconfig:"RNDC_ADDRESS" desc:"IP address of the DNS server to contact via RNDC"`
- RNDCPort int `default:"954" envconfig:"RNDC_PORT" desc:"port of the DNS server to contact via RNDC"`
- RNDCKeyFile string `default:"/key/rndc.conf.maas" envconfig:"RNDC_KEY_FILE" desc:"key file, with default, to contact DNS server"`
- RNDCZone string `default:"cord.lab" envconfig:"RNDC_ZONE" desc:"zone to reload"`
- BadClientNames []string `default:"localhost" envconfig:"BAD_CLIENT_NAMES" desc:"list of invalid hostnames for clients"`
- ClientNameTemplate string `default:"UKN-{{with $x:=.HardwareAddress|print}}{{regex $x \":\" \"\"}}{{end}}" envconfig:"CLIENT_NAME_TEMPLATE" desc:"template for generated host name"`
+ Port int `default:"4246" desc:"port on which the service will listen for requests"`
+ Listen string `default:"0.0.0.0" desc:"IP on which the service will listen for requests"`
+ LogLevel string `default:"warning" envconfig:"LOG_LEVEL" desc:"log output level"`
+ LogFormat string `default:"text" envconfig:"LOG_FORMAT" desc:"format of log messages"`
+ DHCPLeaseFile string `default:"/harvester/dhcpd.leases" envconfig:"DHCP_LEASE_FILE" desc:"lease file to parse for lease information"`
+ DHCPReservationFile string `default:"/reservations/dhcpd.reservations" envconfig:"DHCP_RESERVATION_FILE" desc:"lease reservation file for IP information"`
+ OutputFile string `envconfig:"OUTPUT_FILE" desc:"name of file to output discovered lease in bind9 format"`
+ OutputFormat string `default:"{{.ClientHostname}}\tIN A {{.IPAddress}}\t; {{.HardwareAddress}}" envconfig:"OUTPUT_FORMAT" desc:"specifies the single entry format when outputing to a file"`
+ VerifyLeases bool `default:"true" envconfig:"VERIFY_LEASES" desc:"verifies leases with a ping"`
+ VerifyTimeout time.Duration `default:"1s" envconfig:"VERIFY_TIMEOUT" desc:"max timeout (RTT) to wait for verification pings"`
+ VerifyWithUDP bool `default:"false" envconfig:"VERIFY_WITH_UDP" desc:"use UDP instead of raw sockets for ping verification"`
+ QueryPeriod time.Duration `default:"30s" envconfig:"QUERY_PERIOD" desc:"period at which the DHCP lease file is processed"`
+ QuietPeriod time.Duration `default:"2s" envconfing:"QUIET_PERIOD" desc:"period to wait between accepting parse requests"`
+ RequestTimeout time.Duration `default:"10s" envconfig:"REQUEST_TIMEOUT" desc:"period to wait for processing when requesting a DHCP lease database parsing"`
+ RNDCUpdate bool `default:"false" envconfig:"RNDC_UPDATE" desc:"determines if the harvester reloads the DNS servers after harvest"`
+ RNDCAddress string `default:"127.0.0.1" envconfig:"RNDC_ADDRESS" desc:"IP address of the DNS server to contact via RNDC"`
+ RNDCPort int `default:"954" envconfig:"RNDC_PORT" desc:"port of the DNS server to contact via RNDC"`
+ RNDCKeyFile string `default:"/key/rndc.conf.maas" envconfig:"RNDC_KEY_FILE" desc:"key file, with default, to contact DNS server"`
+ RNDCZone string `default:"cord.lab" envconfig:"RNDC_ZONE" desc:"zone to reload"`
+ BadClientNames []string `default:"localhost" envconfig:"BAD_CLIENT_NAMES" desc:"list of invalid hostnames for clients"`
+ ClientNameTemplate string `default:"UKN-{{with $x:=.HardwareAddress|print}}{{regex $x \":\" \"\"}}{{end}}" envconfig:"CLIENT_NAME_TEMPLATE" desc:"template for generated host name"`
appFlags *flag.FlagSet `ignored:"true"`
log *logrus.Logger `ignored:"true"`
@@ -113,29 +114,30 @@
// output the configuration
app.log.Infof(`Configuration:
- LISTEN: %s
- PORT: %d
- LOG_LEVEL: %s
- LOG_FORMAT: %s
- DHCP_LEASE_FILE: %s
- OUTPUT_FILE: %s
- OUTPUT_FORMAT: %s
- VERIFY_LEASES: %t
- VERIFY_TIMEOUT: %s
- VERIFY_WITH_UDP: %t
- QUERY_PERIOD: %s
- QUIET_PERIOD: %s
- REQUEST_TIMEOUT: %s
- RNDC_UPDATE: %t
- RNDC_ADDRESS: %s
- RNDC_PORT: %d
- RNDC_KEY_FILE: %s
- RNDC_ZONE: %s
- BAD_CLIENT_NAMES: %s
- CLIENT_NAME_TEMPLATE: %s`,
+ LISTEN: %s
+ PORT: %d
+ LOG_LEVEL: %s
+ LOG_FORMAT: %s
+ DHCP_LEASE_FILE: %s
+ DHCP_RESERVATION_FILE: %s
+ OUTPUT_FILE: %s
+ OUTPUT_FORMAT: %s
+ VERIFY_LEASES: %t
+ VERIFY_TIMEOUT: %s
+ VERIFY_WITH_UDP: %t
+ QUERY_PERIOD: %s
+ QUIET_PERIOD: %s
+ REQUEST_TIMEOUT: %s
+ RNDC_UPDATE: %t
+ RNDC_ADDRESS: %s
+ RNDC_PORT: %d
+ RNDC_KEY_FILE: %s
+ RNDC_ZONE: %s
+ BAD_CLIENT_NAMES: %s
+ CLIENT_NAME_TEMPLATE: %s`,
app.Listen, app.Port,
app.LogLevel, app.LogFormat,
- app.DHCPLeaseFile, app.OutputFile, strconv.Quote(app.OutputFormat),
+ app.DHCPLeaseFile, app.DHCPReservationFile, app.OutputFile, strconv.Quote(app.OutputFormat),
app.VerifyLeases, app.VerifyTimeout, app.VerifyWithUDP,
app.QueryPeriod, app.QuietPeriod, app.RequestTimeout,
app.RNDCUpdate, app.RNDCAddress, app.RNDCPort, app.RNDCKeyFile, app.RNDCZone,
diff --git a/harvester/parse.go b/harvester/parse.go
index 906b23c..059bf8e 100644
--- a/harvester/parse.go
+++ b/harvester/parse.go
@@ -136,6 +136,70 @@
return leases, nil
}
+// parseReservation parses a single reservation entry
+func (app *application) parseReservation(scanner *bufio.Scanner, lease *Lease) error {
+ var err error
+ for scanner.Scan() {
+ fields := strings.Fields(scanner.Text())
+ if len(fields) > 0 {
+ switch fields[0] {
+ case "}":
+ // If not IP or MAC specified then return error
+ if len(lease.HardwareAddress) == 0 {
+ return fmt.Errorf("Reservation requires hardware address")
+ }
+ if len(lease.IPAddress) == 0 {
+ return fmt.Errorf("Reservation requires IP address")
+ }
+ return nil
+ case "hardware":
+ lease.HardwareAddress, err = net.ParseMAC(strings.Trim(fields[2], ";"))
+ if err != nil {
+ return err
+ }
+ case "fixed-address":
+ lease.IPAddress = net.ParseIP(strings.Trim(fields[1], ";"))
+ if lease.IPAddress == nil {
+ return fmt.Errorf("Invalid IP Address")
+ }
+ }
+ }
+ }
+ return nil
+}
+
+// parseReservationFile parses the reservation file to include reservation IPs in IP information
+func (app *application) parseReservationFile(filename string, leases map[string]*Lease) (map[string]*Lease, error) {
+ // If no filename was specified, nothing to parse
+ if len(filename) == 0 {
+ return leases, nil
+ }
+
+ file, err := os.Open(filename)
+ if err != nil {
+ return nil, err
+ }
+ defer file.Close()
+
+ scanner := bufio.NewScanner(file)
+ scanner.Split(bufio.ScanLines)
+ for scanner.Scan() {
+ fields := strings.Fields(scanner.Text())
+ if len(fields) > 0 && fields[0] == "host" {
+ lease := Lease{}
+ lease.ClientHostname = fields[1]
+ app.parseReservation(scanner, &lease)
+ leases[lease.IPAddress.String()] = &lease
+ }
+ }
+
+ if err = scanner.Err(); err != nil {
+ return nil, err
+ }
+
+ return leases, nil
+}
+
// syncRequestHandler accepts requests to parse the lease file and either processes or ignores because of quiet period
func (app *application) syncRequestHandler(requests chan *chan uint) {
@@ -168,69 +232,81 @@
app.log.Errorf("Unable to parse DHCP lease file at '%s' : %s",
app.DHCPLeaseFile, err)
} else {
- // if configured to verify leases with a ping do so
- if app.VerifyLeases {
- app.log.Infof("Verifing %d discovered leases", len(leases))
- _, err := app.verifyLeases(leases)
- if err != nil {
- app.log.Errorf("unexpected error while verifing leases : %s", err)
- app.log.Infof("Discovered %d active, not verified because of error, DHCP leases",
- len(leases))
- } else {
- app.log.Infof("Discovered %d active and verified DHCP leases", len(leases))
- }
+ leaseCount := len(leases)
+ app.log.Infof("Read %d leases from lease file", leaseCount)
+ // Process the reservation file, if specified
+ app.log.Info("Synchronizing DHCP reservation file")
+ leases, err = app.parseReservationFile(app.DHCPReservationFile, leases)
+ if err != nil {
+ app.log.Errorf("Unable to parse reservation file '%s' : '%s'",
+ app.DHCPReservationFile, err)
} else {
- app.log.Infof("Discovered %d active, not not verified, DHCP leases", len(leases))
- }
-
- // if configured to output the lease information to a file, do so
- if len(app.OutputFile) > 0 {
- app.log.Infof("Writing lease information to file '%s'", app.OutputFile)
- out, err := os.Create(app.OutputFile)
- if err != nil {
- app.log.Errorf(
- "unexpected error while attempting to open file `%s' for output : %s",
- app.OutputFile, err)
- } else {
- table := tabwriter.NewWriter(out, 1, 0, 4, ' ', 0)
- for _, lease := range leases {
- if err := app.outputTemplate.Execute(table, lease); err != nil {
- app.log.Errorf(
- "unexpected error while writing leases to file '%s' : %s",
- app.OutputFile, err)
- break
- }
- fmt.Fprintln(table)
+ app.log.Infof("Read %d reservations from reservation file",
+ len(leases)-leaseCount)
+ // if configured to verify leases with a ping do so
+ if app.VerifyLeases {
+ app.log.Infof("Verifing %d discovered leases", len(leases))
+ _, err := app.verifyLeases(leases)
+ if err != nil {
+ app.log.Errorf("unexpected error while verifing leases : %s", err)
+ app.log.Infof("Discovered %d active, not verified because of error, DHCP leases",
+ len(leases))
+ } else {
+ app.log.Infof("Discovered %d active and verified DHCP leases", len(leases))
}
- table.Flush()
- }
- out.Close()
- }
-
- // if configured to reload the DNS server, then use the RNDC command to do so
- if app.RNDCUpdate {
- cmd := exec.Command("rndc", "-s", app.RNDCAddress, "-p", strconv.Itoa(app.RNDCPort),
- "-c", app.RNDCKeyFile, "reload", app.RNDCZone)
- err := cmd.Run()
- if err != nil {
- app.log.Errorf("Unexplected error while attempting to reload zone '%s' on DNS server '%s:%d' : %s", app.RNDCZone, app.RNDCAddress, app.RNDCPort, err)
} else {
- app.log.Infof("Successfully reloaded DNS zone '%s' on server '%s:%d' via RNDC command",
- app.RNDCZone, app.RNDCAddress, app.RNDCPort)
+ app.log.Infof("Discovered %d active, not not verified, DHCP leases", len(leases))
}
- }
- // process the results of the parse to internal data structures
- app.interchange.Lock()
- app.leases = leases
- app.byHostname = make(map[string]*Lease)
- app.byHardware = make(map[string]*Lease)
- for _, lease := range leases {
- app.byHostname[lease.ClientHostname] = lease
- app.byHardware[lease.HardwareAddress.String()] = lease
+ // if configured to output the lease information to a file, do so
+ if len(app.OutputFile) > 0 {
+ app.log.Infof("Writing lease information to file '%s'", app.OutputFile)
+ out, err := os.Create(app.OutputFile)
+ if err != nil {
+ app.log.Errorf(
+ "unexpected error while attempting to open file `%s' for output : %s",
+ app.OutputFile, err)
+ } else {
+ table := tabwriter.NewWriter(out, 1, 0, 4, ' ', 0)
+ for _, lease := range leases {
+ if err := app.outputTemplate.Execute(table, lease); err != nil {
+ app.log.Errorf(
+ "unexpected error while writing leases to file '%s' : %s",
+ app.OutputFile, err)
+ break
+ }
+ fmt.Fprintln(table)
+ }
+ table.Flush()
+ }
+ out.Close()
+ }
+
+ // if configured to reload the DNS server, then use the RNDC command to do so
+ if app.RNDCUpdate {
+ cmd := exec.Command("rndc", "-s", app.RNDCAddress, "-p", strconv.Itoa(app.RNDCPort),
+ "-c", app.RNDCKeyFile, "reload", app.RNDCZone)
+ err := cmd.Run()
+ if err != nil {
+ app.log.Errorf("Unexplected error while attempting to reload zone '%s' on DNS server '%s:%d' : %s", app.RNDCZone, app.RNDCAddress, app.RNDCPort, err)
+ } else {
+ app.log.Infof("Successfully reloaded DNS zone '%s' on server '%s:%d' via RNDC command",
+ app.RNDCZone, app.RNDCAddress, app.RNDCPort)
+ }
+ }
+
+ // process the results of the parse to internal data structures
+ app.interchange.Lock()
+ app.leases = leases
+ app.byHostname = make(map[string]*Lease)
+ app.byHardware = make(map[string]*Lease)
+ for _, lease := range leases {
+ app.byHostname[lease.ClientHostname] = lease
+ app.byHardware[lease.HardwareAddress.String()] = lease
+ }
+ leases = nil
+ app.interchange.Unlock()
}
- leases = nil
- app.interchange.Unlock()
}
if last == nil {
last = &time.Time{}
diff --git a/roles/maas/templates/automation-compose.yml.j2 b/roles/maas/templates/automation-compose.yml.j2
index 3da052e..237b430 100644
--- a/roles/maas/templates/automation-compose.yml.j2
+++ b/roles/maas/templates/automation-compose.yml.j2
@@ -132,6 +132,7 @@
- "lab.solution=cord"
- "lab.component=harvester"
volumes:
+ - "/etc/dhcp:/reservations"
- "/var/lib/maas/dhcp:/harvester"
- "/etc/bind/maas:/bind"
- "/etc/bind/maas:/key"