Initial commit of Warewulf v4 POC

This commit is contained in:
Gregory Kurtzer
2020-10-27 19:24:38 -04:00
parent a8b88db140
commit c168aa9418
15 changed files with 592 additions and 1 deletions

29
Makefile Normal file
View File

@@ -0,0 +1,29 @@
files:
sudo install -d -m 0755 /var/warewulf/provision
sudo install -d -m 0755 /var/warewulf/provision/kernels
sudo install -d -m 0755 /var/warewulf/provision/overlays
sudo install -d -m 0755 /var/warewulf/provision/bases
sudo install -d -m 0755 /etc/warewulf/
sudo install -m 0644 dhcpd.conf /etc/dhcp/dhcpd.conf
sudo install -m 0644 nodes.yaml /etc/warewulf/nodes.yaml
sudo cp -r tftpboot/* /var/lib/tftpboot/warewulf/ipxe/
sudo cp -r overlays /etc/warewulf/
services: files
sudo systemctl enable tftp
sudo systemctl restart tftp
sudo systemctl enable dhcpd
sudo systemctl restart dhcpd
build:
go build cmd/warewulfd/warewulfd.go
go build cmd/wwbuild/wwbuild.go
clean:
rm -f warewulfd
rm -f wwbuild
install: build files services

View File

@@ -1 +1,17 @@
# warewulf # warewulf
This is built on CentOS-7. More needs to be done to make it work on other
distributions and versions specifically with the system service
compoenents.
In a nutshell, to use:
1. make install
2. build VNFS `sudo singularity build --sandbox /var/chroots/centos-7 centos-7.def`
3. Edit `/etc/warewulf/nodes.yaml`
4. Run the following commands:
a. `./wwbuild vnfs`
b. `./wwbuild kernel`
c. `./wwbuild overlay`
c. `./warewulfd`
5. Boot your node and watch the console and the output of the Warewulfd process

17
centos-7.def Normal file
View File

@@ -0,0 +1,17 @@
BootStrap: yum
OSVersion: 7
MirrorURL: http://mirror.centos.org/centos-%{OSVERSION}/%{OSVERSION}/os/x86_64/
Include: yum
%post
sed -i 's/^root:\*:/root::/g' /etc/passwd
yum install -y basesystem bash chkconfig coreutils e2fsprogs \
ethtool filesystem findutils gawk grep initscripts iproute \
iputils net-tools nfs-utils pam psmisc rdate rsync sed setup \
shadow-utils rsyslog tcp_wrappers tzdata util-linux words \
zlib tar less gzip which util-linux openssh-clients \
openssh-server dhclient pciutils vim-minimal shadow-utils \
strace cronie crontabs cpio wget centos-release

117
cmd/warewulfd/warewulfd.go Normal file
View File

@@ -0,0 +1,117 @@
package main
import (
"fmt"
"strings"
"os"
"strconv"
"io"
"path"
"net/http"
"github.com/hpcng/warewulf/internal/pkg/assets"
)
const LocalStateDir = "/var/warewulf"
const SysConfDir = "/etc/warewulf"
func ipxe(w http.ResponseWriter, req *http.Request) {
url := strings.Split(req.URL.Path, "/");
if url[2] == "" {
fmt.Printf("ERROR: Bad iPXE request from %s\n", req.RemoteAddr)
return
}
node := assets.FindByHwaddr(url[2])
if node.HostName != "" {
fmt.Printf("IPXE: %15s: hwaddr=%s\n", node.Fqdn, url[2]);
fmt.Fprintf(w, "#!ipxe\n");
fmt.Fprintf(w, "echo Now booting Warewulf - v4 Proof of Concept\n");
fmt.Fprintf(w, "set base http://192.168.1.1:9873/\n")
fmt.Fprintf(w, "kernel ${base}/files/kernel/%s crashkernel=no quiet\n", url[2])
fmt.Fprintf(w, "initrd ${base}/files/vnfs/%s\n", url[2])
fmt.Fprintf(w, "initrd ${base}/files/kmods/%s\n", url[2])
fmt.Fprintf(w, "initrd ${base}/files/overlay/%s\n", url[2])
fmt.Fprintf(w, "boot\n");
} else {
fmt.Printf("ERROR: iPXE request from unknown Node (hwaddr=%s)\n", url[2])
}
return
}
func files(w http.ResponseWriter, req *http.Request) {
url := strings.Split(req.URL.Path, "/")
node := assets.FindByHwaddr(url[3])
fmt.Printf("GET: %15s: %s\n", node.Fqdn, req.URL.Path)
if url[2] == "kernel" {
if node.KernelVersion != "" {
kernelFile := fmt.Sprintf("%s/provision/kernels/vmlinuz-%s", LocalStateDir, node.KernelVersion);
sendFile(w, kernelFile, node.Fqdn);
}
} else if url[2] == "kmods" {
if node.KernelVersion != "" {
kmodsFile := fmt.Sprintf("%s/provision/kernels/kmods-%s.img", LocalStateDir, node.KernelVersion);
sendFile(w, kmodsFile, node.Fqdn);
}
} else if url[2] == "vnfs" {
if node.Vnfs != "" {
vnfsFile := fmt.Sprintf("%s/provision/bases/%s.img.gz", LocalStateDir, path.Base(node.Vnfs))
sendFile(w, vnfsFile, node.Fqdn);
}
} else if url[2] == "overlay" {
if node.Overlay!= "" {
overlayFile := fmt.Sprintf("%s/provision/overlays/%s.img", LocalStateDir, node.Fqdn);
sendFile(w, overlayFile, node.Fqdn);
}
}
return
}
func sendFile(w http.ResponseWriter, filename string, sendto string) {
fmt.Printf("SEND: %15s: %s\n", sendto, filename);
fd, err := os.Open(filename)
if err != nil {
fmt.Println("ERROR: %s\n", err);
return;
}
FileHeader := make([]byte, 512)
fd.Read(FileHeader)
FileContentType := http.DetectContentType(FileHeader)
FileStat, _ := fd.Stat()
FileSize := strconv.FormatInt(FileStat.Size(), 10)
w.Header().Set("Content-Disposition", "attachment; filename=kernel")
w.Header().Set("Content-Type", FileContentType)
w.Header().Set("Content-Length", FileSize)
fd.Seek(0, 0)
io.Copy(w, fd)
}
func main() {
http.HandleFunc("/ipxe/", ipxe)
http.HandleFunc("/files/", files)
http.ListenAndServe(":9873", nil)
}

86
cmd/wwbuild/wwbuild.go Normal file
View File

@@ -0,0 +1,86 @@
package main
import(
"os"
"os/exec"
"fmt"
"path"
"github.com/hpcng/warewulf/internal/pkg/assets"
)
const LocalStateDir = "/var/warewulf"
func main(){
if len(os.Args) < 2 {
fmt.Printf("USAGE: %s [vnfs/kernel/overlays/all]\n", os.Args[0]);
return
}
if os.Args[1] == "vnfs" {
for _, vnfs := range assets.FindAllVnfs() {
if _, err := os.Stat(vnfs); err == nil {
cmd := fmt.Sprintf("cd %s; find . | cpio --quiet -o -H newc | gzip -c > \"%s/provision/bases/%s.img.gz\"", vnfs, LocalStateDir, path.Base(vnfs));
fmt.Printf("BUILDING VNFS: %s\n", vnfs);
out, err := exec.Command("/bin/sh", "-c", cmd).Output();
if err != nil {
fmt.Printf("%s", err)
}
output := string(out[:])
fmt.Println(output)
} else {
fmt.Printf("SKIPPING VNFS: (bad path) %s\n", vnfs);
}
}
} else if os.Args[1] == "kernel" {
for _, kernelVers := range assets.FindAllKernels() {
kernelSource := fmt.Sprintf("/boot/vmlinuz-%s", kernelVers)
if _, err := os.Stat(kernelSource); err == nil {
kernelDestination := fmt.Sprintf("%s/provision/kernels/vmlinuz-%s", LocalStateDir, kernelVers);
fmt.Printf("SETUP KERNEL: %s (%s)\n", kernelSource, kernelDestination);
err := exec.Command("cp", kernelSource, kernelDestination).Run()
if err != nil {
fmt.Printf("%s", err)
}
kernelMods := fmt.Sprintf("/lib/modules/%s", kernelVers)
if _, err := os.Stat(kernelMods); err == nil {
fmt.Printf("BUILDING MODS: %s\n", kernelMods);
cmd := fmt.Sprintf("find %s | cpio --quiet -o -H newc -F \"%s/provision/kernels/kmods-%s.img\"", kernelMods, LocalStateDir, kernelVers);
err := exec.Command("/bin/sh", "-c", cmd).Run();
if err != nil {
fmt.Printf("OUTPUT: %s", err)
}
}
}
}
} else if os.Args[1] == "overlay" {
fmt.Printf("note: This needs to create an overlay for each node with macro expansions\n");
for _, node := range assets.FindAllNodes() {
overlayDir := fmt.Sprintf("/etc/warewulf/overlays/%s", node.Overlay);
if _, err := os.Stat(overlayDir); err == nil {
cmd := fmt.Sprintf("cd %s; find . | cpio --quiet -o -H newc | gzip -c > \"%s/provision/overlays/%s.img\"", overlayDir, LocalStateDir, node.Fqdn);
fmt.Printf("BUILDING OVERLAY: %s\n", node.Fqdn);
err := exec.Command("/bin/sh", "-c", cmd).Run();
if err != nil {
fmt.Printf("%s", err)
}
} else {
fmt.Printf("SKIPPING OVERLAY: (bad path) %s\n", overlayDir);
}
}
}
}

54
dhcpd.conf Normal file
View File

@@ -0,0 +1,54 @@
allow booting;
allow bootp;
ddns-update-style interim;
authoritative;
option space ipxe;
# Tell iPXE to not wait for ProxyDHCP requests to speed up boot.
option ipxe.no-pxedhcp code 176 = unsigned integer 8;
option ipxe.no-pxedhcp 1;
option architecture-type code 93 = unsigned integer 16;
if exists user-class and option user-class = "iPXE" {
#filename "http://192.168.1.1/WW4/ipxe/${mac:hexhyp}";
#filename "http://192.168.1.1:9873/ipxe?hwaddr=${mac:hexhyp}";
filename "http://192.168.1.1:9873/ipxe/${mac:hexhyp}";
} else {
if option architecture-type = 00:0B {
filename "/warewulf/ipxe/bin-arm64-efi/snp.efi";
} elsif option architecture-type = 00:0A {
filename "/warewulf/ipxe/bin-arm32-efi/placeholder.efi";
} elsif option architecture-type = 00:09 {
filename "/warewulf/ipxe/bin-x86_64-efi/snp.efi";
} elsif option architecture-type = 00:07 {
filename "/warewulf/ipxe/bin-x86_64-efi/snp.efi";
} elsif option architecture-type = 00:06 {
filename "/warewulf/ipxe/bin-i386-efi/snp.efi";
} elsif option architecture-type = 00:00 {
filename "/warewulf/ipxe/bin-i386-pcbios/undionly.kpxe";
}
}
subnet 192.168.1.0 netmask 255.255.255.0 {
range 192.168.1.10 192.168.1.99;
next-server 192.168.1.1;
}
#subnet 192.168.1.0 netmask 255.255.255.0 {
# not authoritative;
# option subnet-mask 255.255.255.0;
#}
#group {
# host n0000_localdomain-eth0 {
# option host-name n0000;
# option domain-name "localdomain";
# hardware ethernet 00:0c:29:c3:bf:42;
# fixed-address 192.168.1.100;
# next-server 192.168.1.1;
# }
#}

5
go.mod Normal file
View File

@@ -0,0 +1,5 @@
module github.com/hpcng/warewulf
go 1.15
require gopkg.in/yaml.v2 v2.3.0

3
go.sum Normal file
View File

@@ -0,0 +1,3 @@
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@@ -0,0 +1,182 @@
package assets
import (
"fmt"
"io/ioutil"
"gopkg.in/yaml.v2"
)
const ConfigFile = "/etc/warewulf/nodes.yaml"
func init() {
}
type nodeYaml struct {
NodeGroups map[string]nodeGroup //`yaml:"nodegroups"`
}
type nodeGroup struct {
Comment string
Vnfs string
Overlay string
DomainSuffix string `yaml:"domain suffix"`
KernelVersion string `yaml:"kernel version"`
Nodes map[string]nodeEntry
}
type nodeEntry struct {
Hostname string
Vnfs string
Overlay string
DomainSuffix string `yaml:"domain suffix"`
KernelVersion string `yaml:"kernel version"`
IpmiIpaddr string `yaml:"ipmi ipaddr"`
NetDevs map[string]netDevs
}
type netDevs struct {
Type string
Hwaddr string
Ipaddr string
Netmask string
Gateway string
}
type NodeInfo struct {
GroupName string
HostName string
Fqdn string
Vnfs string
Overlay string
KernelVersion string
NetDevs map[string]netDevs
}
func FindAllNodes() []NodeInfo {
var c nodeYaml
var ret []NodeInfo
fd, err := ioutil.ReadFile(ConfigFile)
if err != nil {
fmt.Println(err)
}
err = yaml.Unmarshal(fd, &c)
if err != nil {
fmt.Println(err)
}
for groupname, group := range c.NodeGroups {
for _, node := range group.Nodes {
var n NodeInfo
n.GroupName = groupname
n.HostName = node.Hostname
n.Vnfs = group.Vnfs
n.Overlay = group.Overlay
n.KernelVersion = group.KernelVersion
n.NetDevs = node.NetDevs
if ( group.DomainSuffix != "" ) {
n.Fqdn = node.Hostname + "." + group.DomainSuffix
} else {
n.Fqdn = node.Hostname
}
if node.KernelVersion != "" {
n.KernelVersion = node.KernelVersion
}
if node.Vnfs != "" {
n.Vnfs = node.Vnfs
}
if node.Overlay != "" {
n.Overlay = node.Overlay
}
ret = append(ret, n)
}
}
return ret
}
func FindByHwaddr(hwa string) NodeInfo{
var ret NodeInfo
for _, node := range FindAllNodes() {
for _, dev := range node.NetDevs {
if dev.Hwaddr == hwa {
return node
}
}
}
return ret
}
func FindAllVnfs() []string {
var ret []string
set := make(map[string]bool)
for _, node := range FindAllNodes() {
if node.Vnfs != "" {
set[node.Vnfs] = true
}
}
for entry, _ := range set {
ret = append(ret, entry)
}
return ret
}
func FindAllKernels() []string {
var ret []string
set := make(map[string]bool)
for _, node := range FindAllNodes() {
if node.KernelVersion != "" {
set[node.KernelVersion] = true
}
}
for entry, _ := range set {
ret = append(ret, entry)
}
return ret
}
func FindAllOverlays() []string {
var ret []string
set := make(map[string]bool)
for _, node := range FindAllNodes() {
if node.Overlay != "" {
set[node.Overlay] = true
}
}
for entry, _ := range set {
ret = append(ret, entry)
}
return ret
}

16
internal/pkg/util/util.go Normal file
View File

@@ -0,0 +1,16 @@
package util
import (
"fmt"
"crypto/sha1"
)
func Sha1Sum(input string) []byte{
}

52
nodes.yaml Normal file
View File

@@ -0,0 +1,52 @@
nodegroups:
group_1:
comment: This is the group 1
vnfs: /var/chroots/test
overlay: generic
domain suffix: group1
nodes:
n0000:
hostname: n0000
kernel version: 3.10.0-1127.el7.x86_64
netdevs:
eth0:
hwaddr: xx:xx:xx:xx:aa
ipaddr: x.x.x.x
netmask: x.x.x.x
gateway: x.x.x.x
group_2:
comment: This is the group 2
vnfs: /var/chroots/test
overlay: generic
domain suffix: group2
kernel version: 3.10.0-1127.el7.x86_64
nodes:
n0000:
hostname: n0000
ipmi ipaddr: x.x.x.x
netdevs:
eth0:
hwaddr: 00-0c-29-c3-bf-42
ipaddr: x.x.x.x
netmask: x.x.x.x
gateway: x.x.x.x
type: ethernet
ib0:
hwaddr: xx:xx:xx:xx:xx
ipaddr: x.x.x.x
netmask: x.x.x.x
gateway: x.x.x.x
type: infiniband
n0001:
hostname: n0001
ipmi ipaddr: x.x.x.x
netdevs:
eth0:
hwaddr: xx:xx:xx:xx:xx
ipaddr: x.x.x.x
netmask: x.x.x.x
gateway: x.x.x.x
ib0:
hwaddr: xx:xx:xx:xx:xx
ipaddr: x.x.x.x
netmask: x.x.x.x

14
overlays/generic/init Executable file
View File

@@ -0,0 +1,14 @@
#!/bin/sh
clear
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs devtmpfs /dev
/sbin/ip link set dev lo up
echo "Calling /sbin/init..."
echo
exec /sbin/init

Binary file not shown.

Binary file not shown.

Binary file not shown.