From 47f4e165a004c10661bb18f5994d283130c93fec Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Tue, 23 Aug 2016 16:36:29 +0100 Subject: [PATCH] Fix main startup (#232) Set version and name of the program. And then call coremain.Run(). The coremain split makes CoreDNS embeddable. Also see #189 for an old PR. --- README.md | 6 +- coredns.go | 28 +---- coremain/run.go | 223 ++++++++++++++++++++++++++++++++++++++ coremain/run_test.go | 44 ++++++++ middleware/chaos/setup.go | 2 +- plugin_generate.go | 48 -------- 6 files changed, 272 insertions(+), 79 deletions(-) create mode 100644 coremain/run.go create mode 100644 coremain/run_test.go delete mode 100644 plugin_generate.go diff --git a/README.md b/README.md index e7d62735c..de4676e5a 100644 --- a/README.md +++ b/README.md @@ -51,10 +51,8 @@ Caddyfile when I forked it). ## Compilation -CoreDNS (as a servertype plugin for Caddy) has a dependency on Caddy - this is *almost* like -the normal Go dependencies, but with a small twist: caddy (the source) need to know that CoreDNS -exists and for this we need to add 1 line `_ "github.com/miekg/coredns/core"` to file in caddy. - +CoreDNS (as a servertype plugin for Caddy) has a dependency on Caddy, but this is not different than +any other Go dependency. You have the source of CoreDNS, this should preferably be downloaded under your `$GOPATH`. Get all dependencies: diff --git a/coredns.go b/coredns.go index 26de37386..f03c218e9 100644 --- a/coredns.go +++ b/coredns.go @@ -1,31 +1,7 @@ package main -import ( - "flag" - - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddy/caddymain" -) - -//go:generate go run plugin_generate.go +import "github.com/miekg/coredns/coremain" func main() { - setFlag() - setName() - - caddymain.Run() + coremain.Run() } - -// setFlag sets flags to predefined values for CoreDNS. -func setFlag() { - flag.Set("type", "dns") -} - -// setName sets application name and versioning information for CoreDNS. -func setName() { - caddy.DefaultConfigFile = "Corefile" - caddy.AppName = "CoreDNS" - caddy.AppVersion = version -} - -const version = "001" diff --git a/coremain/run.go b/coremain/run.go new file mode 100644 index 000000000..1a5282bc4 --- /dev/null +++ b/coremain/run.go @@ -0,0 +1,223 @@ +package coremain + +import ( + "errors" + "flag" + "fmt" + "io/ioutil" + "log" + "os" + "runtime" + "strconv" + "strings" + + "github.com/mholt/caddy" + "gopkg.in/natefinch/lumberjack.v2" + + // Plug in CoreDNS + _ "github.com/miekg/coredns/core" +) + +func init() { + caddy.TrapSignals() + setVersion() + + flag.StringVar(&conf, "conf", "", "Caddyfile to load (default \""+caddy.DefaultConfigFile+"\")") + flag.StringVar(&cpu, "cpu", "100%", "CPU cap") + flag.BoolVar(&plugins, "plugins", false, "List installed plugins") + flag.StringVar(&logfile, "log", "", "Process log file") + flag.StringVar(&caddy.PidFile, "pidfile", "", "Path to write pid file") + flag.BoolVar(&caddy.Quiet, "quiet", false, "Quiet mode (no initialization output)") + flag.BoolVar(&version, "version", false, "Show version") + + caddy.RegisterCaddyfileLoader("flag", caddy.LoaderFunc(confLoader)) + caddy.SetDefaultCaddyfileLoader("default", caddy.LoaderFunc(defaultLoader)) +} + +// Run is CoreDNS's main() function. +func Run() { + flag.Parse() + + caddy.AppName = coreName + caddy.AppVersion = coreVersion + + // Set up process log before anything bad happens + switch logfile { + case "stdout": + log.SetOutput(os.Stdout) + case "stderr": + log.SetOutput(os.Stderr) + case "": + log.SetOutput(ioutil.Discard) + default: + log.SetOutput(&lumberjack.Logger{ + Filename: logfile, + MaxSize: 100, + MaxAge: 14, + MaxBackups: 10, + }) + } + + if version { + fmt.Printf("%s-%s\n", caddy.AppName, caddy.AppVersion) + if devBuild && gitShortStat != "" { + fmt.Printf("%s\n%s\n", gitShortStat, gitFilesModified) + } + os.Exit(0) + } + if plugins { + fmt.Println(caddy.DescribePlugins()) + os.Exit(0) + } + + // Set CPU cap + err := setCPU(cpu) + if err != nil { + mustLogFatal(err) + } + + // Get Caddyfile input + caddyfile, err := caddy.LoadCaddyfile(serverType) + if err != nil { + mustLogFatal(err) + } + + // Start your engines + instance, err := caddy.Start(caddyfile) + if err != nil { + mustLogFatal(err) + } + + // Twiddle your thumbs + instance.Wait() +} + +// mustLogFatal wraps log.Fatal() in a way that ensures the +// output is always printed to stderr so the user can see it +// if the user is still there, even if the process log was not +// enabled. If this process is an upgrade, however, and the user +// might not be there anymore, this just logs to the process +// log and exits. +func mustLogFatal(args ...interface{}) { + if !caddy.IsUpgrade() { + log.SetOutput(os.Stderr) + } + log.Fatal(args...) +} + +// confLoader loads the Caddyfile using the -conf flag. +func confLoader(serverType string) (caddy.Input, error) { + if conf == "" { + return nil, nil + } + + if conf == "stdin" { + return caddy.CaddyfileFromPipe(os.Stdin) + } + + contents, err := ioutil.ReadFile(conf) + if err != nil { + return nil, err + } + return caddy.CaddyfileInput{ + Contents: contents, + Filepath: conf, + ServerTypeName: serverType, + }, nil +} + +// defaultLoader loads the Caddyfile from the current working directory. +func defaultLoader(serverType string) (caddy.Input, error) { + contents, err := ioutil.ReadFile(caddy.DefaultConfigFile) + if err != nil { + if os.IsNotExist(err) { + return nil, nil + } + return nil, err + } + return caddy.CaddyfileInput{ + Contents: contents, + Filepath: caddy.DefaultConfigFile, + ServerTypeName: serverType, + }, nil +} + +// setVersion figures out the version information +// based on variables set by -ldflags. +func setVersion() { + // A development build is one that's not at a tag or has uncommitted changes + devBuild = gitTag == "" || gitShortStat != "" + + // Only set the appVersion if -ldflags was used + if gitNearestTag != "" || gitTag != "" { + if devBuild && gitNearestTag != "" { + appVersion = fmt.Sprintf("%s (+%s %s)", + strings.TrimPrefix(gitNearestTag, "v"), gitCommit, buildDate) + } else if gitTag != "" { + appVersion = strings.TrimPrefix(gitTag, "v") + } + } +} + +// setCPU parses string cpu and sets GOMAXPROCS +// according to its value. It accepts either +// a number (e.g. 3) or a percent (e.g. 50%). +func setCPU(cpu string) error { + var numCPU int + + availCPU := runtime.NumCPU() + + if strings.HasSuffix(cpu, "%") { + // Percent + var percent float32 + pctStr := cpu[:len(cpu)-1] + pctInt, err := strconv.Atoi(pctStr) + if err != nil || pctInt < 1 || pctInt > 100 { + return errors.New("invalid CPU value: percentage must be between 1-100") + } + percent = float32(pctInt) / 100 + numCPU = int(float32(availCPU) * percent) + } else { + // Number + num, err := strconv.Atoi(cpu) + if err != nil || num < 1 { + return errors.New("invalid CPU value: provide a number or percent greater than 0") + } + numCPU = num + } + + if numCPU > availCPU { + numCPU = availCPU + } + + runtime.GOMAXPROCS(numCPU) + return nil +} + +// Flags that control program flow or startup +var ( + conf string + cpu string + logfile string + version bool + plugins bool +) + +// Build information obtained with the help of -ldflags +var ( + appVersion = "(untracked dev build)" // inferred at startup + devBuild = true // inferred at startup + + buildDate string // date -u + gitTag string // git describe --exact-match HEAD 2> /dev/null + gitNearestTag string // git describe --abbrev=0 --tags HEAD + gitCommit string // git rev-parse HEAD + gitShortStat string // git diff-index --shortstat + gitFilesModified string // git diff-index --name-only HEAD +) + +const ( + coreName = "CoreDNS" + coreVersion = "001" + serverType = "dns" +) diff --git a/coremain/run_test.go b/coremain/run_test.go new file mode 100644 index 000000000..da01637d8 --- /dev/null +++ b/coremain/run_test.go @@ -0,0 +1,44 @@ +package coremain + +import ( + "runtime" + "testing" +) + +func TestSetCPU(t *testing.T) { + currentCPU := runtime.GOMAXPROCS(-1) + maxCPU := runtime.NumCPU() + halfCPU := int(0.5 * float32(maxCPU)) + if halfCPU < 1 { + halfCPU = 1 + } + for i, test := range []struct { + input string + output int + shouldErr bool + }{ + {"1", 1, false}, + {"-1", currentCPU, true}, + {"0", currentCPU, true}, + {"100%", maxCPU, false}, + {"50%", halfCPU, false}, + {"110%", currentCPU, true}, + {"-10%", currentCPU, true}, + {"invalid input", currentCPU, true}, + {"invalid input%", currentCPU, true}, + {"9999", maxCPU, false}, // over available CPU + } { + err := setCPU(test.input) + if test.shouldErr && err == nil { + t.Errorf("Test %d: Expected error, but there wasn't any", i) + } + if !test.shouldErr && err != nil { + t.Errorf("Test %d: Expected no error, but there was one: %v", i, err) + } + if actual, expected := runtime.GOMAXPROCS(-1), test.output; actual != expected { + t.Errorf("Test %d: GOMAXPROCS was %d but expected %d", i, actual, expected) + } + // teardown + runtime.GOMAXPROCS(currentCPU) + } +} diff --git a/middleware/chaos/setup.go b/middleware/chaos/setup.go index 8bdb3053e..2a584b3c9 100644 --- a/middleware/chaos/setup.go +++ b/middleware/chaos/setup.go @@ -47,4 +47,4 @@ func chaosParse(c *caddy.Controller) (string, map[string]bool, error) { return version, authors, nil } -const defaultVersion = "CoreDNS" +var defaultVersion = caddy.AppName + "-" + caddy.AppVersion diff --git a/plugin_generate.go b/plugin_generate.go deleted file mode 100644 index c52958e20..000000000 --- a/plugin_generate.go +++ /dev/null @@ -1,48 +0,0 @@ -//+build ignore - -package main - -import ( - "bytes" - "go/ast" - "go/parser" - "go/printer" - "go/token" - "io/ioutil" - "log" - - "golang.org/x/tools/go/ast/astutil" -) - -func GenerateFile(fset *token.FileSet, file *ast.File) ([]byte, error) { - var output []byte - buffer := bytes.NewBuffer(output) - if err := printer.Fprint(buffer, fset, file); err != nil { - return nil, err - } - - return buffer.Bytes(), nil -} - -func main() { - fset := token.NewFileSet() - f, err := parser.ParseFile(fset, caddyrun, nil, parser.ParseComments) - if err != nil { - log.Fatalf("failed to parse %s: %s", caddyrun, err) - } - astutil.AddNamedImport(fset, f, "_", coredns) - astutil.DeleteNamedImport(fset, f, "_", caddy) - - out, err := GenerateFile(fset, f) - if err := ioutil.WriteFile(caddyrun, out, 0644); err != nil { - log.Fatalf("failed to write go file: %s", err) - } -} - -const ( - coredns = "github.com/miekg/coredns/core" - caddy = "github.com/mholt/caddy/caddyhttp" - - // If everything is OK and we are sitting in CoreDNS' dir, this is where run.go should be. - caddyrun = "../../mholt/caddy/caddy/caddymain/run.go" -)