// TestServer returns a test server. // The ports can be retreived with server.LocalAddr(). The testserver itself can be stopped // with Stop(). It just takes a normal Corefile as input. func TestServer(t *testing.T, corefile string) (*server.Server, error) { crfile := CorefileInput{Contents: []byte(corefile)} configs, err := loadConfigs(path.Base(crfile.Path()), bytes.NewReader(crfile.Body())) if err != nil { return nil, err } groupings, err := arrangeBindings(configs) if err != nil { return nil, err } t.Logf("Starting %d servers", len(groupings)) group := groupings[0] s, err := server.New(group.BindAddr.String(), group.Configs, time.Second) return s, err }
// startServers starts all the servers in groupings, // taking into account whether or not this process is // a child from a graceful restart or not. It blocks // until the servers are listening. func startServers(groupings bindingGroup) error { var startupWg sync.WaitGroup errChan := make(chan error, len(groupings)) // must be buffered to allow Serve functions below to return if stopped later for _, group := range groupings { s, err := server.New(group.BindAddr.String(), group.Configs, GracefulTimeout) if err != nil { return err } // TODO(miek): does not work, because this callback uses http instead of dns // s.ReqCallback = https.RequestCallback // ensures we can solve ACME challenges while running if s.OnDemandTLS { s.TLSConfig.GetCertificate = https.GetOrObtainCertificate // TLS on demand -- awesome! } else { s.TLSConfig.GetCertificate = https.GetCertificate } var ( ln net.Listener pc net.PacketConn ) if IsRestart() { // Look up this server's listener in the map of inherited file descriptors; if we don't have one, we must make a new one (later). if fdIndex, ok := loadedGob.ListenerFds["tcp"+s.Addr]; ok { file := os.NewFile(fdIndex, "") fln, err := net.FileListener(file) if err != nil { return err } ln, ok = fln.(*net.TCPListener) if !ok { return errors.New("listener for " + s.Addr + " was not a *net.TCPListener") } file.Close() delete(loadedGob.ListenerFds, "tcp"+s.Addr) } if fdIndex, ok := loadedGob.ListenerFds["udp"+s.Addr]; ok { file := os.NewFile(fdIndex, "") fpc, err := net.FilePacketConn(file) if err != nil { return err } pc, ok = fpc.(*net.UDPConn) if !ok { return errors.New("packetConn for " + s.Addr + " was not a *net.PacketConn") } file.Close() delete(loadedGob.ListenerFds, "udp"+s.Addr) } } wg.Add(1) go func(s *server.Server, ln net.Listener, pc net.PacketConn) { defer wg.Done() // run startup functions that should only execute when the original parent process is starting. if !IsRestart() && !startedBefore { err := s.RunFirstStartupFuncs() if err != nil { errChan <- err return } } // start the server if ln != nil && pc != nil { errChan <- s.Serve(ln, pc) } else { errChan <- s.ListenAndServe() } }(s, ln, pc) startupWg.Add(1) go func(s *server.Server) { defer startupWg.Done() s.WaitUntilStarted() }(s) serversMu.Lock() servers = append(servers, s) serversMu.Unlock() } // Close the remaining (unused) file descriptors to free up resources if IsRestart() { for key, fdIndex := range loadedGob.ListenerFds { os.NewFile(fdIndex, "").Close() delete(loadedGob.ListenerFds, key) } } // Wait for all servers to finish starting startupWg.Wait() // Return the first error, if any select { case err := <-errChan: // "use of closed network connection" is normal if it was a graceful shutdown if err != nil && !strings.Contains(err.Error(), "use of closed network connection") { return err } default: } return nil }