From 633f4410216740f0078cc85603cd9980061e2b9e Mon Sep 17 00:00:00 2001 From: Dalton Hubble Date: Fri, 11 Dec 2015 03:07:00 -0800 Subject: [PATCH] api: Add BootAdapter with an in-memory impl and UUID matching --- api/adapter.go | 53 ++++++++++++++++++++++++++++++++++++++++++++ api/boot.go | 18 +++++++++++++++ api/boot_provider.go | 48 --------------------------------------- api/ipxe.go | 8 +++---- api/machine.go | 11 +++++++++ api/pixiecore.go | 11 +++++---- api/server.go | 6 ++--- cmd/main.go | 6 ++--- 8 files changed, 99 insertions(+), 62 deletions(-) create mode 100644 api/adapter.go create mode 100644 api/boot.go delete mode 100644 api/boot_provider.go create mode 100644 api/machine.go diff --git a/api/adapter.go b/api/adapter.go new file mode 100644 index 00000000..31b5d965 --- /dev/null +++ b/api/adapter.go @@ -0,0 +1,53 @@ +package api + +import ( + "fmt" + "net" +) + +// MapBootAdapter maps MachineAttrs to BootConfigs using an in-memory map. +type MapBootAdapter struct { + uuids map[string]*BootConfig + macs map[string]*BootConfig + fallback *BootConfig +} + +// NewMapBootAdapter returns a new in-memory BootAdapter. +func NewMapBootAdapter() *MapBootAdapter { + return &MapBootAdapter{ + uuids: make(map[string]*BootConfig), + macs: make(map[string]*BootConfig), + } +} + +// Get returns the BootConfig for the machine with the given attributes. +// Matches are searched in priority order: UUID, MAC address, default. +func (a *MapBootAdapter) Get(attrs MachineAttrs) (*BootConfig, error) { + if config, ok := a.uuids[attrs.UUID]; ok { + return config, nil + } + if config, ok := a.macs[attrs.MAC.String()]; ok { + return config, nil + } + if a.fallback != nil { + return a.fallback, nil + } + return nil, fmt.Errorf("no matching boot configuration") +} + +// AddUUID adds a BootConfig for the machine with the given UUID. +func (a *MapBootAdapter) AddUUID(uuid string, config *BootConfig) { + a.uuids[uuid] = config +} + +// AddMAC adds a BootConfig for the machine with NIC with the given MAC +// address. +func (a *MapBootAdapter) AddMAC(mac net.HardwareAddr, config *BootConfig) { + a.macs[mac.String()] = config +} + +// SetDefault sets the default or fallback BootConfig to use if no machine +// attribute matches are found. +func (a *MapBootAdapter) SetDefault(config *BootConfig) { + a.fallback = config +} diff --git a/api/boot.go b/api/boot.go new file mode 100644 index 00000000..46de984a --- /dev/null +++ b/api/boot.go @@ -0,0 +1,18 @@ +package api + +// A BootAdapter maps MachineAttrs to a BootConfig which should be used. +type BootAdapter interface { + // Get returns the BootConfig to boot the machine with the given attributes + Get(attrs MachineAttrs) (*BootConfig, error) +} + +// BootConfig defines the kernel image, kernel options, and initrds to boot +// on a client machine. +type BootConfig struct { + // 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"` + // command line arguments to the kernel + Cmdline map[string]interface{} `json:"cmdline"` +} diff --git a/api/boot_provider.go b/api/boot_provider.go deleted file mode 100644 index c79eaa8f..00000000 --- a/api/boot_provider.go +++ /dev/null @@ -1,48 +0,0 @@ -package api - -import ( - "fmt" -) - -// BootConfig describes the boot configuration of a host. -type BootConfig struct { - // 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"` - // command line arguments to the kernel - Cmdline map[string]interface{} `json:"cmdline"` -} - -// A BootConfigProvider provides a mapping from MAC addresses to BootConfigs. -type BootConfigProvider interface { - Add(addr string, config *BootConfig) - Get(addr string) (*BootConfig, error) -} - -// NewBootConfig returns a new memory map BootConfigProvider. -func NewBootConfigProvider() BootConfigProvider { - return &bootConfigProvider{ - mac2boot: make(map[string]*BootConfig), - } -} - -const DefaultAddr = "default" - -// bootConfigProvider implements a MAC address to Boot config map in memory. -type bootConfigProvider struct { - mac2boot map[string]*BootConfig -} - -func (p *bootConfigProvider) Add(addr string, config *BootConfig) { - p.mac2boot[addr] = config -} - -func (p *bootConfigProvider) Get(addr string) (*BootConfig, error) { - if config, ok := p.mac2boot[addr]; ok { - return config, nil - } else if config, ok := p.mac2boot[DefaultAddr]; ok { - return config, nil - } - return nil, fmt.Errorf("no boot config for %s", addr) -} diff --git a/api/ipxe.go b/api/ipxe.go index cecfbcd0..3eab5254 100644 --- a/api/ipxe.go +++ b/api/ipxe.go @@ -18,7 +18,7 @@ boot `)) // ipxeMux handles iPXE requests for boot (config) scripts. -func ipxeMux(bootConfigs BootConfigProvider) http.Handler { +func ipxeMux(bootConfigs BootAdapter) http.Handler { mux := http.NewServeMux() mux.Handle("/ipxe/boot.ipxe", ipxeInspect()) mux.Handle("/ipxe/config", ipxeBoot(bootConfigs)) @@ -36,11 +36,11 @@ func ipxeInspect() http.Handler { // ipxeBoot returns a handler which renders an iPXE boot config script based // on the machine attribtue query parameters. -func ipxeBoot(bootConfigs BootConfigProvider) http.Handler { +func ipxeBoot(bootConfigs BootAdapter) http.Handler { fn := func(w http.ResponseWriter, req *http.Request) { params := req.URL.Query() - UUID := params.Get("uuid") - bootConfig, err := bootConfigs.Get(UUID) + attrs := MachineAttrs{UUID: params.Get("uuid")} + bootConfig, err := bootConfigs.Get(attrs) if err != nil { http.Error(w, err.Error(), 404) return diff --git a/api/machine.go b/api/machine.go new file mode 100644 index 00000000..55edb29b --- /dev/null +++ b/api/machine.go @@ -0,0 +1,11 @@ +package api + +import ( + "net" +) + +// MachineAttrs collects machine identifiers and attributes. +type MachineAttrs struct { + UUID string + MAC net.HardwareAddr +} diff --git a/api/pixiecore.go b/api/pixiecore.go index 0bff9243..966b03c4 100644 --- a/api/pixiecore.go +++ b/api/pixiecore.go @@ -2,18 +2,21 @@ package api import ( "encoding/json" - "log" + "net" "net/http" "strings" ) const pixiecorePath = "/v1/boot/" -// pixiecoreHandler implements the Pixiecore API Server Spec. -func pixiecoreHandler(bootConfigs BootConfigProvider) http.Handler { +// pixiecoreHandler returns a handler that renders Boot Configs as JSON to +// implement the Pixiecore API specification. +// https://github.com/danderson/pixiecore/blob/master/README.api.md +func pixiecoreHandler(bootConfigs BootAdapter) http.Handler { fn := func(w http.ResponseWriter, req *http.Request) { remainder := strings.TrimPrefix(req.URL.String(), pixiecorePath) - bootConfig, err := bootConfigs.Get(remainder) + attrs := MachineAttrs{MAC: net.HardwareAddr(remainder)} + bootConfig, err := bootConfigs.Get(attrs) if err != nil { http.Error(w, err.Error(), 404) return diff --git a/api/server.go b/api/server.go index 4def4f45..24dff827 100644 --- a/api/server.go +++ b/api/server.go @@ -6,11 +6,11 @@ import ( // Server serves iPXE/Pixiecore boot configs and hosts images. type Server struct { - bootConfigs BootConfigProvider + bootConfigs BootAdapter } -// NewServer returns a new Server which uses the given BootConfigProvider. -func NewServer(bootConfigs BootConfigProvider) *Server { +// NewServer returns a new Server which uses the given BootAdapter. +func NewServer(bootConfigs BootAdapter) *Server { return &Server{ bootConfigs: bootConfigs, } diff --git a/cmd/main.go b/cmd/main.go index 47f87f33..d519fb64 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -37,10 +37,10 @@ var CoreOSLocal = &api.BootConfig{ func main() { // initial boot configs - bootConfigs := api.NewBootConfigProvider() - bootConfigs.Add(api.DefaultAddr, CoreOSStable) + bootAdapter := api.NewMapBootAdapter() + bootAdapter.SetDefault(CoreOSStable) // api server - server := api.NewServer(bootConfigs) + server := api.NewServer(bootAdapter) h := server.HTTPHandler() log.Printf("Starting boot config server") err := http.ListenAndServe(address, h)