api: Add iPXE initial support

This commit is contained in:
Dalton Hubble
2015-12-10 10:30:23 -08:00
parent a6b090a8ae
commit cb52319285
7 changed files with 149 additions and 63 deletions

View File

@@ -11,7 +11,7 @@ build-aci:
./acifile
run-docker:
docker run -p 8080:8080 --name=bcs -v $(shell echo $$PWD)/static:/static dghubble/bcs:latest
docker run -p 8080:8080 --name=bcs --rm -v $(shell echo $$PWD)/static:/static dghubble/bcs:latest
run-rkt:
rkt --insecure-options=image run --no-overlay bin/bcs-0.0.1-linux-amd64.aci

View File

@@ -1,19 +1,12 @@
package server
package api
import (
"fmt"
)
// CoreOSBootConfig references a CoreOS pproduction image and initrd.
var CoreOSBootConfig = &BootConfig{
Kernel: "/static/coreos_production_pxe.vmlinuz",
Initrd: []string{"/static/coreos_production_pxe_image.cpio.gz"},
Cmdline: map[string]interface{}{},
}
// BootConfig describes the boot configuration of a host.
type BootConfig struct {
// the URL of the kernel boot image
// the URL of the kernel boot image
Kernel string `json:"kernel"`
// the initrd URLs which will be flattened into a single filesystem
Initrd []string `json:"initrd"`
@@ -21,7 +14,7 @@ type BootConfig struct {
Cmdline map[string]interface{} `json:"cmdline"`
}
// A BootConfigProvider provides a mapping from MAC addresses to BootConfigs.
// A BootConfigProvider provides a mapping from MAC addresses to BootConfigs.
type BootConfigProvider interface {
Add(addr string, config *BootConfig)
Get(addr string) (*BootConfig, error)
@@ -53,4 +46,3 @@ func (p *bootConfigProvider) Get(addr string) (*BootConfig, error) {
}
return nil, fmt.Errorf("no boot config for %s", addr)
}

58
api/ipxe.go Normal file
View File

@@ -0,0 +1,58 @@
package api
import (
"bytes"
"fmt"
"net/http"
"text/template"
)
const ipxeBootstrap = `#!ipxe
chain config?uuid=${uuid}
`
var ipxeTemplate = template.Must(template.New("ipxe boot").Parse(`#!ipxe
kernel {{.Kernel}} cloud-config-url=cloud/config?uuid=${uuid} {{range $key, $value := .Cmdline}} {{if $value}}{{$key}}={{$value}}{{else}}{{$key}}{{end}}{{end}}
initrd {{ range $element := .Initrd }} {{$element}}{{end}}
boot
`))
// ipxeMux handles iPXE requests for boot (config) scripts.
func ipxeMux(bootConfigs BootConfigProvider) http.Handler {
mux := http.NewServeMux()
mux.Handle("/ipxe/boot.ipxe", ipxeInspect())
mux.Handle("/ipxe/config", ipxeBoot(bootConfigs))
return mux
}
// ipxeInspect returns a handler that responds with an iPXE script to gather
// client machine data and chain load the real boot script.
func ipxeInspect() http.Handler {
fn := func(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, ipxeBootstrap)
}
return http.HandlerFunc(fn)
}
// ipxeBoot returns a handler which renders an iPXE boot config script based
// on the machine attribtue query parameters.
func ipxeBoot(bootConfigs BootConfigProvider) http.Handler {
fn := func(w http.ResponseWriter, req *http.Request) {
params := req.URL.Query()
UUID := params.Get("uuid")
bootConfig, err := bootConfigs.Get(UUID)
if err != nil {
http.Error(w, err.Error(), 404)
return
}
var buf bytes.Buffer
err = ipxeTemplate.Execute(&buf, bootConfig)
if err != nil {
http.Error(w, err.Error(), 404)
return
}
buf.WriteTo(w)
}
return http.HandlerFunc(fn)
}

24
api/pixiecore.go Normal file
View File

@@ -0,0 +1,24 @@
package api
import (
"encoding/json"
"log"
"net/http"
"strings"
)
const pixiecorePath = "/v1/boot/"
// pixiecoreHandler implements the Pixiecore API Server Spec.
func pixiecoreHandler(bootConfigs BootConfigProvider) http.Handler {
fn := func(w http.ResponseWriter, req *http.Request) {
remainder := strings.TrimPrefix(req.URL.String(), pixiecorePath)
bootConfig, err := bootConfigs.Get(remainder)
if err != nil {
http.Error(w, err.Error(), 404)
return
}
json.NewEncoder(w).Encode(bootConfig)
}
return http.HandlerFunc(fn)
}

