mirror of
https://github.com/danderson/netboot.git
synced 2025-10-16 01:51:19 +02:00
pixiecore: set Content-Length when serving the kernel/initrd.
iPXE appears to have *really* poor performance (orders of magnitude worse) if it doesn't know the length of the kernel/initrds that it's downloading. Without this change, booting CoreOS takes longer than I've had patience to wait. With this change, the bottleneck becomes the network transfer speed. Fixes #10.
This commit is contained in:
parent
d040347fbd
commit
3aa769557c
@ -71,22 +71,31 @@ func (s *staticBooter) BootSpec(m Machine) (*Spec, error) {
|
|||||||
return s.spec, nil
|
return s.spec, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *staticBooter) serveFile(path string) (io.ReadCloser, error) {
|
func (s *staticBooter) serveFile(path string) (io.ReadCloser, int64, error) {
|
||||||
if strings.HasPrefix(path, "http://") || strings.HasPrefix(path, "https://") {
|
if strings.HasPrefix(path, "http://") || strings.HasPrefix(path, "https://") {
|
||||||
resp, err := http.Get(path)
|
resp, err := http.Get(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, -1, err
|
||||||
}
|
}
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
resp.Body.Close()
|
resp.Body.Close()
|
||||||
return nil, fmt.Errorf("%s: %s", path, http.StatusText(resp.StatusCode))
|
return nil, -1, fmt.Errorf("%s: %s", path, http.StatusText(resp.StatusCode))
|
||||||
}
|
}
|
||||||
return resp.Body, nil
|
return resp.Body, resp.ContentLength, nil
|
||||||
}
|
}
|
||||||
return os.Open(path)
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, -1, err
|
||||||
|
}
|
||||||
|
fi, err := f.Stat()
|
||||||
|
if err != nil {
|
||||||
|
f.Close()
|
||||||
|
return nil, -1, err
|
||||||
|
}
|
||||||
|
return f, fi.Size(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *staticBooter) ReadBootFile(id ID) (io.ReadCloser, error) {
|
func (s *staticBooter) ReadBootFile(id ID) (io.ReadCloser, int64, error) {
|
||||||
path := string(id)
|
path := string(id)
|
||||||
switch {
|
switch {
|
||||||
case path == "kernel":
|
case path == "kernel":
|
||||||
@ -95,19 +104,19 @@ func (s *staticBooter) ReadBootFile(id ID) (io.ReadCloser, error) {
|
|||||||
case strings.HasPrefix(path, "initrd-"):
|
case strings.HasPrefix(path, "initrd-"):
|
||||||
i, err := strconv.Atoi(string(path[7:]))
|
i, err := strconv.Atoi(string(path[7:]))
|
||||||
if err != nil || i < 0 || i >= len(s.initrd) {
|
if err != nil || i < 0 || i >= len(s.initrd) {
|
||||||
return nil, fmt.Errorf("no file with ID %q", id)
|
return nil, -1, fmt.Errorf("no file with ID %q", id)
|
||||||
}
|
}
|
||||||
return s.serveFile(s.initrd[i])
|
return s.serveFile(s.initrd[i])
|
||||||
|
|
||||||
case strings.HasPrefix(path, "other-"):
|
case strings.HasPrefix(path, "other-"):
|
||||||
i, err := strconv.Atoi(string(path[6:]))
|
i, err := strconv.Atoi(string(path[6:]))
|
||||||
if err != nil || i < 0 || i >= len(s.otherIDs) {
|
if err != nil || i < 0 || i >= len(s.otherIDs) {
|
||||||
return nil, fmt.Errorf("no file with ID %q", id)
|
return nil, -1, fmt.Errorf("no file with ID %q", id)
|
||||||
}
|
}
|
||||||
return s.serveFile(s.otherIDs[i])
|
return s.serveFile(s.otherIDs[i])
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("no file with ID %q", id)
|
return nil, -1, fmt.Errorf("no file with ID %q", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *staticBooter) WriteBootFile(ID, io.Reader) error {
|
func (s *staticBooter) WriteBootFile(ID, io.Reader) error {
|
||||||
@ -229,18 +238,22 @@ func (b *apibooter) BootSpec(m Machine) (*Spec, error) {
|
|||||||
return &ret, nil
|
return &ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *apibooter) ReadBootFile(id ID) (io.ReadCloser, error) {
|
func (b *apibooter) ReadBootFile(id ID) (io.ReadCloser, int64, error) {
|
||||||
urlStr, err := getURL(id, &b.key)
|
urlStr, err := getURL(id, &b.key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, -1, err
|
||||||
}
|
}
|
||||||
|
|
||||||
u, err := url.Parse(urlStr)
|
u, err := url.Parse(urlStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%q is not an URL", urlStr)
|
return nil, -1, fmt.Errorf("%q is not an URL", urlStr)
|
||||||
}
|
}
|
||||||
var ret io.ReadCloser
|
var (
|
||||||
|
ret io.ReadCloser
|
||||||
|
sz int64 = -1
|
||||||
|
)
|
||||||
if u.Scheme == "file" {
|
if u.Scheme == "file" {
|
||||||
|
// TODO serveFile
|
||||||
ret, err = os.Open(u.Path)
|
ret, err = os.Open(u.Path)
|
||||||
} else {
|
} else {
|
||||||
// urlStr will get reparsed by http.Get, which is mildly
|
// urlStr will get reparsed by http.Get, which is mildly
|
||||||
@ -248,18 +261,18 @@ func (b *apibooter) ReadBootFile(id ID) (io.ReadCloser, error) {
|
|||||||
// Request.
|
// Request.
|
||||||
resp, err := http.Get(urlStr)
|
resp, err := http.Get(urlStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, -1, err
|
||||||
}
|
}
|
||||||
if resp.StatusCode != 200 {
|
if resp.StatusCode != 200 {
|
||||||
return nil, fmt.Errorf("GET %q failed: %s", urlStr, resp.Status)
|
return nil, -1, fmt.Errorf("GET %q failed: %s", urlStr, resp.Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
ret, err = resp.Body, nil
|
ret, sz, err = resp.Body, resp.ContentLength, nil
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, -1, err
|
||||||
}
|
}
|
||||||
return ret, nil
|
return ret, sz, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *apibooter) WriteBootFile(id ID, body io.Reader) error {
|
func (b *apibooter) WriteBootFile(id ID, body io.Reader) error {
|
||||||
|
@ -43,7 +43,7 @@ func mustWrite(dir, path, contents string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func mustRead(f io.ReadCloser, err error) string {
|
func mustRead(f io.ReadCloser, sz int64, err error) string {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@ -52,6 +52,9 @@ func mustRead(f io.ReadCloser, err error) string {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
if sz >= 0 && int64(len(bs)) != sz {
|
||||||
|
panic(fmt.Errorf("sz = %d, but ReadCloser has %d bytes", sz, len(bs)))
|
||||||
|
}
|
||||||
return string(bs)
|
return string(bs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,6 @@ package cli // import "go.universe.tf/netboot/pixiecore/cli"
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@ -71,7 +70,7 @@ func todo(msg string, args ...interface{}) {
|
|||||||
func serverConfigFlags(cmd *cobra.Command) {
|
func serverConfigFlags(cmd *cobra.Command) {
|
||||||
cmd.Flags().BoolP("debug", "d", false, "Log more things that aren't directly related to booting a recognized client")
|
cmd.Flags().BoolP("debug", "d", false, "Log more things that aren't directly related to booting a recognized client")
|
||||||
cmd.Flags().BoolP("log-timestamps", "t", false, "Add a timestamp to each log line")
|
cmd.Flags().BoolP("log-timestamps", "t", false, "Add a timestamp to each log line")
|
||||||
cmd.Flags().IPP("listen-addr", "l", net.IPv4zero, "IPv4 address to listen on")
|
cmd.Flags().StringP("listen-addr", "l", "", "IPv4 address to listen on")
|
||||||
cmd.Flags().IntP("port", "p", 80, "Port to listen on for HTTP")
|
cmd.Flags().IntP("port", "p", 80, "Port to listen on for HTTP")
|
||||||
cmd.Flags().Bool("dhcp-no-bind", false, "Handle DHCP traffic without binding to the DHCP server port")
|
cmd.Flags().Bool("dhcp-no-bind", false, "Handle DHCP traffic without binding to the DHCP server port")
|
||||||
cmd.Flags().String("ipxe-bios", "", "Path to an iPXE binary for BIOS/UNDI")
|
cmd.Flags().String("ipxe-bios", "", "Path to an iPXE binary for BIOS/UNDI")
|
||||||
@ -97,7 +96,7 @@ func serverFromFlags(cmd *cobra.Command) *pixiecore.Server {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf("Error reading flag: %s", err)
|
fatalf("Error reading flag: %s", err)
|
||||||
}
|
}
|
||||||
addr, err := cmd.Flags().GetIP("listen-addr")
|
addr, err := cmd.Flags().GetString("listen-addr")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf("Error reading flag: %s", err)
|
fatalf("Error reading flag: %s", err)
|
||||||
}
|
}
|
||||||
@ -122,9 +121,6 @@ func serverFromFlags(cmd *cobra.Command) *pixiecore.Server {
|
|||||||
fatalf("Error reading flag: %s", err)
|
fatalf("Error reading flag: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if addr != nil && addr.To4() == nil {
|
|
||||||
fatalf("Listen address must be IPv4")
|
|
||||||
}
|
|
||||||
if httpPort <= 0 {
|
if httpPort <= 0 {
|
||||||
fatalf("HTTP port must be >0")
|
fatalf("HTTP port must be >0")
|
||||||
}
|
}
|
||||||
@ -154,8 +150,8 @@ func serverFromFlags(cmd *cobra.Command) *pixiecore.Server {
|
|||||||
if debug {
|
if debug {
|
||||||
ret.Debug = ret.Log
|
ret.Debug = ret.Log
|
||||||
}
|
}
|
||||||
if addr != nil {
|
if addr != "" {
|
||||||
ret.Address = addr.String()
|
ret.Address = addr
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
@ -108,13 +108,16 @@ func (s *Server) handleFile(w http.ResponseWriter, r *http.Request) {
|
|||||||
http.Error(w, "missing filename", http.StatusBadRequest)
|
http.Error(w, "missing filename", http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := s.Booter.ReadBootFile(ID(name))
|
f, sz, err := s.Booter.ReadBootFile(ID(name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log("HTTP", "Error getting file %q (query %q from %s): %s", name, r.URL, r.RemoteAddr, err)
|
s.log("HTTP", "Error getting file %q (query %q from %s): %s", name, r.URL, r.RemoteAddr, err)
|
||||||
http.Error(w, "couldn't get file", http.StatusInternalServerError)
|
http.Error(w, "couldn't get file", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
if sz >= 0 {
|
||||||
|
w.Header().Set("Content-Length", strconv.FormatInt(sz, 10))
|
||||||
|
}
|
||||||
if _, err = io.Copy(w, f); err != nil {
|
if _, err = io.Copy(w, f); err != nil {
|
||||||
s.log("HTTP", "Copy of %q to %s (query %q) failed: %s", name, r.RemoteAddr, r.URL, err)
|
s.log("HTTP", "Copy of %q to %s (query %q) failed: %s", name, r.RemoteAddr, r.URL, err)
|
||||||
return
|
return
|
||||||
|
@ -28,9 +28,11 @@ import (
|
|||||||
|
|
||||||
type booterFunc func(Machine) (*Spec, error)
|
type booterFunc func(Machine) (*Spec, error)
|
||||||
|
|
||||||
func (b booterFunc) BootSpec(m Machine) (*Spec, error) { return b(m) }
|
func (b booterFunc) BootSpec(m Machine) (*Spec, error) { return b(m) }
|
||||||
func (b booterFunc) ReadBootFile(id ID) (io.ReadCloser, error) { return nil, errors.New("no") }
|
func (b booterFunc) ReadBootFile(id ID) (io.ReadCloser, int64, error) {
|
||||||
func (b booterFunc) WriteBootFile(id ID, r io.Reader) error { return errors.New("no") }
|
return nil, -1, errors.New("no")
|
||||||
|
}
|
||||||
|
func (b booterFunc) WriteBootFile(id ID, r io.Reader) error { return errors.New("no") }
|
||||||
|
|
||||||
var logSync sync.Mutex
|
var logSync sync.Mutex
|
||||||
|
|
||||||
@ -149,8 +151,9 @@ boot kernel initrd=initrd0 initrd=initrd1 thing=http://localhost:1234/_/file?nam
|
|||||||
type readBootFile string
|
type readBootFile string
|
||||||
|
|
||||||
func (b readBootFile) BootSpec(m Machine) (*Spec, error) { return nil, nil }
|
func (b readBootFile) BootSpec(m Machine) (*Spec, error) { return nil, nil }
|
||||||
func (b readBootFile) ReadBootFile(id ID) (io.ReadCloser, error) {
|
func (b readBootFile) ReadBootFile(id ID) (io.ReadCloser, int64, error) {
|
||||||
return ioutil.NopCloser(bytes.NewBuffer([]byte(fmt.Sprintf("%s %s", id, b)))), nil
|
d := fmt.Sprintf("%s %s", id, b)
|
||||||
|
return ioutil.NopCloser(bytes.NewBuffer([]byte(d))), int64(len(d)), nil
|
||||||
}
|
}
|
||||||
func (b readBootFile) WriteBootFile(id ID, r io.Reader) error { return errors.New("no") }
|
func (b readBootFile) WriteBootFile(id ID, r io.Reader) error { return errors.New("no") }
|
||||||
|
|
||||||
|
@ -102,7 +102,12 @@ type Booter interface {
|
|||||||
// the client machine's request.
|
// the client machine's request.
|
||||||
BootSpec(m Machine) (*Spec, error)
|
BootSpec(m Machine) (*Spec, error)
|
||||||
// Get the bytes corresponding to an ID given in Spec.
|
// Get the bytes corresponding to an ID given in Spec.
|
||||||
ReadBootFile(id ID) (io.ReadCloser, error)
|
//
|
||||||
|
// Additionally returns the total number of bytes in the
|
||||||
|
// ReadCloser, or -1 if the size is unknown. Be warned, returning
|
||||||
|
// -1 will make the boot process orders of magnitude slower due to
|
||||||
|
// poor ipxe behavior.
|
||||||
|
ReadBootFile(id ID) (io.ReadCloser, int64, error)
|
||||||
// Write the given Reader to an ID given in Spec.
|
// Write the given Reader to an ID given in Spec.
|
||||||
WriteBootFile(id ID, body io.Reader) error
|
WriteBootFile(id ID, body io.Reader) error
|
||||||
}
|
}
|
||||||
@ -214,6 +219,8 @@ func (s *Server) Serve() error {
|
|||||||
// blocking.
|
// blocking.
|
||||||
s.errs = make(chan error, 5)
|
s.errs = make(chan error, 5)
|
||||||
|
|
||||||
|
s.debug("Init", "Starting Pixiecore goroutines")
|
||||||
|
|
||||||
go func() { s.errs <- s.serveDHCP(dhcp) }()
|
go func() { s.errs <- s.serveDHCP(dhcp) }()
|
||||||
go func() { s.errs <- s.servePXE(pxe) }()
|
go func() { s.errs <- s.servePXE(pxe) }()
|
||||||
go func() { s.errs <- s.serveTFTP(tftp) }()
|
go func() { s.errs <- s.serveTFTP(tftp) }()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user