From a2ea8c2f950c3efaf390f9c5fe11f5e5b9f7b87f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 31 Jan 2020 19:00:51 +0100 Subject: [PATCH] cmd/dhcp4d: refactor for testing, add /lease/ test --- cmd/dhcp4d/dhcp4d.go | 49 ++++++++++++++----- cmd/dhcp4d/dhcp4d_test.go | 99 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+), 13 deletions(-) create mode 100644 cmd/dhcp4d/dhcp4d_test.go diff --git a/cmd/dhcp4d/dhcp4d.go b/cmd/dhcp4d/dhcp4d.go index 88b5039..ab68829 100644 --- a/cmd/dhcp4d/dhcp4d.go +++ b/cmd/dhcp4d/dhcp4d.go @@ -17,6 +17,7 @@ package main import ( "bytes" + "context" "encoding/json" "flag" "fmt" @@ -27,6 +28,7 @@ import ( "net/http" "os" "os/signal" + "path/filepath" "sort" "strings" "sync" @@ -220,10 +222,15 @@ func updateListeners() error { return nil } -func logic() error { +type srv struct { + errs chan error + leases func(newLeases []*dhcp4d.Lease, latest *dhcp4d.Lease) +} + +func newSrv(permDir string) (*srv, error) { http.Handle("/metrics", promhttp.Handler()) if err := updateListeners(); err != nil { - return err + return nil, err } go func() { ch := make(chan os.Signal, 1) @@ -235,20 +242,20 @@ func logic() error { } }() - if err := os.MkdirAll("/perm/dhcp4d", 0755); err != nil { - return err + if err := os.MkdirAll(filepath.Join(permDir, "dhcp4d"), 0755); err != nil { + return nil, err } errs := make(chan error) ifc, err := net.InterfaceByName(*iface) if err != nil { - return err + return nil, err } - handler, err := dhcp4d.NewHandler("/perm", ifc, *iface, nil) + handler, err := dhcp4d.NewHandler(permDir, ifc, *iface, nil) if err != nil { - return err + return nil, err } - if err := loadLeases(handler, "/perm/dhcp4d/leases.json"); err != nil { - return err + if err := loadLeases(handler, filepath.Join(permDir, "dhcp4d/leases.json")); err != nil { + return nil, err } http.HandleFunc("/lease/", func(w http.ResponseWriter, r *http.Request) { @@ -356,7 +363,7 @@ func logic() error { if err := json.Indent(&out, b, "", "\t"); err == nil { b = out.Bytes() } - if err := renameio.WriteFile("/perm/dhcp4d/leases.json", out.Bytes(), 0644); err != nil { + if err := renameio.WriteFile(filepath.Join(permDir, "dhcp4d/leases.json"), out.Bytes(), 0644); err != nil { errs <- err } updateNonExpired(leases) @@ -366,18 +373,34 @@ func logic() error { } conn, err := conn.NewUDP4BoundListener(*iface, ":67") if err != nil { - return err + return nil, err } go func() { errs <- dhcp4.Serve(conn, handler) }() - return <-errs + return &srv{ + errs, + handler.Leases, + }, nil +} + +func (s *srv) run(ctx context.Context) error { + select { + case <-ctx.Done(): + return ctx.Err() + case err := <-s.errs: + return err + } } func main() { // TODO: drop privileges, run as separate uid? flag.Parse() - if err := logic(); err != nil { + srv, err := newSrv("/perm") + if err != nil { + log.Fatal(err) + } + if err := srv.run(context.Background()); err != nil { log.Fatal(err) } } diff --git a/cmd/dhcp4d/dhcp4d_test.go b/cmd/dhcp4d/dhcp4d_test.go new file mode 100644 index 0000000..ecdb9ca --- /dev/null +++ b/cmd/dhcp4d/dhcp4d_test.go @@ -0,0 +1,99 @@ +// Copyright 2019 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "context" + "encoding/json" + "flag" + "io/ioutil" + "net" + "net/http" + "os" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/rtr7/router7/internal/dhcp4d" + "golang.org/x/sync/errgroup" +) + +const interfacesJson = ` +{ + "interfaces": [ + { + "name": "lo", + "addr": "192.0.2.1/24" + } + ] +} +` + +func TestLeaseHandler(t *testing.T) { + flag.Set("interface", "lo") + ctx, canc := context.WithCancel(context.Background()) + defer canc() + tmp, err := ioutil.TempDir("", "dhcp4dtest") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmp) + if err := ioutil.WriteFile(filepath.Join(tmp, "interfaces.json"), []byte(interfacesJson), 0644); err != nil { + t.Fatal(err) + } + srv, err := newSrv(tmp) + if err != nil { + t.Fatal(err) + } + var eg errgroup.Group + eg.Go(func() error { return srv.run(ctx) }) + lease := dhcp4d.Lease{ + Num: 74, + Addr: net.ParseIP("10.0.0.76"), + HardwareAddr: "02:73:53:00:ca:fe", + Hostname: "midna", + Expiry: time.Now().Add(20 * time.Minute), + } + srv.leases([]*dhcp4d.Lease{&lease}, &lease) + req, err := http.NewRequest("GET", "http://localhost:8067/lease/midna", nil) + if err != nil { + t.Fatal(err) + } + resp, err := http.DefaultClient.Do(req) + if err != nil { + t.Fatal(err) + } + if got, want := resp.StatusCode, http.StatusOK; got != want { + b, _ := ioutil.ReadAll(resp.Body) + t.Fatalf("unexpected HTTP response code: got %v (%s), want %v", resp.Status, strings.TrimSpace(string(b)), want) + } + b, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatal(err) + } + var got dhcp4d.Lease + if err := json.Unmarshal(b, &got); err != nil { + t.Fatal(err) + } + if diff := cmp.Diff(lease, got); diff != "" { + t.Fatalf("/lease/midna: unexpected lease: diff (-want +got):\n%s", diff) + } + canc() + if err := eg.Wait(); err != nil && err != context.Canceled { + t.Fatal(err) + } +}