diff --git a/Makefile b/Makefile index d4e74189..b71f2782 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,7 @@ lint: @echo Running golangci-lint... @$(GOLANGCI_LINT) run --build-tags "$(WW_BUILD_GO_BUILD_TAGS)" ./... -all: vendor warewulfd wwctl wwclient +all: vendor wwctl wwclient files: all install -d -m 0755 /var/warewulf/ @@ -60,9 +60,6 @@ services: files # sudo systemctl enable tftp # sudo systemctl restart tftp -warewulfd: - cd cmd/warewulfd; go build -mod vendor -tags "$(WW_BUILD_GO_BUILD_TAGS)" -o ../../warewulfd - wwctl: cd cmd/wwctl; go build -mod vendor -tags "$(WW_BUILD_GO_BUILD_TAGS)" -o ../../wwctl @@ -70,7 +67,6 @@ wwclient: cd cmd/wwclient; CGO_ENABLED=0 GOOS=linux go build -mod vendor -a -ldflags '-extldflags -static' -o ../../wwclient clean: - rm -f warewulfd rm -f wwclient rm -f wwctl diff --git a/README.md b/README.md index 47b51b27..12a4bfa5 100644 --- a/README.md +++ b/README.md @@ -97,24 +97,78 @@ There are three major groups of data to provision: ``` -sudo ./wwctl container pull docker://warewulf/centos-7 centos-7 -sudo ./wwctl container build centos-7 -sudo ./wwctl kernel build $(uname -r) +sudo ./wwctl container pull docker://warewulf/centos-7 centos-7 --setdefault +sudo ./wwctl kernel build $(uname -r) --setdefault ``` #### Set up the default node profile +The `--setdefault` arguments above will automatically set those entries in the default +profile, but if you wanted to set them by hand to something different, you can do the +following: + ``` sudo ./wwctl profile set default -K $(uname -r) -C centos-7 +``` + +Next we set some default networking configurations for the first ethernet device. On +modern Linux distributions, the name of the device is not critical, as it will be setup +according to the HW address. Because all nodes will share the netmask and gateway, we +can configure them in the default profile as follows: + +``` sudo ./wwctl profile set default --netdev eth0 -M 255.255.255.0 -G 192.168.1.1 sudo ./wwctl profile list ``` #### Add a node and build node specific overlays +Adding nodes can be done while setting configurations in one command. Here we are setting +the IP address of `eth0` and setting this node to be discoverable, which will then +automatically have the HW address added to the configuration as the node boots. + +Node names must be unique. If you have node groups and/or multiple clusters, designate +them using dot notation. + +Note that the full node configuration comes from both cascading profiles and node +configurations which always supersede profile configurations. + ``` -sudo ./wwctl node add n0000.cluster --netdev eth0 -I 192.168.1.100 -H 00:0c:29:23:8b:48 +sudo ./wwctl node add n0000.cluster --netdev eth0 -I 192.168.1.100 --discoverable sudo ./wwctl node list -a n0000 +``` + +### Warewulf Overlays + +There are two types of overlays: system and runtime overlays. + +System overlays are provisioned to the node before `/sbin/init` is called. This enables us +to prepopulate node configurations with content that is node specific like networking and +service configurations. When using the overlay subsystem, system overlays are never shown +by default. So when running `overlay` commands, you are always looking at runtime overlays +unless the `-s` option is passed. + +Runtime overlays are provisioned after the node has booted and periodically during the +normal runtime of the node. Runtime overlays are also obtained using privileged source +ports such that non-root users can not obtain this from the Warewulf service (note: there +are other ways to secure the provisioned files like a provisioning VLan). Because these +overlays are provisioned at periodic intervals, they are very useful for content that +changes, like users and groups. + +Overlays are generated from a template structure that is viewed using the `wwctl overlay` +commands. Files that end in the `.ww` suffix are templates and abide by standard +text/template rules. This supports loops, arrays, variables, and functions making overlays +extremely flexible. + +All overlays are compiled before being provisioned. This accelerates the provisioning +process because there is less to do when nodes are being managed at scale. + +Here are some of the common `overlay` commands: + +``` +sudo ./wwctl overlay list -l +sudo ./wwctl overlay list -ls +sudo EDITOR=vim ./wwctl overlay edit default /etc/hello_world.ww sudo ./wwctl overlay build -a ``` @@ -125,7 +179,8 @@ and then begin booting nodes. ``` sudo ./wwctl ready -./warewulfd +sudo ./wwctl server start +sudo ./wwctl server status ``` #### Boot your compute node and watch it boot diff --git a/cmd/warewulfd/main.go b/cmd/warewulfd/main.go deleted file mode 100644 index 019de38f..00000000 --- a/cmd/warewulfd/main.go +++ /dev/null @@ -1,12 +0,0 @@ -package main - -import ( - "github.com/hpcng/warewulf/internal/app/warewulfd" -) - - -func main() { - root := warewulfd.GetRootCommand() - - root.Execute() -} \ No newline at end of file diff --git a/etc/ipxe/unconfigured.ipxe b/etc/ipxe/unconfigured.ipxe new file mode 100644 index 00000000..e12de477 --- /dev/null +++ b/etc/ipxe/unconfigured.ipxe @@ -0,0 +1,12 @@ +#!ipxe + +echo +echo ================================================================================ +echo Warewulf v4 +echo +echo MESSAGE: This node is unconfigured. Please have your system administrator add a +echo configuration for this node with HW address: {{$.Hwaddr}} +echo +echo Rebooting in 1 minute... +sleep 60 +reboot \ No newline at end of file diff --git a/etc/nodes.conf b/etc/nodes.conf deleted file mode 100644 index 83b14876..00000000 --- a/etc/nodes.conf +++ /dev/null @@ -1,8 +0,0 @@ -nodeprofiles: - default: - comment: "This profile is automatically included for each node" - container name: "" - kernel version: "" - kernel args: crashkernel=no quiet - -nodes: {} diff --git a/go.mod b/go.mod index a6e175c9..e3cc319e 100644 --- a/go.mod +++ b/go.mod @@ -42,6 +42,7 @@ require ( github.com/prometheus/common v0.6.0 // indirect github.com/prometheus/procfs v0.0.3 // indirect github.com/russross/blackfriday/v2 v2.0.1 // indirect + github.com/sevlyar/go-daemon v0.1.5 // indirect github.com/sirupsen/logrus v1.7.0 // indirect github.com/spf13/cobra v1.1.1 github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31 // indirect diff --git a/go.sum b/go.sum index 4f9a7888..afc95a42 100644 --- a/go.sum +++ b/go.sum @@ -201,6 +201,8 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU= github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA= +github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= @@ -340,6 +342,8 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sevlyar/go-daemon v0.1.5 h1:Zy/6jLbM8CfqJ4x4RPr7MJlSKt90f00kNM1D401C+Qk= +github.com/sevlyar/go-daemon v0.1.5/go.mod h1:6dJpPatBT9eUwM5VCw9Bt6CdX9Tk6UWvhW3MebLDRKE= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= diff --git a/internal/app/warewulfd/response/ipxe.go b/internal/app/warewulfd/response/ipxe.go deleted file mode 100644 index 794159b5..00000000 --- a/internal/app/warewulfd/response/ipxe.go +++ /dev/null @@ -1,90 +0,0 @@ -package response - -import ( - "fmt" - "github.com/hpcng/warewulf/internal/pkg/node" - "github.com/hpcng/warewulf/internal/pkg/warewulfconf" - "github.com/hpcng/warewulf/internal/pkg/wwlog" - "log" - "net/http" - "strconv" - "strings" - "text/template" -) - -type iPxeTemplate struct { - Hostname string - Fqdn string - ContainerName string - Hwaddr string - Ipaddr string - Port string - KernelArgs string - KernelVersion string -} - -func IpxeSend(w http.ResponseWriter, req *http.Request) { - url := strings.Split(req.URL.Path, "/") - - nodes, err := node.New() - if err != nil { - log.Printf("Could not read node configuration file: %s\n", err) - w.WriteHeader(503) - return - } - - if url[2] == "" { - log.Printf("ERROR: Bad iPXE request from %s\n", req.RemoteAddr) - return - } - - hwaddr := strings.ReplaceAll(url[2], "-", ":") - node, err := nodes.FindByHwaddr(hwaddr) - if err != nil { - log.Printf("Could not find HW Addr: %s: %s\n", hwaddr, err) - w.WriteHeader(404) - return - } - - if node.Id.Defined() == true { - conf, err := warewulfconf.New() - if err != nil { - wwlog.Printf(wwlog.ERROR, "%s\n", err) - return - } - - log.Printf("IPXE: %15s: %s\n", node.Id.Get(), req.URL.Path) - - // TODO: Fix template path to use config package - ipxeTemplate := fmt.Sprintf("/etc/warewulf/ipxe/%s.ipxe", node.Ipxe.Get()) - - tmpl, err := template.ParseFiles(ipxeTemplate) - if err != nil { - wwlog.Printf(wwlog.ERROR, "%s\n", err) - return - } - - var replace iPxeTemplate - - replace.Fqdn = node.Id.Get() - replace.Ipaddr = conf.Ipaddr - replace.Port = strconv.Itoa(conf.Warewulf.Port) - replace.Hostname = node.Id.Get() - replace.Hwaddr = url[2] - replace.ContainerName = node.ContainerName.Get() - replace.KernelArgs = node.KernelArgs.Get() - replace.KernelVersion = node.KernelVersion.Get() - - err = tmpl.Execute(w, replace) - if err != nil { - wwlog.Printf(wwlog.ERROR, "%s\n", err) - return - } - - log.Printf("SEND: %15s: %s\n", node.Id.Get(), ipxeTemplate) - - } else { - log.Printf("ERROR: iPXE request from unknown Node (hwaddr=%s)\n", url[2]) - } - return -} diff --git a/internal/app/warewulfd/root.go b/internal/app/warewulfd/root.go deleted file mode 100644 index 9809b0b5..00000000 --- a/internal/app/warewulfd/root.go +++ /dev/null @@ -1,40 +0,0 @@ -package warewulfd - -import ( - "github.com/hpcng/warewulf/internal/pkg/wwlog" - "github.com/spf13/cobra" -) - -var ( - baseCmd = &cobra.Command{ - Use: "warewulfd", - Short: "Warewulf Daemon Service", - Long: "This is the primary Warewulf service for provisioning nodes", - RunE: CobraRunE, - PersistentPreRunE: rootPersistentPreRunE, - } - verboseArg bool - debugArg bool -) - - -func init() { - baseCmd.PersistentFlags().BoolVarP(&verboseArg, "verbose", "v", false, "Run with increased verbosity.") - baseCmd.PersistentFlags().BoolVarP(&debugArg, "debug", "d", false, "Run with debugging messages enabled.") -} - -// GetRootCommand returns the root cobra.Command for the application. -func GetRootCommand() *cobra.Command { - return baseCmd -} - -func rootPersistentPreRunE(cmd *cobra.Command, args []string) error { - if verboseArg == true { - wwlog.SetLevel(wwlog.VERBOSE) - } else if debugArg == true { - wwlog.SetLevel(wwlog.DEBUG) - } else { - wwlog.SetLevel(wwlog.INFO) - } - return nil -} \ No newline at end of file diff --git a/internal/app/warewulfd/warewulfd.go b/internal/app/warewulfd/warewulfd.go deleted file mode 100644 index a6805d2a..00000000 --- a/internal/app/warewulfd/warewulfd.go +++ /dev/null @@ -1,24 +0,0 @@ -package warewulfd - -import ( - "github.com/hpcng/warewulf/internal/app/warewulfd/response" - "github.com/spf13/cobra" - "net/http" -) - -// TODO: https://github.com/danderson/netboot/blob/master/pixiecore/dhcp.go -// TODO: https://github.com/pin/tftp - -func CobraRunE(cmd *cobra.Command, args []string) error { - - http.HandleFunc("/ipxe/", response.IpxeSend) - http.HandleFunc("/kernel/", response.KernelSend) - http.HandleFunc("/kmods/", response.KmodsSend) - http.HandleFunc("/container/", response.ContainerSend) - http.HandleFunc("/overlay-system/", response.SystemOverlaySend) - http.HandleFunc("/overlay-runtime", response.RuntimeOverlaySend) - - http.ListenAndServe(":9873", nil) - - return nil -} diff --git a/internal/app/wwctl/service/dhcp/main.go b/internal/app/wwctl/configure/dhcp/main.go similarity index 100% rename from internal/app/wwctl/service/dhcp/main.go rename to internal/app/wwctl/configure/dhcp/main.go diff --git a/internal/app/wwctl/service/dhcp/root.go b/internal/app/wwctl/configure/dhcp/root.go similarity index 100% rename from internal/app/wwctl/service/dhcp/root.go rename to internal/app/wwctl/configure/dhcp/root.go diff --git a/internal/app/wwctl/service/root.go b/internal/app/wwctl/configure/root.go similarity index 80% rename from internal/app/wwctl/service/root.go rename to internal/app/wwctl/configure/root.go index 55167a35..d94568c4 100644 --- a/internal/app/wwctl/service/root.go +++ b/internal/app/wwctl/configure/root.go @@ -1,17 +1,17 @@ -package service +package configure import ( "fmt" - "github.com/hpcng/warewulf/internal/app/wwctl/service/dhcp" - "github.com/hpcng/warewulf/internal/app/wwctl/service/tftp" + "github.com/hpcng/warewulf/internal/app/wwctl/configure/dhcp" + "github.com/hpcng/warewulf/internal/app/wwctl/configure/tftp" "github.com/spf13/cobra" "os" ) var ( baseCmd = &cobra.Command{ - Use: "service", - Short: "Initialize Warewulf services", + Use: "configure", + Short: "Configure Warewulf services", Long: "Warewulf Service Initialization", RunE: CobraRunE, } diff --git a/internal/app/wwctl/service/tftp/main.go b/internal/app/wwctl/configure/tftp/main.go similarity index 100% rename from internal/app/wwctl/service/tftp/main.go rename to internal/app/wwctl/configure/tftp/main.go diff --git a/internal/app/wwctl/service/tftp/root.go b/internal/app/wwctl/configure/tftp/root.go similarity index 100% rename from internal/app/wwctl/service/tftp/root.go rename to internal/app/wwctl/configure/tftp/root.go diff --git a/internal/app/wwctl/container/build/main.go b/internal/app/wwctl/container/build/main.go index 236bf712..fe9c9d88 100644 --- a/internal/app/wwctl/container/build/main.go +++ b/internal/app/wwctl/container/build/main.go @@ -24,7 +24,13 @@ func CobraRunE(cmd *cobra.Command, args []string) error { os.Exit(1) } - container.Build(c, BuildForce) + output, err := container.Build(c, BuildForce) + if err != nil { + wwlog.Printf(wwlog.ERROR, "Could not build container %s: %s\n", c, err) + os.Exit(1) + } else { + fmt.Printf("%-20s: %s\n", c, output) + } } if SetDefault == true { diff --git a/internal/app/wwctl/container/delete/main.go b/internal/app/wwctl/container/delete/main.go new file mode 100644 index 00000000..dfd9d8b5 --- /dev/null +++ b/internal/app/wwctl/container/delete/main.go @@ -0,0 +1,44 @@ +package delete + +import ( + "fmt" + "github.com/hpcng/warewulf/internal/pkg/container" + "github.com/hpcng/warewulf/internal/pkg/node" + "github.com/hpcng/warewulf/internal/pkg/wwlog" + "github.com/spf13/cobra" + "os" +) + +func CobraRunE(cmd *cobra.Command, args []string) error { + + nodeDB, err := node.New() + if err != nil { + wwlog.Printf(wwlog.ERROR, "Could not open nodeDB: %s\n", err) + os.Exit(1) + } + + nodes, _ := nodeDB.FindAllNodes() + +ARG_LOOP: + for _, arg := range args { + for _, n := range nodes { + if n.ContainerName.Get() == arg { + wwlog.Printf(wwlog.ERROR, "Container is configured for nodes, skipping: %s\n", arg) + continue ARG_LOOP + } + } + + if container.ValidSource(arg) == false { + wwlog.Printf(wwlog.ERROR, "Container name is not a valid source: %s\n", arg) + continue + } + err := container.DeleteSource(arg) + if err != nil { + wwlog.Printf(wwlog.ERROR, "Could not remove source: %s\n", arg) + } else { + fmt.Printf("Container has been deleted: %s\n", arg) + } + } + + return nil +} diff --git a/internal/app/wwctl/container/delete/root.go b/internal/app/wwctl/container/delete/root.go new file mode 100644 index 00000000..2171df6a --- /dev/null +++ b/internal/app/wwctl/container/delete/root.go @@ -0,0 +1,25 @@ +package delete + +import "github.com/spf13/cobra" + +var ( + baseCmd = &cobra.Command{ + Use: "delete", + Short: "Delete Source OCI VNFS images", + Long: "Delete Source OCI VNFS images ", + RunE: CobraRunE, + Args: cobra.MinimumNArgs(1), + } + SetForce bool + SetUpdate bool + SetBuild bool +) + +func init() { + +} + +// GetRootCommand returns the root cobra.Command for the application. +func GetCommand() *cobra.Command { + return baseCmd +} diff --git a/internal/app/wwctl/container/exec/child/root.go b/internal/app/wwctl/container/exec/child/root.go index 6df31ff4..48aa0d8b 100644 --- a/internal/app/wwctl/container/exec/child/root.go +++ b/internal/app/wwctl/container/exec/child/root.go @@ -4,10 +4,11 @@ import "github.com/spf13/cobra" var ( baseCmd = &cobra.Command{ - Use: "__child", - Hidden: true, - RunE: CobraRunE, - Args: cobra.MinimumNArgs(1), + Use: "__child", + Hidden: true, + RunE: CobraRunE, + Args: cobra.MinimumNArgs(1), + FParseErrWhitelist: cobra.FParseErrWhitelist{UnknownFlags: true}, } ) diff --git a/internal/app/wwctl/container/exec/main.go b/internal/app/wwctl/container/exec/main.go index 23db6553..765baf6d 100644 --- a/internal/app/wwctl/container/exec/main.go +++ b/internal/app/wwctl/container/exec/main.go @@ -36,5 +36,14 @@ func CobraRunE(cmd *cobra.Command, args []string) error { os.Exit(1) } + fmt.Printf("Rebuilding container...\n") + output, err := container.Build(containerName, false) + if err != nil { + wwlog.Printf(wwlog.ERROR, "Could not build container %s: %s\n", containerName, err) + os.Exit(1) + } else { + fmt.Printf("%s\n", output) + } + return nil } diff --git a/internal/app/wwctl/container/exec/root.go b/internal/app/wwctl/container/exec/root.go index 049ca014..32b6c75b 100644 --- a/internal/app/wwctl/container/exec/root.go +++ b/internal/app/wwctl/container/exec/root.go @@ -7,11 +7,12 @@ import ( var ( baseCmd = &cobra.Command{ - Use: "exec", - Short: "Spawn any command inside a Warewulf container", - Long: "Run a command inside a Warewulf container ", - RunE: CobraRunE, - Args: cobra.MinimumNArgs(1), + Use: "exec", + Short: "Spawn any command inside a Warewulf container", + Long: "Run a command inside a Warewulf container ", + RunE: CobraRunE, + Args: cobra.MinimumNArgs(1), + FParseErrWhitelist: cobra.FParseErrWhitelist{UnknownFlags: true}, } ) diff --git a/internal/app/wwctl/container/list/main.go b/internal/app/wwctl/container/list/main.go index 132b118f..1a2f3128 100644 --- a/internal/app/wwctl/container/list/main.go +++ b/internal/app/wwctl/container/list/main.go @@ -18,8 +18,8 @@ func CobraRunE(cmd *cobra.Command, args []string) error { os.Exit(1) } - nconfig, _ := node.New() - nodes, _ := nconfig.FindAllNodes() + nodeDB, _ := node.New() + nodes, _ := nodeDB.FindAllNodes() nodemap := make(map[string]int) for _, n := range nodes { diff --git a/internal/app/wwctl/container/pull/main.go b/internal/app/wwctl/container/pull/main.go index f9a9919d..4a91d965 100644 --- a/internal/app/wwctl/container/pull/main.go +++ b/internal/app/wwctl/container/pull/main.go @@ -3,6 +3,7 @@ package pull import ( "fmt" "github.com/hpcng/warewulf/internal/pkg/container" + "github.com/hpcng/warewulf/internal/pkg/node" "github.com/hpcng/warewulf/internal/pkg/util" "github.com/hpcng/warewulf/internal/pkg/wwlog" "github.com/spf13/cobra" @@ -50,9 +51,35 @@ func CobraRunE(cmd *cobra.Command, args []string) error { os.Exit(1) } - if SetBuild == true { - fmt.Printf("Building container: %s\n", name) - container.Build(name, false) + fmt.Printf("Building container: %s\n", name) + output, err := container.Build(name, true) + if err != nil { + wwlog.Printf(wwlog.ERROR, "Could not build container %s: %s\n", name, err) + os.Exit(1) + } else { + fmt.Printf("%s: %s\n", name, output) + } + + if SetDefault == true { + nodeDB, err := node.New() + if err != nil { + wwlog.Printf(wwlog.ERROR, "Could not open node configuration: %s\n", err) + os.Exit(1) + } + + //TODO: Don't loop through profiles, instead have a nodeDB function that goes directly to the map + profiles, _ := nodeDB.FindAllProfiles() + for _, profile := range profiles { + wwlog.Printf(wwlog.DEBUG, "Looking for profile default: %s\n", profile.Id.Get()) + if profile.Id.Get() == "default" { + wwlog.Printf(wwlog.DEBUG, "Found profile default, setting container name to: %s\n", name) + profile.ContainerName.Set(name) + nodeDB.ProfileUpdate(profile) + } + } + nodeDB.Persist() + fmt.Printf("Set default profile to container: %s\n", name) + } return nil diff --git a/internal/app/wwctl/container/pull/root.go b/internal/app/wwctl/container/pull/root.go index 84ed3d04..882a18f0 100644 --- a/internal/app/wwctl/container/pull/root.go +++ b/internal/app/wwctl/container/pull/root.go @@ -10,15 +10,17 @@ var ( RunE: CobraRunE, Args: cobra.MinimumNArgs(1), } - SetForce bool - SetUpdate bool - SetBuild bool + SetForce bool + SetUpdate bool + SetBuild bool + SetDefault bool ) func init() { baseCmd.PersistentFlags().BoolVarP(&SetForce, "force", "f", false, "Force overwrite of an existing container") baseCmd.PersistentFlags().BoolVarP(&SetUpdate, "update", "u", false, "Update and overwrite an existing container") baseCmd.PersistentFlags().BoolVarP(&SetBuild, "build", "b", false, "Build container when after pulling") + baseCmd.PersistentFlags().BoolVar(&SetDefault, "setdefault", false, "Set this container for the default profile") } diff --git a/internal/app/wwctl/container/root.go b/internal/app/wwctl/container/root.go index 9bda463a..00739fd7 100644 --- a/internal/app/wwctl/container/root.go +++ b/internal/app/wwctl/container/root.go @@ -2,6 +2,7 @@ package container import ( "github.com/hpcng/warewulf/internal/app/wwctl/container/build" + "github.com/hpcng/warewulf/internal/app/wwctl/container/delete" "github.com/hpcng/warewulf/internal/app/wwctl/container/exec" "github.com/hpcng/warewulf/internal/app/wwctl/container/list" "github.com/hpcng/warewulf/internal/app/wwctl/container/pull" @@ -22,7 +23,7 @@ func init() { baseCmd.AddCommand(list.GetCommand()) baseCmd.AddCommand(pull.GetCommand()) baseCmd.AddCommand(exec.GetCommand()) - + baseCmd.AddCommand(delete.GetCommand()) } // GetRootCommand returns the root cobra.Command for the application. diff --git a/internal/app/wwctl/kernel/build/main.go b/internal/app/wwctl/kernel/build/main.go index 39ac088f..a605dae5 100644 --- a/internal/app/wwctl/kernel/build/main.go +++ b/internal/app/wwctl/kernel/build/main.go @@ -12,10 +12,12 @@ import ( func CobraRunE(cmd *cobra.Command, args []string) error { for _, arg := range args { - err := kernel.Build(arg) + output, err := kernel.Build(arg) if err != nil { wwlog.Printf(wwlog.ERROR, "Failed building kernel: %s\n", err) os.Exit(1) + } else { + fmt.Printf("%s: %s\n", arg, output) } } diff --git a/internal/app/wwctl/node/add/main.go b/internal/app/wwctl/node/add/main.go index 22937985..598b4974 100644 --- a/internal/app/wwctl/node/add/main.go +++ b/internal/app/wwctl/node/add/main.go @@ -102,6 +102,17 @@ func CobraRunE(cmd *cobra.Command, args []string) error { } } + if SetDiscoverable == true { + wwlog.Printf(wwlog.VERBOSE, "Node: %s, Setting node to discoverable\n", n.Id.Get()) + + n.Discoverable.SetB(true) + err := nodeDB.NodeUpdate(n) + if err != nil { + wwlog.Printf(wwlog.ERROR, "%s\n", err) + os.Exit(1) + } + } + } nodeDB.Persist() diff --git a/internal/app/wwctl/node/add/root.go b/internal/app/wwctl/node/add/root.go index 99a4a75c..da7f8d60 100644 --- a/internal/app/wwctl/node/add/root.go +++ b/internal/app/wwctl/node/add/root.go @@ -10,13 +10,14 @@ var ( RunE: CobraRunE, Args: cobra.MinimumNArgs(1), } - SetGroup string - SetController string - SetNetDev string - SetIpaddr string - SetNetmask string - SetGateway string - SetHwaddr string + SetGroup string + SetController string + SetNetDev string + SetIpaddr string + SetNetmask string + SetGateway string + SetHwaddr string + SetDiscoverable bool ) func init() { @@ -27,6 +28,7 @@ func init() { baseCmd.PersistentFlags().StringVarP(&SetNetmask, "netmask", "M", "", "Set the node's network device netmask") baseCmd.PersistentFlags().StringVarP(&SetGateway, "gateway", "G", "", "Set the node's network device gateway") baseCmd.PersistentFlags().StringVarP(&SetHwaddr, "hwaddr", "H", "", "Set the node's network device HW address") + baseCmd.PersistentFlags().BoolVar(&SetDiscoverable, "discoverable", false, "Make this node discoverable") } diff --git a/internal/app/wwctl/node/list/main.go b/internal/app/wwctl/node/list/main.go index 40f92d04..342e672e 100644 --- a/internal/app/wwctl/node/list/main.go +++ b/internal/app/wwctl/node/list/main.go @@ -50,6 +50,9 @@ func CobraRunE(cmd *cobra.Command, args []string) error { fmt.Printf("%-20s %-18s %-12s %s\n", node.Id.Get(), "ClusterName", node.ClusterName.Source(), node.ClusterName.Print()) fmt.Printf("%-20s %-18s %-12s %s\n", node.Id.Get(), "Profiles", "--", strings.Join(node.Profiles, ",")) + fmt.Printf("%-20s %-18s %-12s %t\n", node.Id.Get(), "Disabled", node.Disabled.Source(), node.Disabled.PrintB()) + fmt.Printf("%-20s %-18s %-12s %t\n", node.Id.Get(), "Discoverable", node.Discoverable.Source(), node.Discoverable.PrintB()) + fmt.Printf("%-20s %-18s %-12s %s\n", node.Id.Get(), "ContainerName", node.ContainerName.Source(), node.ContainerName.Print()) fmt.Printf("%-20s %-18s %-12s %s\n", node.Id.Get(), "KernelVersion", node.KernelVersion.Source(), node.KernelVersion.Print()) fmt.Printf("%-20s %-18s %-12s %s\n", node.Id.Get(), "KernelArgs", node.KernelArgs.Source(), node.KernelArgs.Print()) diff --git a/internal/app/wwctl/node/set/main.go b/internal/app/wwctl/node/set/main.go index dea2ca07..f8641b24 100644 --- a/internal/app/wwctl/node/set/main.go +++ b/internal/app/wwctl/node/set/main.go @@ -195,6 +195,27 @@ func CobraRunE(cmd *cobra.Command, args []string) error { } } + if SetDiscoverable == true { + wwlog.Printf(wwlog.VERBOSE, "Node: %s, Setting node to discoverable\n", n.Id.Get()) + + n.Discoverable.SetB(true) + err := nodeDB.NodeUpdate(n) + if err != nil { + wwlog.Printf(wwlog.ERROR, "%s\n", err) + os.Exit(1) + } + } + if SetUndiscoverable == true { + wwlog.Printf(wwlog.VERBOSE, "Node: %s, Setting node to undiscoverable\n", n.Id.Get()) + + n.Discoverable.SetB(false) + err := nodeDB.NodeUpdate(n) + if err != nil { + wwlog.Printf(wwlog.ERROR, "%s\n", err) + os.Exit(1) + } + } + if len(SetAddProfile) > 0 { for _, p := range SetAddProfile { wwlog.Printf(wwlog.VERBOSE, "Node: %s, adding profile to '%s'\n", n.Id.Get(), p) diff --git a/internal/app/wwctl/node/set/root.go b/internal/app/wwctl/node/set/root.go index 31e9d25d..47e9b32e 100644 --- a/internal/app/wwctl/node/set/root.go +++ b/internal/app/wwctl/node/set/root.go @@ -33,6 +33,8 @@ var ( SetDelProfile []string SetForce bool SetInit string + SetDiscoverable bool + SetUndiscoverable bool ) func init() { @@ -63,6 +65,8 @@ func init() { baseCmd.PersistentFlags().BoolVarP(&SetYes, "yes", "y", false, "Set 'yes' to all questions asked") baseCmd.PersistentFlags().BoolVarP(&SetForce, "force", "f", false, "Force configuration (even on error)") + baseCmd.PersistentFlags().BoolVar(&SetDiscoverable, "discoverable", false, "Make this node discoverable") + baseCmd.PersistentFlags().BoolVar(&SetUndiscoverable, "undiscoverable", false, "Remove the discoverable flag") } diff --git a/internal/app/wwctl/overlay/edit/main.go b/internal/app/wwctl/overlay/edit/main.go index 7f308a83..1ede83ae 100644 --- a/internal/app/wwctl/overlay/edit/main.go +++ b/internal/app/wwctl/overlay/edit/main.go @@ -60,7 +60,7 @@ func CobraRunE(cmd *cobra.Command, args []string) error { fmt.Fprintf(w, "# when the overlay is rendered for the individual nodes. Here are some examples\n") fmt.Fprintf(w, "# of macros and logic which can be used within this file:\n") fmt.Fprintf(w, "#\n") - fmt.Fprintf(w, "# Node FQDN = {{.Fqdn}}\n") + fmt.Fprintf(w, "# Node FQDN = {{.Id}}\n") fmt.Fprintf(w, "# Node Group = {{.GroupName}}\n") fmt.Fprintf(w, "# Network Config = {{.NetDevs.eth0.Ipaddr}}, {{.NetDevs.eth0.Hwaddr}}, etc.\n") fmt.Fprintf(w, "#\n") diff --git a/internal/app/wwctl/root.go b/internal/app/wwctl/root.go index 72119651..e3bd91fe 100644 --- a/internal/app/wwctl/root.go +++ b/internal/app/wwctl/root.go @@ -1,13 +1,14 @@ package wwctl import ( + "github.com/hpcng/warewulf/internal/app/wwctl/configure" "github.com/hpcng/warewulf/internal/app/wwctl/container" "github.com/hpcng/warewulf/internal/app/wwctl/kernel" "github.com/hpcng/warewulf/internal/app/wwctl/node" "github.com/hpcng/warewulf/internal/app/wwctl/overlay" "github.com/hpcng/warewulf/internal/app/wwctl/profile" "github.com/hpcng/warewulf/internal/app/wwctl/ready" - "github.com/hpcng/warewulf/internal/app/wwctl/service" + "github.com/hpcng/warewulf/internal/app/wwctl/server" "github.com/hpcng/warewulf/internal/pkg/wwlog" "github.com/spf13/cobra" @@ -37,8 +38,9 @@ func init() { rootCmd.AddCommand(kernel.GetCommand()) // rootCmd.AddCommand(group.GetCommand()) rootCmd.AddCommand(profile.GetCommand()) - rootCmd.AddCommand(service.GetCommand()) + rootCmd.AddCommand(configure.GetCommand()) rootCmd.AddCommand(ready.GetCommand()) + rootCmd.AddCommand(server.GetCommand()) } diff --git a/internal/app/wwctl/server/root.go b/internal/app/wwctl/server/root.go new file mode 100644 index 00000000..b368ac4d --- /dev/null +++ b/internal/app/wwctl/server/root.go @@ -0,0 +1,29 @@ +package server + +import ( + "github.com/hpcng/warewulf/internal/app/wwctl/server/start" + "github.com/hpcng/warewulf/internal/app/wwctl/server/status" + "github.com/hpcng/warewulf/internal/app/wwctl/server/stop" + "github.com/spf13/cobra" +) + +var ( + baseCmd = &cobra.Command{ + Use: "server", + Short: "Warewulf server process commands", + Long: "Warewulf profiles...", + } + test bool +) + +func init() { + baseCmd.AddCommand(start.GetCommand()) + baseCmd.AddCommand(status.GetCommand()) + baseCmd.AddCommand(stop.GetCommand()) + +} + +// GetRootCommand returns the root cobra.Command for the application. +func GetCommand() *cobra.Command { + return baseCmd +} diff --git a/internal/app/wwctl/server/start/main.go b/internal/app/wwctl/server/start/main.go new file mode 100644 index 00000000..b3c2b339 --- /dev/null +++ b/internal/app/wwctl/server/start/main.go @@ -0,0 +1,11 @@ +package start + +import ( + "github.com/hpcng/warewulf/internal/pkg/warewulfd" + "github.com/spf13/cobra" +) + +func CobraRunE(cmd *cobra.Command, args []string) error { + + return warewulfd.DaemonStart() +} diff --git a/internal/app/wwctl/server/start/root.go b/internal/app/wwctl/server/start/root.go new file mode 100644 index 00000000..357e0169 --- /dev/null +++ b/internal/app/wwctl/server/start/root.go @@ -0,0 +1,20 @@ +package start + +import "github.com/spf13/cobra" + +var ( + baseCmd = &cobra.Command{ + Use: "start", + Short: "Start Warewulf server", + Long: "Warewulf Server ", + RunE: CobraRunE, + } +) + +func init() { +} + +// GetRootCommand returns the root cobra.Command for the application. +func GetCommand() *cobra.Command { + return baseCmd +} diff --git a/internal/app/wwctl/server/status/main.go b/internal/app/wwctl/server/status/main.go new file mode 100644 index 00000000..30b4041a --- /dev/null +++ b/internal/app/wwctl/server/status/main.go @@ -0,0 +1,11 @@ +package status + +import ( + "github.com/hpcng/warewulf/internal/pkg/warewulfd" + "github.com/spf13/cobra" +) + +func CobraRunE(cmd *cobra.Command, args []string) error { + warewulfd.DaemonStatus() + return nil +} diff --git a/internal/app/wwctl/server/status/root.go b/internal/app/wwctl/server/status/root.go new file mode 100644 index 00000000..71805969 --- /dev/null +++ b/internal/app/wwctl/server/status/root.go @@ -0,0 +1,20 @@ +package status + +import "github.com/spf13/cobra" + +var ( + baseCmd = &cobra.Command{ + Use: "status", + Short: "Warewulf server status", + Long: "Warewulf Server ", + RunE: CobraRunE, + } +) + +func init() { +} + +// GetRootCommand returns the root cobra.Command for the application. +func GetCommand() *cobra.Command { + return baseCmd +} diff --git a/internal/app/wwctl/server/stop/main.go b/internal/app/wwctl/server/stop/main.go new file mode 100644 index 00000000..b49f8641 --- /dev/null +++ b/internal/app/wwctl/server/stop/main.go @@ -0,0 +1,11 @@ +package stop + +import ( + "github.com/hpcng/warewulf/internal/pkg/warewulfd" + "github.com/spf13/cobra" +) + +func CobraRunE(cmd *cobra.Command, args []string) error { + warewulfd.DaemonStop() + return nil +} diff --git a/internal/app/wwctl/server/stop/root.go b/internal/app/wwctl/server/stop/root.go new file mode 100644 index 00000000..fd331c56 --- /dev/null +++ b/internal/app/wwctl/server/stop/root.go @@ -0,0 +1,20 @@ +package stop + +import "github.com/spf13/cobra" + +var ( + baseCmd = &cobra.Command{ + Use: "stop", + Short: "Stop Warewulf server", + Long: "Warewulf Server ", + RunE: CobraRunE, + } +) + +func init() { +} + +// GetRootCommand returns the root cobra.Command for the application. +func GetCommand() *cobra.Command { + return baseCmd +} diff --git a/internal/pkg/container/build.go b/internal/pkg/container/build.go index 80ce972e..9e29f3ee 100644 --- a/internal/pkg/container/build.go +++ b/internal/pkg/container/build.go @@ -2,6 +2,7 @@ package container import ( "fmt" + "github.com/hpcng/warewulf/internal/pkg/errors" "github.com/hpcng/warewulf/internal/pkg/util" "github.com/hpcng/warewulf/internal/pkg/wwlog" "os" @@ -9,46 +10,40 @@ import ( "path" ) -func Build(name string, buildForce bool) { +func Build(name string, buildForce bool) (string, error) { rootfsPath := RootFsDir(name) imagePath := ImageFile(name) if ValidSource(name) == false { - wwlog.Printf(wwlog.INFO, "%-35s: Skipping (bad path)\n", name) - return + return "", errors.New("Container does not exist") } if buildForce == false { wwlog.Printf(wwlog.DEBUG, "Checking if there have been any updates to the VNFS directory\n") if util.PathIsNewer(rootfsPath, imagePath) { - wwlog.Printf(wwlog.INFO, "%-35s: Skipping, VNFS is current\n", name) - return + return "Skipping (VNFS is current)", nil } } wwlog.Printf(wwlog.DEBUG, "Making parent directory for: %s\n", name) err := os.MkdirAll(path.Dir(imagePath), 0755) if err != nil { - fmt.Printf("ERROR: %s\n", err) - return + return "Failed creating directory", err } wwlog.Printf(wwlog.DEBUG, "Making parent directory for: %s\n", rootfsPath) err = os.MkdirAll(path.Dir(rootfsPath), 0755) if err != nil { - fmt.Printf("ERROR: %s\n", err) - return + return "Failed creating directory", err } wwlog.Printf(wwlog.DEBUG, "Building VNFS image: '%s' -> '%s'\n", rootfsPath, imagePath) cmd := fmt.Sprintf("cd %s; find . | cpio --quiet -o -H newc | gzip -c > \"%s\"", rootfsPath, imagePath) err = exec.Command("/bin/sh", "-c", cmd).Run() if err != nil { - wwlog.Printf(wwlog.ERROR, "Failed building VNFS: %s\n", err) - return + return "Failed building VNFS", err } - wwlog.Printf(wwlog.INFO, "%-35s: Done\n", name) - + return "Done", nil } diff --git a/internal/pkg/container/vnfs.go b/internal/pkg/container/vnfs.go index e86b8e45..436dfed7 100644 --- a/internal/pkg/container/vnfs.go +++ b/internal/pkg/container/vnfs.go @@ -77,9 +77,15 @@ func ValidSource(name string) bool { } if util.IsDir(fullPath) == false { - wwlog.Printf(wwlog.WARN, "Location is not a VNFS source directory: %s\n", name) + wwlog.Printf(wwlog.VERBOSE, "Location is not a VNFS source directory: %s\n", name) return false } return true } + +func DeleteSource(name string) error { + fullPath := SourceDir(name) + + return os.RemoveAll(fullPath) +} diff --git a/internal/pkg/kernel/kernel.go b/internal/pkg/kernel/kernel.go index e59444f7..d7df72ad 100644 --- a/internal/pkg/kernel/kernel.go +++ b/internal/pkg/kernel/kernel.go @@ -69,7 +69,7 @@ func ListKernels() ([]string, error) { return ret, nil } -func Build(kernelVersion string) error { +func Build(kernelVersion string) (string, error) { kernelImage := "/boot/vmlinuz-" + kernelVersion kernelDrivers := "/lib/modules/" + kernelVersion @@ -81,20 +81,19 @@ func Build(kernelVersion string) error { os.MkdirAll(path.Dir(driversDestination), 0755) if util.IsFile(kernelImage) == false { - return errors.New("Could not locate kernel image: " + kernelImage) + return "", errors.New("Could not locate kernel image") } if util.IsDir(kernelDrivers) == false { - return errors.New("Could not locate kernel drivers: " + kernelDrivers) + return "", errors.New("Could not locate kernel drivers") } wwlog.Printf(wwlog.VERBOSE, "Setting up Kernel\n") if _, err := os.Stat(kernelImage); err == nil { err := util.CopyFile(kernelImage, kernelDestination) if err != nil { - return err + return "", err } - wwlog.Printf(wwlog.INFO, "%-45s: Done\n", "vmlinuz-"+kernelVersion) } wwlog.Printf(wwlog.VERBOSE, "Building Kernel driver image\n") @@ -102,10 +101,9 @@ func Build(kernelVersion string) error { cmd := fmt.Sprintf("cd /; find .%s | cpio --quiet -o -H newc -F \"%s\"", kernelDrivers, driversDestination) err := exec.Command("/bin/sh", "-c", cmd).Run() if err != nil { - return err + return "", err } - wwlog.Printf(wwlog.INFO, "%-45s: Done\n", "kmods-"+kernelVersion+".img") } - return nil + return "Done", nil } diff --git a/internal/pkg/node/constructors.go b/internal/pkg/node/constructors.go index 67fff9b3..4dbd2e1c 100644 --- a/internal/pkg/node/constructors.go +++ b/internal/pkg/node/constructors.go @@ -7,6 +7,7 @@ import ( "gopkg.in/yaml.v2" "io/ioutil" "regexp" + "sort" "strings" ) @@ -74,6 +75,9 @@ func (self *nodeYaml) FindAllNodes() ([]NodeInfo, error) { n.SystemOverlay.Set(node.SystemOverlay) n.RuntimeOverlay.Set(node.RuntimeOverlay) + n.Discoverable.SetB(node.Discoverable) + n.Disabled.SetB(node.Disabled) + for devname, netdev := range node.NetDevs { if _, ok := n.NetDevs[devname]; !ok { var netdev NetDevEntry @@ -113,6 +117,9 @@ func (self *nodeYaml) FindAllNodes() ([]NodeInfo, error) { n.SystemOverlay.SetAlt(self.NodeProfiles[p].SystemOverlay, pstring) n.RuntimeOverlay.SetAlt(self.NodeProfiles[p].RuntimeOverlay, pstring) + n.Disabled.SetAltB(self.NodeProfiles[p].Disabled, pstring) + n.Discoverable.SetAltB(self.NodeProfiles[p].Discoverable, pstring) + for devname, netdev := range self.NodeProfiles[p].NetDevs { if _, ok := n.NetDevs[devname]; !ok { var netdev NetDevEntry @@ -157,6 +164,9 @@ func (self *nodeYaml) FindAllProfiles() ([]NodeInfo, error) { p.RuntimeOverlay.Set(profile.RuntimeOverlay) p.SystemOverlay.Set(profile.SystemOverlay) + p.Disabled.SetB(profile.Disabled) + p.Discoverable.SetB(profile.Discoverable) + for devname, netdev := range profile.NetDevs { if _, ok := p.NetDevs[devname]; !ok { var netdev NetDevEntry @@ -177,9 +187,40 @@ func (self *nodeYaml) FindAllProfiles() ([]NodeInfo, error) { ret = append(ret, p) } + + sort.Slice(ret, func(i, j int) bool { + if ret[i].ClusterName.Get() < ret[j].ClusterName.Get() { + return true + } else if ret[i].ClusterName.Get() == ret[j].ClusterName.Get() { + if ret[i].Id.Get() < ret[j].Id.Get() { + return true + } + } + return false + }) + return ret, nil } +func (self *nodeYaml) FindDiscoverableNode() (NodeInfo, string, error) { + var ret NodeInfo + + nodes, _ := self.FindAllNodes() + + for _, node := range nodes { + if node.Discoverable.GetB() == false { + continue + } + for netdev, dev := range node.NetDevs { + if dev.Hwaddr.Defined() == false { + return node, netdev, nil + } + } + } + + return ret, "", errors.New("No unconfigured nodes found") +} + func (self *nodeYaml) FindByHwaddr(hwa string) (NodeInfo, error) { var ret NodeInfo diff --git a/internal/pkg/node/datastructure.go b/internal/pkg/node/datastructure.go index 2097fef2..2a23621e 100644 --- a/internal/pkg/node/datastructure.go +++ b/internal/pkg/node/datastructure.go @@ -1,6 +1,7 @@ package node import ( + "fmt" "github.com/hpcng/warewulf/internal/pkg/util" "github.com/hpcng/warewulf/internal/pkg/wwlog" "os" @@ -31,6 +32,7 @@ type NodeConf struct { RuntimeOverlay string `yaml:"runtime overlay files,omitempty"` SystemOverlay string `yaml:"system overlay files,omitempty"` Init string `yaml:"init,omitempty"` + Discoverable bool `yaml:"discoverable,omitempty"` Profiles []string `yaml:"profiles,omitempty"` NetDevs map[string]*NetDevs `yaml:"network devices,omitempty"` } @@ -73,6 +75,8 @@ type NodeInfo struct { IpmiPassword Entry RuntimeOverlay Entry SystemOverlay Entry + Discoverable Entry + Disabled Entry Init Entry //TODO: Finish adding this... Profiles []string GroupProfiles []string @@ -91,7 +95,20 @@ type NetDevEntry struct { func init() { //TODO: Check to make sure nodes.conf is found if util.IsFile(ConfigFile) == false { - wwlog.Printf(wwlog.ERROR, "Configuration file not found: %s\n", ConfigFile) - os.Exit(1) + c, err := os.OpenFile(ConfigFile, os.O_RDWR|os.O_CREATE, 0644) + if err != nil { + wwlog.Printf(wwlog.ERROR, "Could not create new configuration file: %s\n", err) + os.Exit(1) + } + + fmt.Fprintf(c, "nodeprofiles:\n") + fmt.Fprintf(c, " default:\n") + fmt.Fprintf(c, " comment: This profile is automatically included for each node\n") + fmt.Fprintf(c, " kernel args: crashkernel=no quiet\n") + fmt.Fprintf(c, "nodes: {}\n") + + c.Close() + + wwlog.Printf(wwlog.INFO, "Created default node configuration\n") } } diff --git a/internal/pkg/node/methods.go b/internal/pkg/node/methods.go index 9068831c..1fcbd7a7 100644 --- a/internal/pkg/node/methods.go +++ b/internal/pkg/node/methods.go @@ -41,8 +41,10 @@ func (self *Entry) SetAlt(val string, from string) { } func (self *Entry) SetAltB(val bool, from string) { - self.altbool = val - self.from = from + if val == true { + self.altbool = val + self.from = from + } return } @@ -106,6 +108,13 @@ func (self *Entry) Print() string { return "--" } +func (self *Entry) PrintB() bool { + if self.from == "" { + return self.bool + } + return self.altbool +} + func (self *Entry) Source() string { if self.value != "" && self.altvalue != "" { return "SUPERSEDED" diff --git a/internal/pkg/node/modifiers.go b/internal/pkg/node/modifiers.go index 1ba765d9..2eeaef69 100644 --- a/internal/pkg/node/modifiers.go +++ b/internal/pkg/node/modifiers.go @@ -67,6 +67,10 @@ func (self *nodeYaml) NodeUpdate(node NodeInfo) error { self.Nodes[nodeID].IpmiPassword = node.IpmiPassword.GetReal() self.Nodes[nodeID].RuntimeOverlay = node.RuntimeOverlay.GetReal() self.Nodes[nodeID].SystemOverlay = node.SystemOverlay.GetReal() + + self.Nodes[nodeID].Disabled = node.Disabled.GetRealB() + self.Nodes[nodeID].Discoverable = node.Discoverable.GetRealB() + self.Nodes[nodeID].Profiles = node.Profiles self.Nodes[nodeID].NetDevs = make(map[string]*NetDevs) @@ -140,6 +144,10 @@ func (self *nodeYaml) ProfileUpdate(profile NodeInfo) error { self.NodeProfiles[profileID].IpmiPassword = profile.IpmiPassword.GetReal() self.NodeProfiles[profileID].RuntimeOverlay = profile.RuntimeOverlay.GetReal() self.NodeProfiles[profileID].SystemOverlay = profile.SystemOverlay.GetReal() + + self.NodeProfiles[profileID].Disabled = profile.Disabled.GetRealB() + self.NodeProfiles[profileID].Discoverable = profile.Discoverable.GetRealB() + self.NodeProfiles[profileID].Profiles = profile.Profiles self.NodeProfiles[profileID].NetDevs = make(map[string]*NetDevs) diff --git a/internal/pkg/overlay/overlay.go b/internal/pkg/overlay/overlay.go index f8e3d253..419d8a57 100644 --- a/internal/pkg/overlay/overlay.go +++ b/internal/pkg/overlay/overlay.go @@ -119,9 +119,13 @@ func buildOverlay(nodeList []node.NodeInfo, overlayType string) error { var OverlayFile string if overlayType == "runtime" { + wwlog.Printf(wwlog.VERBOSE, "Building runtime overlay for: %s\n", n.Id.Get()) + OverlayDir = config.RuntimeOverlaySource(n.RuntimeOverlay.Get()) OverlayFile = config.RuntimeOverlayImage(n.Id.Get()) } else if overlayType == "system" { + wwlog.Printf(wwlog.VERBOSE, "Building system overlay for: %s\n", n.Id.Get()) + OverlayDir = config.SystemOverlaySource(n.RuntimeOverlay.Get()) OverlayFile = config.SystemOverlayImage(n.Id.Get()) } else { diff --git a/internal/app/warewulfd/response/container.go b/internal/pkg/warewulfd/container.go similarity index 97% rename from internal/app/warewulfd/response/container.go rename to internal/pkg/warewulfd/container.go index da58d804..1aa0b69d 100644 --- a/internal/app/warewulfd/response/container.go +++ b/internal/pkg/warewulfd/container.go @@ -1,4 +1,4 @@ -package response +package warewulfd import ( "github.com/hpcng/warewulf/internal/pkg/container" diff --git a/internal/pkg/warewulfd/daemon.go b/internal/pkg/warewulfd/daemon.go new file mode 100644 index 00000000..f52ffe9f --- /dev/null +++ b/internal/pkg/warewulfd/daemon.go @@ -0,0 +1,114 @@ +package warewulfd + +import ( + "fmt" + "github.com/hpcng/warewulf/internal/pkg/util" + "github.com/hpcng/warewulf/internal/pkg/wwlog" + "io/ioutil" + "os" + "os/exec" + "strconv" + "syscall" + "time" +) + +const ( + WAREWULFD_PIDFILE = "/tmp/warewulfd.pid" + WAREWULFD_LOGFILE = "/tmp/warewulfd.log" +) + +func DaemonStart() error { + + if os.Getenv("WAREWULFD_BACKGROUND") == "1" { + RunServer() + + } else { + os.Setenv("WAREWULFD_BACKGROUND", "1") + + f, err := os.OpenFile(WAREWULFD_LOGFILE, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644) + if err != nil { + return err + } + + p, err := os.OpenFile(WAREWULFD_PIDFILE, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { + return err + } + + cmd := exec.Command(os.Args[0], "server", "start") + cmd.Stdout = f + cmd.Stderr = f + cmd.Start() + pid := cmd.Process.Pid + + fmt.Fprintf(p, "%d", pid) + + p.Close() + + time.Sleep(1 * time.Second) + + DaemonStatus() + } + + return nil +} + +func DaemonStatus() error { + + if util.IsFile(WAREWULFD_PIDFILE) == false { + wwlog.Printf(wwlog.INFO, "Warewulf daemon process not running (%s)\n", WAREWULFD_PIDFILE) + return nil + } + + dat, err := ioutil.ReadFile(WAREWULFD_PIDFILE) + if err != nil { + return err + } + + pid, _ := strconv.Atoi(string(dat)) + process, err := os.FindProcess(pid) + if err != nil { + fmt.Printf("Failed to find process: %s\n", err) + return err + } else { + err := process.Signal(syscall.Signal(0)) + if err != nil { + fmt.Printf("SIGCONT on pid %d returned: %v\n", pid, err) + return err + } else { + fmt.Printf("Warewulf daemon is running at PID: %d\n", pid) + } + } + + return nil +} + +func DaemonStop() error { + + if util.IsFile(WAREWULFD_PIDFILE) == false { + wwlog.Printf(wwlog.INFO, "Warewulf daemon process not running (%s)\n", WAREWULFD_PIDFILE) + return nil + } + + dat, err := ioutil.ReadFile(WAREWULFD_PIDFILE) + if err != nil { + return err + } + + pid, _ := strconv.Atoi(string(dat)) + process, err := os.FindProcess(pid) + if err != nil { + fmt.Printf("Failed to find process: %s\n", err) + } else { + err := process.Signal(syscall.Signal(15)) + if err != nil { + fmt.Printf("SIGCONT on pid %d returned: %v\n", pid, err) + } else { + fmt.Printf("Terminated Warewulf process at PID: %d\n", pid) + } + } + + os.Remove(WAREWULFD_PIDFILE) + + return nil +} diff --git a/internal/pkg/warewulfd/ipxe.go b/internal/pkg/warewulfd/ipxe.go new file mode 100644 index 00000000..5fde3bc5 --- /dev/null +++ b/internal/pkg/warewulfd/ipxe.go @@ -0,0 +1,144 @@ +package warewulfd + +import ( + "fmt" + "github.com/hpcng/warewulf/internal/pkg/node" + "github.com/hpcng/warewulf/internal/pkg/overlay" + "github.com/hpcng/warewulf/internal/pkg/warewulfconf" + "github.com/hpcng/warewulf/internal/pkg/wwlog" + "log" + "net/http" + "strconv" + "strings" + "text/template" +) + +type iPxeTemplate struct { + Message string + WaitTime string + Hostname string + Fqdn string + ContainerName string + Hwaddr string + Ipaddr string + Port string + KernelArgs string + KernelVersion string +} + +func IpxeSend(w http.ResponseWriter, req *http.Request) { + url := strings.Split(req.URL.Path, "/") + var unconfiguredNode bool + + nodeDB, err := node.New() + if err != nil { + log.Printf("Could not read node configuration file: %s\n", err) + w.WriteHeader(503) + return + } + + if url[2] == "" { + log.Printf("ERROR: Bad iPXE request from %s\n", req.RemoteAddr) + w.WriteHeader(404) + return + } + + conf, err := warewulfconf.New() + if err != nil { + wwlog.Printf(wwlog.ERROR, "%s\n", err) + w.WriteHeader(503) + return + } + + hwaddr := strings.ReplaceAll(url[2], "-", ":") + + n, err := nodeDB.FindByHwaddr(hwaddr) + if err != nil { + // If we failed to find a node, let's see if we can add one... + var netdev string + + wwlog.Printf(wwlog.VERBOSE, "Node was not found, looking for discoverable nodes...\n") + + n, netdev, err = nodeDB.FindDiscoverableNode() + if err != nil { + wwlog.Printf(wwlog.WARN, "No nodes are set as discoverable...\n") + unconfiguredNode = true + + } else { + wwlog.Printf(wwlog.INFO, "Adding new configuration to discoverable node: %s\n", n.Id.Get()) + + n.NetDevs[netdev].Hwaddr.Set(hwaddr) + n.Discoverable.SetB(false) + err := nodeDB.NodeUpdate(n) + if err != nil { + wwlog.Printf(wwlog.ERROR, "Could not add discovered configuration for node: %s\n", n.Id.Get()) + unconfiguredNode = true + } else { + err := nodeDB.Persist() + if err != nil { + wwlog.Printf(wwlog.ERROR, "Could not persist new node configuration while adding node: %s\n", n.Id.Get()) + unconfiguredNode = true + } else { + wwlog.Printf(wwlog.INFO, "Building System Overlay:\n") + _ = overlay.BuildSystemOverlay([]node.NodeInfo{n}) + wwlog.Printf(wwlog.INFO, "Building Runtime Overlay:\n") + _ = overlay.BuildRuntimeOverlay([]node.NodeInfo{n}) + } + } + } + } + + if unconfiguredNode == true { + log.Printf("UNCONFIGURED NODE: %15s\n", hwaddr) + + tmpl, err := template.ParseFiles("/etc/warewulf/ipxe/unconfigured.ipxe") + if err != nil { + wwlog.Printf(wwlog.ERROR, "%s\n", err) + return + } + + var replace iPxeTemplate + + replace.Hwaddr = hwaddr + + err = tmpl.Execute(w, replace) + if err != nil { + wwlog.Printf(wwlog.ERROR, "%s\n", err) + return + } + + return + + } else { + log.Printf("IPXE: %15s: %s\n", n.Id.Get(), req.URL.Path) + + ipxeTemplate := fmt.Sprintf("/etc/warewulf/ipxe/%s.ipxe", n.Ipxe.Get()) + + tmpl, err := template.ParseFiles(ipxeTemplate) + if err != nil { + wwlog.Printf(wwlog.ERROR, "%s\n", err) + return + } + + var replace iPxeTemplate + + replace.Fqdn = n.Id.Get() + replace.Ipaddr = conf.Ipaddr + replace.Port = strconv.Itoa(conf.Warewulf.Port) + replace.Hostname = n.Id.Get() + replace.Hwaddr = url[2] + replace.ContainerName = n.ContainerName.Get() + replace.KernelArgs = n.KernelArgs.Get() + replace.KernelVersion = n.KernelVersion.Get() + + err = tmpl.Execute(w, replace) + if err != nil { + wwlog.Printf(wwlog.ERROR, "%s\n", err) + return + } + + log.Printf("SEND: %15s: %s\n", n.Id.Get(), ipxeTemplate) + + } + return +} diff --git a/internal/app/warewulfd/response/kernel.go b/internal/pkg/warewulfd/kernel.go similarity index 97% rename from internal/app/warewulfd/response/kernel.go rename to internal/pkg/warewulfd/kernel.go index e530de98..4d8d7a5c 100644 --- a/internal/app/warewulfd/response/kernel.go +++ b/internal/pkg/warewulfd/kernel.go @@ -1,4 +1,4 @@ -package response +package warewulfd import ( "github.com/hpcng/warewulf/internal/pkg/kernel" diff --git a/internal/app/warewulfd/response/kmods.go b/internal/pkg/warewulfd/kmods.go similarity index 97% rename from internal/app/warewulfd/response/kmods.go rename to internal/pkg/warewulfd/kmods.go index 4622b3b2..7558de83 100644 --- a/internal/app/warewulfd/response/kmods.go +++ b/internal/pkg/warewulfd/kmods.go @@ -1,4 +1,4 @@ -package response +package warewulfd import ( "github.com/hpcng/warewulf/internal/pkg/kernel" diff --git a/internal/app/warewulfd/response/runtime.go b/internal/pkg/warewulfd/runtime.go similarity index 99% rename from internal/app/warewulfd/response/runtime.go rename to internal/pkg/warewulfd/runtime.go index ec6291b7..7f13668d 100644 --- a/internal/app/warewulfd/response/runtime.go +++ b/internal/pkg/warewulfd/runtime.go @@ -1,4 +1,4 @@ -package response +package warewulfd import ( "fmt" diff --git a/internal/app/warewulfd/response/system.go b/internal/pkg/warewulfd/system.go similarity index 97% rename from internal/app/warewulfd/response/system.go rename to internal/pkg/warewulfd/system.go index 3a3cf285..3a92a7a5 100644 --- a/internal/app/warewulfd/response/system.go +++ b/internal/pkg/warewulfd/system.go @@ -1,4 +1,4 @@ -package response +package warewulfd import ( "github.com/hpcng/warewulf/internal/pkg/config" diff --git a/internal/app/warewulfd/response/util.go b/internal/pkg/warewulfd/util.go similarity index 98% rename from internal/app/warewulfd/response/util.go rename to internal/pkg/warewulfd/util.go index 8ef67270..3e5403ab 100644 --- a/internal/app/warewulfd/response/util.go +++ b/internal/pkg/warewulfd/util.go @@ -1,4 +1,4 @@ -package response +package warewulfd import ( "fmt" diff --git a/internal/pkg/warewulfd/warewulfd.go b/internal/pkg/warewulfd/warewulfd.go new file mode 100644 index 00000000..ba90adcc --- /dev/null +++ b/internal/pkg/warewulfd/warewulfd.go @@ -0,0 +1,27 @@ +package warewulfd + +import ( + "github.com/hpcng/warewulf/internal/pkg/wwlog" + "net/http" +) + +// TODO: https://github.com/danderson/netboot/blob/master/pixiecore/dhcp.go +// TODO: https://github.com/pin/tftp + +func RunServer() error { + + wwlog.Printf(wwlog.DEBUG, "Registering handlers for the web service\n") + + http.HandleFunc("/ipxe/", IpxeSend) + http.HandleFunc("/kernel/", KernelSend) + http.HandleFunc("/kmods/", KmodsSend) + http.HandleFunc("/container/", ContainerSend) + http.HandleFunc("/overlay-system/", SystemOverlaySend) + http.HandleFunc("/overlay-runtime", RuntimeOverlaySend) + + wwlog.Printf(wwlog.VERBOSE, "Starting HTTPD REST service\n") + + http.ListenAndServe(":9873", nil) + + return nil +}