diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..4ef21982 --- /dev/null +++ b/Makefile @@ -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 + diff --git a/README.md b/README.md index 95b918c6..ff374d5b 100644 --- a/README.md +++ b/README.md @@ -1 +1,17 @@ -# warewulf \ No newline at end of file +# 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 diff --git a/centos-7.def b/centos-7.def new file mode 100644 index 00000000..b0b9afc5 --- /dev/null +++ b/centos-7.def @@ -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 diff --git a/cmd/warewulfd/warewulfd.go b/cmd/warewulfd/warewulfd.go new file mode 100644 index 00000000..f80d85ce --- /dev/null +++ b/cmd/warewulfd/warewulfd.go @@ -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) +} diff --git a/cmd/wwbuild/wwbuild.go b/cmd/wwbuild/wwbuild.go new file mode 100644 index 00000000..031c6f98 --- /dev/null +++ b/cmd/wwbuild/wwbuild.go @@ -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); + } + } + + + } +} diff --git a/dhcpd.conf b/dhcpd.conf new file mode 100644 index 00000000..0c3349fc --- /dev/null +++ b/dhcpd.conf @@ -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; +# } +#} diff --git a/go.mod b/go.mod new file mode 100644 index 00000000..a6c2066a --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module github.com/hpcng/warewulf + +go 1.15 + +require gopkg.in/yaml.v2 v2.3.0 diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..8fabe8da --- /dev/null +++ b/go.sum @@ -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= diff --git a/internal/pkg/assets/assets.go b/internal/pkg/assets/assets.go new file mode 100644 index 00000000..0bea0237 --- /dev/null +++ b/internal/pkg/assets/assets.go @@ -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 +} + + diff --git a/internal/pkg/util/util.go b/internal/pkg/util/util.go new file mode 100644 index 00000000..fa94f0a2 --- /dev/null +++ b/internal/pkg/util/util.go @@ -0,0 +1,16 @@ + +package util + + +import ( + "fmt" + "crypto/sha1" +) + + + +func Sha1Sum(input string) []byte{ + + + +} diff --git a/nodes.yaml b/nodes.yaml new file mode 100644 index 00000000..9bcf32e1 --- /dev/null +++ b/nodes.yaml @@ -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 diff --git a/overlays/generic/init b/overlays/generic/init new file mode 100755 index 00000000..e2a82553 --- /dev/null +++ b/overlays/generic/init @@ -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 diff --git a/tftpboot/bin-i386-efi/snp.efi b/tftpboot/bin-i386-efi/snp.efi new file mode 100644 index 00000000..d7c37485 Binary files /dev/null and b/tftpboot/bin-i386-efi/snp.efi differ diff --git a/tftpboot/bin-i386-pcbios/undionly.kpxe b/tftpboot/bin-i386-pcbios/undionly.kpxe new file mode 100644 index 00000000..f82aa3da Binary files /dev/null and b/tftpboot/bin-i386-pcbios/undionly.kpxe differ diff --git a/tftpboot/bin-x86_64-efi/snp.efi b/tftpboot/bin-x86_64-efi/snp.efi new file mode 100644 index 00000000..5833735f Binary files /dev/null and b/tftpboot/bin-x86_64-efi/snp.efi differ