29
api/server.go Normal file
View File

@@ -0,0 +1,29 @@
package api
import (
"net/http"
)
// Server serves iPXE/Pixiecore boot configs and hosts images.
type Server struct {
bootConfigs BootConfigProvider
}
// NewServer returns a new Server which uses the given BootConfigProvider.
func NewServer(bootConfigs BootConfigProvider) *Server {
return &Server{
bootConfigs: bootConfigs,
}
}
// HTTPHandler returns a HTTP handler for the server.
func (s *Server) HTTPHandler() http.Handler {
mux := http.NewServeMux()
// iPXE
mux.Handle("/ipxe/", ipxeMux(s.bootConfigs))
// Pixiecore API Server
mux.Handle(pixiecorePath, pixiecoreHandler(s.bootConfigs))
// Kernel and Initrd Images
mux.Handle("/images/", http.StripPrefix("/images/", http.FileServer(http.Dir("static"))))
return mux
}

View File

@@ -4,19 +4,47 @@ import (
"log"
"net/http"
"github.com/coreos/coreos-baremetal/server"
"github.com/coreos/coreos-baremetal/api"
)
const address = ":8080"
// Example Boot Configs
var CoreOSStable = &api.BootConfig{
Kernel: "http://stable.release.core-os.net/amd64-usr/current/coreos_production_pxe.vmlinuz",
Initrd: []string{"http://stable.release.core-os.net/amd64-usr/current/coreos_production_pxe_image.cpio.gz"},
Cmdline: map[string]interface{}{},
}
var CoreOSBeta = &api.BootConfig{
Kernel: "http://beta.release.core-os.net/amd64-usr/current/coreos_production_pxe.vmlinuz",
Initrd: []string{"http://beta.release.core-os.net/amd64-usr/current/coreos_production_pxe_image.cpio.gz"},
Cmdline: map[string]interface{}{},
}
var CoreOSAlpha = &api.BootConfig{
Kernel: "http://alpha.release.core-os.net/amd64-usr/current/coreos_production_pxe.vmlinuz",
Initrd: []string{"http://alpha.release.core-os.net/amd64-usr/current/coreos_production_pxe_image.cpio.gz"},
Cmdline: map[string]interface{}{},
}
var CoreOSLocal = &api.BootConfig{
Kernel: "/images/kernel/coreos_production_pxe.vmlinuz",
Initrd: []string{"/images/initrd/coreos_production_pxe_image.cpio.gz"},
Cmdline: map[string]interface{}{},
}
func main() {
bootConfigProvider := server.NewBootConfigProvider()
bootConfigProvider.Add(server.DefaultAddr, server.CoreOSBootConfig)
srv := server.NewServer(bootConfigProvider)
h := srv.HTTPHandler()
// initial boot configs
bootConfigs := api.NewBootConfigProvider()
bootConfigs.Add(api.DefaultAddr, CoreOSStable)
// api server
server := api.NewServer(bootConfigs)
h := server.HTTPHandler()
log.Printf("Starting boot config server")
err := http.ListenAndServe(address, h)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
}

View File

@@ -1,45 +0,0 @@
package server
import (
"net/http"
"encoding/json"
"log"
"strings"
)
// Server manages boot and cloud configs for hosts by MAC address or UUID.
type Server struct {
bootConfigProvider BootConfigProvider
}
// NewServer returns a new Server which uses the given BootConfigProvider.
func NewServer(bootConfigProvider BootConfigProvider) *Server {
return &Server{
bootConfigProvider: bootConfigProvider,
}
}
// HTTPHandler returns a HTTP handler for the server.
func (s *Server) HTTPHandler() http.Handler {
mux := http.NewServeMux()
// Pixiecore API Server
mux.Handle("/v1/boot/", pixiecoreHandler(s.bootConfigProvider))
// Kernel and File Server
mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
return mux
}
// pixiecoreHandler implements the Pixiecore API Server Spec.
func pixiecoreHandler(bootConfigs BootConfigProvider) http.Handler {
fn := func(w http.ResponseWriter, req *http.Request) {
remainder := strings.TrimPrefix(req.URL.String(), "/v1/boot/")
log.Printf("render boot config for %s", remainder)
bootConfig, err := bootConfigs.Get(remainder)
if err != nil {
http.Error(w, err.Error(), 404)
return
}
json.NewEncoder(w).Encode(bootConfig)
}
return http.HandlerFunc(fn)
}