diff --git a/go.mod b/go.mod index ad9c083d..e143d362 100644 --- a/go.mod +++ b/go.mod @@ -79,6 +79,7 @@ require ( github.com/prometheus/procfs v0.6.0 // indirect github.com/rootless-containers/proto v0.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/sassoftware/go-rpmutils v0.2.0 // indirect github.com/sirupsen/logrus v1.8.1 // indirect github.com/spf13/pflag v1.0.6-0.20210604193023-d5e0c0615ace // indirect github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980 // indirect diff --git a/go.sum b/go.sum index 6a7ee2ff..bd1727b5 100644 --- a/go.sum +++ b/go.sum @@ -409,6 +409,7 @@ github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBp github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= @@ -863,6 +864,7 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/klauspost/compress v1.10.9/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.1/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.12.1 h1:/+xsCsk06wE38cyiqOR/o7U2fSftcH72xD+BQXmja/g= github.com/klauspost/compress v1.12.1/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/pgzip v1.2.4/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= @@ -1043,6 +1045,8 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= +github.com/sassoftware/go-rpmutils v0.2.0 h1:pKW0HDYMFWQ5b4JQPiI3WI12hGsVoW0V8+GMoZiI/JE= +github.com/sassoftware/go-rpmutils v0.2.0/go.mod h1:TJJQYtLe/BeEmEjelI3b7xNZjzAukEkeWKmoakvaOoI= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 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= @@ -1116,6 +1120,7 @@ github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKw github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8= github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= @@ -1146,6 +1151,7 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1: github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -1174,6 +1180,7 @@ go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqe go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -1192,6 +1199,7 @@ golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa h1:idItI2DDfCokpg0N51B2VtiLdJ4vAuXC9fnCb2gACo4= @@ -1454,6 +1462,7 @@ golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1504,6 +1513,7 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= diff --git a/internal/pkg/overlay/config.go b/internal/pkg/overlay/config.go index 8cc09d95..cbfe81b7 100644 --- a/internal/pkg/overlay/config.go +++ b/internal/pkg/overlay/config.go @@ -28,29 +28,32 @@ func OverlaySourceDir(overlayName string) string { return overlaypath } -/* -Returns the overlay name of the image for a given node -*/ -func OverlayImage(nodeName string, overlayName []string, img_context ...string) string { +// OverlayImage returns the full path to an overlay image based on the +// context and the overlays contained in it. +// +// If a context is provided, the image file name is based on that +// context name, in the form __{CONTEXT}__. +// +// If the context is empty ("") the image file name is a concatenated +// list of the contained overlays joined by "-". +// +// If the context is empty and no overlays are specified, the empty +// string is returned. +func OverlayImage(nodeName string, context string, overlayNames []string) string { var name string - var context string - - /* Check optional context argument. If missing, default to legacy. */ - if len(img_context) == 0 { - context = "legacy" + if context != "" { + if len(overlayNames) > 0 { + wwlog.Warn("context(%v) and overlays(%v) specified: prioritizing context(%v)", + context, overlayNames, context) + } + name = "__" + strings.ToUpper(context) + "__.img" + } else if len(overlayNames) > 0 { + name = strings.Join(overlayNames, "-")+".img" } else { - context = img_context[0] + wwlog.Warn("unable to generate overlay image path: no context or overlays specified") + return "" } conf := warewulfconf.Get() - - switch context { - case "legacy": - name = strings.Join(overlayName, "-")+".img" - default: - wwlog.Warn("Context %s passed to OverlayImage(), using %s to build image name.", context, "__" + strings.ToUpper(context) + "__") - name = "__" + strings.ToUpper(context) + "__.img" - } - return path.Join(conf.Paths.WWProvisiondir, "overlays/", nodeName, name) } diff --git a/internal/pkg/overlay/config_test.go b/internal/pkg/overlay/config_test.go new file mode 100644 index 00000000..34254a45 --- /dev/null +++ b/internal/pkg/overlay/config_test.go @@ -0,0 +1,38 @@ +package overlay + +import ( + "testing" + warewulfconf "github.com/hpcng/warewulf/internal/pkg/config" +) + +var overlayImageTests = []struct{ + description string + node string + context string + overlays []string + image string +}{ + {"all empty", "", "", nil, ""}, + {"empty with named context", "", "system", nil, "p/overlays/__SYSTEM__.img"}, + {"empty with named overlay", "", "", []string{"o1"}, "p/overlays/o1.img"}, + {"empty with two named overlays", "", "", []string{"o1", "o2"}, "p/overlays/o1-o2.img"}, + {"empty node", "node1", "", nil, ""}, + {"node system overlay", "node1", "system", nil, "p/overlays/node1/__SYSTEM__.img"}, + {"node runtime overlay", "node1", "runtime", nil, "p/overlays/node1/__RUNTIME__.img"}, + {"node single overlay", "node1", "", []string{"o1"}, "p/overlays/node1/o1.img"}, + {"node two overlays", "node1", "", []string{"o1", "o2"}, "p/overlays/node1/o1-o2.img"}, + {"node with context and overlays", "node1", "system", []string{"o1", "o2"}, "p/overlays/node1/__SYSTEM__.img"}, +} + +func Test_OverlayImage(t *testing.T) { + conf := warewulfconf.Get() + conf.Paths.WWProvisiondir = "p" + for _, tt := range overlayImageTests { + t.Run(tt.description, func(t *testing.T) { + out := OverlayImage(tt.node, tt.context, tt.overlays) + if tt.image != out { + t.Errorf("got %q, want %q", out, tt.image) + } + }) + } +} diff --git a/internal/pkg/overlay/overlay.go b/internal/pkg/overlay/overlay.go index 969ea291..c9e34c11 100644 --- a/internal/pkg/overlay/overlay.go +++ b/internal/pkg/overlay/overlay.go @@ -47,13 +47,13 @@ func BuildAllOverlays(nodes []node.NodeInfo) error { sysOverlays := n.SystemOverlay.GetSlice() wwlog.Info("Building system overlays for %s: [%s]", n.Id.Get(), strings.Join(sysOverlays, ", ")) - err := BuildOverlay(n, sysOverlays, "system") + err := BuildOverlay(n, "system", sysOverlays) if err != nil { return errors.Wrapf(err, "could not build system overlays %v for node %s", sysOverlays, n.Id.Get()) } runOverlays := n.RuntimeOverlay.GetSlice() wwlog.Info("Building runtime overlays for %s: [%s]", n.Id.Get(), strings.Join(runOverlays, ", ")) - err = BuildOverlay(n, runOverlays, "runtime") + err = BuildOverlay(n, "runtime", runOverlays) if err != nil { return errors.Wrapf(err, "could not build runtime overlays %v for node %s", runOverlays, n.Id.Get()) } @@ -67,11 +67,11 @@ func BuildAllOverlays(nodes []node.NodeInfo) error { func BuildSpecificOverlays(nodes []node.NodeInfo, overlayNames []string) error { for _, n := range nodes { wwlog.Info("Building overlay for %s: %v", n.Id.Get(), overlayNames) - for _, overlayName := range overlayNames { - err := BuildOverlay(n, []string{overlayName}) - if err != nil { - return errors.Wrapf(err, "could not build overlay %s for node %s", overlayName, n.Id.Get()) - } + for _, overlayName := range overlayNames { + err := BuildOverlay(n, "", []string{overlayName}) + if err != nil { + return errors.Wrapf(err, "could not build overlay %s for node %s", overlayName, n.Id.Get()) + } } } @@ -140,18 +140,10 @@ func OverlayInit(overlayName string) error { /* Build the given overlays for a node and create a Image for them */ -func BuildOverlay(nodeInfo node.NodeInfo, overlayNames []string, img_context ...string) error { - var context string - /* Check optional context argument. If missing, default to legacy. */ - if len(img_context) == 0 { - context = "legacy" - } else { - context = img_context[0] - } - +func BuildOverlay(nodeInfo node.NodeInfo, context string, overlayNames []string) error { // create the dir where the overlay images will reside name := fmt.Sprintf("overlay %s/%v", nodeInfo.Id.Get(), overlayNames) - overlayImage := OverlayImage(nodeInfo.Id.Get(), overlayNames, context) + overlayImage := OverlayImage(nodeInfo.Id.Get(), context, overlayNames) overlayImageDir := path.Dir(overlayImage) err := os.MkdirAll(overlayImageDir, 0755) diff --git a/internal/pkg/overlay/overlay_test.go b/internal/pkg/overlay/overlay_test.go new file mode 100644 index 00000000..25944a83 --- /dev/null +++ b/internal/pkg/overlay/overlay_test.go @@ -0,0 +1,232 @@ +package overlay + +import ( + "io" + "os" + "path" + "strings" + "sort" + "testing" + "github.com/stretchr/testify/assert" + "github.com/sassoftware/go-rpmutils/cpio" + warewulfconf "github.com/hpcng/warewulf/internal/pkg/config" + "github.com/hpcng/warewulf/internal/pkg/node" +) + +var buildOverlayTests = []struct{ + description string + nodeName string + context string + overlays []string + image string + contents []string +}{ + {"empty", "", "", nil, "", nil}, + {"empty node", "node1", "", nil, "", nil}, + {"empty context", "", "system", nil, "", nil}, + {"empty overlay", "", "", []string{"o1"}, "o1.img", []string{"o1.txt"}}, + {"single overlay", "node1", "", []string{"o1"}, "node1/o1.img", []string{"o1.txt"}}, + {"multiple overlays", "node1", "", []string{"o1", "o2"}, "node1/o1-o2.img", []string{"o1.txt", "o2.txt"}}, + {"empty system overlay", "node1", "system", nil, "", nil}, + {"empty runtime overlay", "node1", "runtime", nil, "", nil}, + {"single system overlay", "node1", "system", []string{"o1"}, "node1/__SYSTEM__.img", []string{"o1.txt"}}, + {"single runtime overlay", "node1", "runtime", []string{"o1"}, "node1/__RUNTIME__.img", []string{"o1.txt"}}, + {"two system overlays", "node1", "system", []string{"o1", "o2"}, "node1/__SYSTEM__.img", []string{"o1.txt", "o2.txt"}}, + {"two runtime overlays", "node1", "runtime", []string{"o1", "o2"}, "node1/__RUNTIME__.img", []string{"o1.txt", "o2.txt"}}, +} + +func Test_BuildOverlay(t *testing.T) { + conf := warewulfconf.Get() + overlayDir, overlayDirErr := os.MkdirTemp(os.TempDir(), "ww-test-overlay-*") + assert.NoError(t, overlayDirErr) + defer os.RemoveAll(overlayDir) + conf.Paths.WWOverlaydir = overlayDir + assert.NoError(t, os.Mkdir(path.Join(overlayDir, "o1"), 0700)) + { _, err := os.Create(path.Join(overlayDir, "o1", "o1.txt")); assert.NoError(t, err) } + assert.NoError(t, os.Mkdir(path.Join(overlayDir, "o2"), 0700)) + { _, err := os.Create(path.Join(overlayDir, "o2", "o2.txt")); assert.NoError(t, err) } + + for _, tt := range buildOverlayTests { + assert.True(t, (tt.image != "" && tt.contents != nil) || (tt.image == "" && tt.contents == nil), + "image and contents must eiher be populated or empty together") + + nodeInfo := node.NodeInfo{} + nodeInfo.Id.Set(tt.nodeName) + t.Run(tt.description, func(t *testing.T) { + provisionDir, provisionDirErr := os.MkdirTemp(os.TempDir(), "ww-test-provision-*") + assert.NoError(t, provisionDirErr) + defer os.RemoveAll(provisionDir) + conf.Paths.WWProvisiondir = provisionDir + + err := BuildOverlay(nodeInfo, tt.context, tt.overlays) + if len(tt.image) > 0 { + image := path.Join(provisionDir, "overlays", tt.image) + assert.FileExists(t, image) + assert.NoError(t, err) + + sort.Strings(tt.contents) + files := cpioFiles(t, image) + sort.Strings(files) + assert.Equal(t, tt.contents, files) + } else { + assert.Error(t, err) + dirName := path.Join(provisionDir, "overlays", tt.nodeName) + isEmpty := dirIsEmpty(t, dirName) + assert.True(t, isEmpty, "%v should be empty, but isn't", dirName) + } + }) + } +} + +var buildAllOverlaysTests = []struct{ + description string + nodes []string + systemOverlays []string + runtimeOverlays []string + succeed bool +}{ + {"no nodes", nil, nil, nil, true}, + {"single empty node", []string{"node1"}, nil, nil, false}, + {"two empty node", []string{"node1", "node2"}, nil, nil, false}, + {"single node with system overlay", []string{"node1"}, + []string{"o1"}, nil, false}, + {"two nodes with system overlays", []string{"node1", "node2"}, + []string{"o1", "o1,o2"}, nil, false}, + {"single node with runtime overlay", []string{"node1"}, + nil, []string{"o1"}, false}, + {"two nodes with runtime overlays", []string{"node1", "node2"}, + nil, []string{"o1", "o1,o2"}, false}, + {"stingle node with full overlays", []string{"node1"}, + []string{"o1"}, []string{"o2"}, true}, + {"two nodes with full overlays", []string{"node1", "node2"}, + []string{"o1", "o1,o2"}, []string{"o2", "o2"}, true}, +} + + +func Test_BuildAllOverlays(t *testing.T) { + conf := warewulfconf.Get() + overlayDir, overlayDirErr := os.MkdirTemp(os.TempDir(), "ww-test-overlay-*") + assert.NoError(t, overlayDirErr) + defer os.RemoveAll(overlayDir) + conf.Paths.WWOverlaydir = overlayDir + assert.NoError(t, os.Mkdir(path.Join(overlayDir, "o1"), 0700)) + assert.NoError(t, os.Mkdir(path.Join(overlayDir, "o2"), 0700)) + + for _, tt := range buildAllOverlaysTests { + t.Run(tt.description, func(t *testing.T) { + provisionDir, provisionDirErr := os.MkdirTemp(os.TempDir(), "ww-test-provision-*") + assert.NoError(t, provisionDirErr) + defer os.RemoveAll(provisionDir) + conf.Paths.WWProvisiondir = provisionDir + + var nodes []node.NodeInfo + for i, nodeName := range tt.nodes { + nodeInfo := node.NodeInfo{} + nodeInfo.Id.Set(nodeName) + if tt.systemOverlays != nil { + nodeInfo.SystemOverlay.SetSlice(strings.Split(tt.systemOverlays[i], ",")) + } + if tt.runtimeOverlays != nil { + nodeInfo.RuntimeOverlay.SetSlice(strings.Split(tt.runtimeOverlays[i], ",")) + } + nodes = append(nodes, nodeInfo) + } + err := BuildAllOverlays(nodes) + if !tt.succeed { + assert.Error(t, err) + } else { + assert.NoError(t, err) + for _, nodeName := range tt.nodes { + assert.FileExists(t, path.Join(provisionDir, "overlays", nodeName, "__SYSTEM__.img")) + assert.FileExists(t, path.Join(provisionDir, "overlays", nodeName, "__RUNTIME__.img")) + } + } + }) + } +} + +var buildSpecificOverlaysTests = []struct{ + description string + nodes []string + overlays string + images []string + succeed bool +}{ + {"no nodes", nil, "", nil, true}, + {"single empty node", []string{"node1"}, "", nil, false}, + {"two empty node", []string{"node1", "node2"}, "", nil, false}, + {"single node with single overlay", []string{"node1"}, "o1", + []string{"node1/o1.img"}, true}, + {"two nodes with single overlay", []string{"node1", "node2"}, "o1", + []string{"node1/o1.img", "node2/o1.img"}, true}, + {"single node with multi overlay", []string{"node1"}, "o1,o2", + []string{"node1/o1.img", "node1/o2.img"}, true}, + {"two nodes with multi overlays", []string{"node1", "node2"}, "o1,o2", + []string{"node1/o1.img", "node1/o2.img", "node2/o1.img", "node2/o2.img"}, true}, +} + + +func Test_BuildSpecificOverlays(t *testing.T) { + conf := warewulfconf.Get() + overlayDir, overlayDirErr := os.MkdirTemp(os.TempDir(), "ww-test-overlay-*") + assert.NoError(t, overlayDirErr) + defer os.RemoveAll(overlayDir) + conf.Paths.WWOverlaydir = overlayDir + assert.NoError(t, os.Mkdir(path.Join(overlayDir, "o1"), 0700)) + assert.NoError(t, os.Mkdir(path.Join(overlayDir, "o2"), 0700)) + + for _, tt := range buildSpecificOverlaysTests { + t.Run(tt.description, func(t *testing.T) { + provisionDir, provisionDirErr := os.MkdirTemp(os.TempDir(), "ww-test-provision-*") + assert.NoError(t, provisionDirErr) + defer os.RemoveAll(provisionDir) + conf.Paths.WWProvisiondir = provisionDir + + var nodes []node.NodeInfo + for _, nodeName := range tt.nodes { + nodeInfo := node.NodeInfo{} + nodeInfo.Id.Set(nodeName) + nodes = append(nodes, nodeInfo) + } + err := BuildSpecificOverlays(nodes, strings.Split(tt.overlays, ",")) + if !tt.succeed { + assert.Error(t, err) + } else { + assert.NoError(t, err) + for _, image := range tt.images { + assert.FileExists(t, path.Join(provisionDir, "overlays", image)) + } + } + }) + } +} + +func dirIsEmpty(t *testing.T, name string) bool { + f, err := os.Open(name) + if err != nil { + t.Log(err) + return true + } + defer f.Close() + + dirnames, err2 := f.Readdirnames(1) + if err2 == io.EOF { + t.Log(err2) + return true + } + t.Log(dirnames) + return false +} + +func cpioFiles(t *testing.T, name string) (files []string) { + f, openErr := os.Open(name) + if openErr != nil { return } + defer f.Close() + + reader := cpio.NewReader(f) + for { + header, err := reader.Next() + if err != nil { return } + files = append(files, header.Filename()) + } +} diff --git a/internal/pkg/warewulfd/provision.go b/internal/pkg/warewulfd/provision.go index d83fd70d..df9ca489 100644 --- a/internal/pkg/warewulfd/provision.go +++ b/internal/pkg/warewulfd/provision.go @@ -5,6 +5,7 @@ import ( "net/http" "path" "strconv" + "strings" "text/template" "github.com/hpcng/warewulf/internal/pkg/container" @@ -29,6 +30,13 @@ type iPxeTemplate struct { KernelOverride string } +var status_stages = map[string]string{ + "ipxe": "IPXE", + "kernel": "KERNEL", + "kmods": "KMODS_OVERLAY", + "system": "SYSTEM_OVERLAY", + "runtime": "RUNTIME_OVERLAY"} + func ProvisionSend(w http.ResponseWriter, req *http.Request) { conf := warewulfconf.Get() @@ -49,17 +57,8 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) { } } - status_stages := map[string]string{ - "ipxe": "IPXE", - "kernel": "KERNEL", - "kmods": "KMODS_OVERLAY", - "system": "SYSTEM_OVERLAY", - "runtime": "RUNTIME_OVERLAY"} - status_stage := status_stages[rinfo.stage] - var stage_overlays []string - var stage_file string = "" - var img_context string = "legacy" /* Default to old image name behavior */ + var stage_file string // TODO: when module version is upgraded to go1.18, should be 'any' type var tmpl_data interface{} @@ -127,33 +126,20 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) { wwlog.Warn("No container set for node %s", node.Id.Get()) } - } else if rinfo.stage == "system" { - if len(node.SystemOverlay.GetSlice()) != 0 { - stage_overlays = node.SystemOverlay.GetSlice() - img_context = rinfo.stage + } else if rinfo.stage == "system" || rinfo.stage == "runtime" { + var context string + var request_overlays []string + + if len(rinfo.overlay) > 0 { + request_overlays = strings.Split(rinfo.overlay, ",") } else { - wwlog.Warn("No system overlay set for node %s", node.Id.Get()) + context = rinfo.stage } - - } else if rinfo.stage == "runtime" { - if rinfo.overlay != "" { - stage_overlays = []string{rinfo.overlay} - img_context = "legacy" - } else if len(node.RuntimeOverlay.GetSlice()) != 0 { - stage_overlays = node.RuntimeOverlay.GetSlice() - img_context = rinfo.stage - } else { - wwlog.Warn("No runtime overlay set for node %s", node.Id.Get()) - } - - } - - if len(stage_overlays) > 0 { stage_file, err = getOverlayFile( node.Id.Get(), - stage_overlays, - conf.Warewulf.AutobuildOverlays, - img_context ) + context, + request_overlays, + conf.Warewulf.AutobuildOverlays) if err != nil { w.WriteHeader(http.StatusInternalServerError) diff --git a/internal/pkg/warewulfd/provision_test.go b/internal/pkg/warewulfd/provision_test.go new file mode 100644 index 00000000..f79dd9e0 --- /dev/null +++ b/internal/pkg/warewulfd/provision_test.go @@ -0,0 +1,69 @@ +package warewulfd + +import ( + "io/ioutil" + "net/http" + "net/http/httptest" + "os" + "path" + "testing" + "github.com/stretchr/testify/assert" + + "github.com/hpcng/warewulf/internal/pkg/node" + warewulfconf "github.com/hpcng/warewulf/internal/pkg/config" +) + + +var provisionSendTests = []struct{ + description string + url string + body string + status int +}{ + {"system overlay", "/overlay-system/00:00:00:ff:ff:ff", "system overlay", 200}, + {"runtime overlay", "/overlay-runtime/00:00:00:ff:ff:ff", "runtime overlay", 200}, + {"fake overlay", "/overlay-system/00:00:00:ff:ff:ff?overlay=fake", "", 404}, + {"specific overlay", "/overlay-system/00:00:00:ff:ff:ff?overlay=o1", "specific overlay", 200}, +} + +func Test_ProvisionSend(t *testing.T) { + file, err := os.CreateTemp(os.TempDir(), "ww-test-nodes.conf-*") + assert.NoError(t, err) + defer file.Close() + { _, err := file.WriteString(`WW_INTERNAL: 43 +nodes: + n1: + network devices: + default: + hwaddr: 00:00:00:ff:ff:ff`); assert.NoError(t, err) } + assert.NoError(t, file.Sync()) + node.ConfigFile = file.Name() + dbErr := LoadNodeDB() + assert.NoError(t, dbErr) + + provisionDir, provisionDirErr := os.MkdirTemp(os.TempDir(), "ww-test-provision-*") + assert.NoError(t, provisionDirErr) + defer os.RemoveAll(provisionDir) + conf := warewulfconf.Get() + conf.Paths.WWProvisiondir = provisionDir + conf.Warewulf.Secure = false + assert.NoError(t, os.MkdirAll(path.Join(provisionDir, "overlays", "n1"), 0700)) + assert.NoError(t, os.WriteFile(path.Join(provisionDir, "overlays", "n1", "__SYSTEM__.img"), []byte("system overlay"), 0600)) + assert.NoError(t, os.WriteFile(path.Join(provisionDir, "overlays", "n1", "__RUNTIME__.img"), []byte("runtime overlay"), 0600)) + assert.NoError(t, os.WriteFile(path.Join(provisionDir, "overlays", "n1", "o1.img"), []byte("specific overlay"), 0600)) + + for _, tt := range provisionSendTests { + t.Run(tt.description, func(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, tt.url, nil) + w := httptest.NewRecorder() + ProvisionSend(w, req) + res := w.Result() + defer res.Body.Close() + + data, readErr := ioutil.ReadAll(res.Body) + assert.NoError(t, readErr) + assert.Equal(t, tt.body, string(data)) + assert.Equal(t, tt.status, res.StatusCode) + }) + } +} diff --git a/internal/pkg/warewulfd/util.go b/internal/pkg/warewulfd/util.go index 4e497c6f..3cebbe1b 100644 --- a/internal/pkg/warewulfd/util.go +++ b/internal/pkg/warewulfd/util.go @@ -43,11 +43,11 @@ func sendFile( func getOverlayFile( nodeId string, + context string, stage_overlays []string, - autobuild bool, - img_context string) (stage_file string, err error) { + autobuild bool) (stage_file string, err error) { - stage_file = overlay.OverlayImage(nodeId, stage_overlays, img_context) + stage_file = overlay.OverlayImage(nodeId, context, stage_overlays) err = nil build := !util.IsFile(stage_file) diff --git a/internal/pkg/warewulfd/util_test.go b/internal/pkg/warewulfd/util_test.go new file mode 100644 index 00000000..671a94a9 --- /dev/null +++ b/internal/pkg/warewulfd/util_test.go @@ -0,0 +1,42 @@ +package warewulfd + +import ( + "testing" + "github.com/stretchr/testify/assert" + warewulfconf "github.com/hpcng/warewulf/internal/pkg/config" +) + +var getOverlayFileTests = []struct{ + description string + node string + context string + overlays []string + result string + succeed bool +}{ + {"empty", "", "", nil, "", true}, + {"empty node", "node1", "", nil, "", true}, + {"specific overlays without node", "", "", []string{"o1", "o2"}, "p/overlays/o1-o2.img", true}, + {"system overlay", "node1", "system", nil, "p/overlays/node1/__SYSTEM__.img", true}, + {"runtime overlay", "node1", "runtime", nil, "p/overlays/node1/__RUNTIME__.img", true}, + {"specific overlay", "node1", "", []string{"o1"}, "p/overlays/node1/o1.img", true}, + {"multiple specific overlays", "node1", "", []string{"o1", "o2"}, "p/overlays/node1/o1-o2.img", true}, + {"multiple specific overlays with context", "node1", "system", []string{"o1", "o2"}, "p/overlays/node1/__SYSTEM__.img", true}, +} + +func Test_getOverlayFile(t *testing.T) { + conf := warewulfconf.Get() + conf.Paths.WWProvisiondir = "p" + for _, tt := range getOverlayFileTests { + t.Run(tt.description, func(t *testing.T) { + result, err := getOverlayFile(tt.node, tt.context, tt.overlays, false) + if !tt.succeed { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + + assert.Equal(t, tt.result, result) + }) + } +}