[lxc-devel] [distrobuilder/master] Add missing tests for lxc/lxd image generation

monstermunchkin on Github lxc-bot at linuxcontainers.org
Wed Feb 28 10:09:52 UTC 2018


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 322 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20180228/9777bbc2/attachment.bin>
-------------- next part --------------
From d1946033d0481a8de624f8c7621c5c34e29a82ec Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Mon, 26 Feb 2018 17:04:29 +0100
Subject: [PATCH 1/2] image: Add LXD tests

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 image/lxd.go      |  26 ++++++--
 image/lxd_test.go | 179 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 199 insertions(+), 6 deletions(-)
 create mode 100644 image/lxd_test.go

diff --git a/image/lxd.go b/image/lxd.go
index ae3607c..f04b6c6 100644
--- a/image/lxd.go
+++ b/image/lxd.go
@@ -57,33 +57,44 @@ func (l *LXDImage) Build(unified bool) error {
 		return fmt.Errorf("Failed to write metadata: %s", err)
 	}
 
-	if unified {
-		var fname string
-		paths := []string{"rootfs", "templates", "metadata.yaml"}
+	paths := []string{"metadata.yaml"}
+
+	// Only include templates directory in the tarball if it's present.
+	info, err := os.Stat(filepath.Join(l.cacheDir, "templates"))
+	if err == nil && info.IsDir() {
+		paths = append(paths, "templates")
+	}
 
+	if unified {
 		ctx := pongo2.Context{
 			"image":         l.definition,
 			"creation_date": l.creationDate.Format("20060201_1504"),
 		}
 
+		var fname string
 		if l.definition.Name != "" {
+			// Use a custom name for the unified tarball.
 			fname, _ = renderTemplate(l.definition.Name, ctx)
 		} else {
+			// Default name for the unified tarball.
 			fname = "lxd"
 		}
 
+		paths = append(paths, "rootfs")
 		err = shared.Pack(fmt.Sprintf("%s.tar.xz", fname), l.cacheDir, paths...)
 		if err != nil {
 			return err
 		}
 	} else {
+		// Create rootfs as squashfs.
 		err = shared.RunCommand("mksquashfs", filepath.Join(l.cacheDir, "rootfs"),
 			"rootfs.squashfs", "-noappend")
 		if err != nil {
 			return err
 		}
 
-		err = shared.Pack("lxd.tar.xz", l.cacheDir, "templates", "metadata.yaml")
+		// Create metadata tarball.
+		err = shared.Pack("lxd.tar.xz", l.cacheDir, paths...)
 		if err != nil {
 			return err
 		}
@@ -107,9 +118,12 @@ func (l *LXDImage) createMetadata() error {
 		return err
 	}
 
-	l.Metadata.Architecture = arch
+	// Use proper architecture name from now on.
+	l.definition.Arch = arch
+
+	l.Metadata.Architecture = l.definition.Arch
 	l.Metadata.CreationDate = l.creationDate.Unix()
-	l.Metadata.Properties["architecture"] = arch
+	l.Metadata.Properties["architecture"] = l.definition.Arch
 	l.Metadata.Properties["os"] = l.definition.Distribution
 	l.Metadata.Properties["release"] = l.definition.Release
 
diff --git a/image/lxd_test.go b/image/lxd_test.go
new file mode 100644
index 0000000..48b2fad
--- /dev/null
+++ b/image/lxd_test.go
@@ -0,0 +1,179 @@
+package image
+
+import (
+	"fmt"
+	"log"
+	"os"
+	"path/filepath"
+	"reflect"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/lxc/distrobuilder/shared"
+	lxd "github.com/lxc/lxd/shared"
+)
+
+var lxdImageDef = shared.DefinitionImage{
+	Description:  "{{ image. Distribution|capfirst }} {{ image.Release }}",
+	Distribution: "ubuntu",
+	Release:      "17.10",
+	Arch:         "amd64",
+	Expiry:       "30d",
+	Name:         "{{ image.Distribution|lower }}-{{ image.Release }}-{{ image.Arch }}-{{ creation_date }}",
+}
+
+func setupLXD(t *testing.T) *LXDImage {
+	cacheDir := filepath.Join(os.TempDir(), "distrobuilder-test")
+
+	err := os.MkdirAll(filepath.Join(cacheDir, "rootfs"), 0755)
+	if err != nil {
+		t.Fatalf("Failed to create rootfs directory: %s", err)
+	}
+
+	err = os.MkdirAll(filepath.Join(cacheDir, "templates"), 0755)
+	if err != nil {
+		t.Fatalf("Failed to create templates directory: %s", err)
+	}
+
+	image := NewLXDImage(cacheDir, lxdImageDef)
+
+	// Override creation date
+	image.creationDate = time.Date(2006, 1, 2, 3, 4, 5, 0, time.UTC)
+
+	// Check cache directory
+	if image.cacheDir != cacheDir {
+		teardownLXD(t)
+		t.Fatalf("Expected cacheDir to be '%s', is '%s'", cacheDir, image.cacheDir)
+	}
+
+	if !reflect.DeepEqual(lxdImageDef, image.definition) {
+		teardownLXD(t)
+		t.Fatal("lxdImageDef and image.definition are not equal")
+	}
+
+	return image
+}
+
+func teardownLXD(t *testing.T) {
+	os.RemoveAll(filepath.Join(os.TempDir(), "distrobuilder-test"))
+}
+
+func TestLXDBuild(t *testing.T) {
+	image := setupLXD(t)
+	defer teardownLXD(t)
+
+	testLXDBuildSplitImage(t, image)
+	testLXDBuildUnifiedImage(t, image)
+}
+
+func testLXDBuildSplitImage(t *testing.T, image *LXDImage) {
+	// Create split tarball and squashfs.
+	err := image.Build(false)
+	if err != nil {
+		t.Fatalf("Unexpected error: %s", err)
+	}
+	defer func() {
+		os.Remove("lxd.tar.xz")
+		os.Remove("rootfs.squashfs")
+	}()
+
+	if !lxd.PathExists("lxd.tar.xz") {
+		t.Fatalf("File '%s' does not exist", "lxd.tar.xz")
+	}
+
+	if !lxd.PathExists("rootfs.squashfs") {
+		t.Fatalf("File '%s' does not exist", "rootfs.squashfs")
+	}
+}
+
+func testLXDBuildUnifiedImage(t *testing.T, image *LXDImage) {
+	// Create unified tarball with custom name.
+	err := image.Build(true)
+	if err != nil {
+		t.Fatalf("Unexpected error: %s", err)
+	}
+	defer os.Remove("ubuntu-17.10-x86_64-20060201_0304.tar.xz")
+
+	if !lxd.PathExists("ubuntu-17.10-x86_64-20060201_0304.tar.xz") {
+		t.Fatalf("File '%s' does not exist", "ubuntu-17.10-x86_64-20060201_0304.tar.xz")
+	}
+
+	// Create unified tarball with default name.
+	image.definition.Name = ""
+	err = image.Build(true)
+	if err != nil {
+		t.Fatalf("Unexpected error: %s", err)
+	}
+	defer os.Remove("lxd.tar.xz")
+
+	if !lxd.PathExists("lxd.tar.xz") {
+		t.Fatalf("File '%s' does not exist", "lxd.tar.xz")
+	}
+}
+
+func TestLXDCreateMetadata(t *testing.T) {
+	image := setupLXD(t)
+	defer teardownLXD(t)
+
+	err := image.createMetadata()
+	if err != nil {
+		t.Fatalf("Unexpected error: %s", err)
+	}
+
+	tests := []struct {
+		name     string
+		have     string
+		expected string
+	}{
+		{
+			"Architecture",
+			image.Metadata.Architecture,
+			"x86_64",
+		},
+		{
+			"CreationDate",
+			string(image.Metadata.CreationDate),
+			string(image.creationDate.Unix()),
+		},
+		{
+			"Properties[architecture]",
+			image.Metadata.Properties["architecture"],
+			"x86_64",
+		},
+		{
+			"Properties[os]",
+			image.Metadata.Properties["os"],
+			lxdImageDef.Distribution,
+		},
+		{
+			"Properties[release]",
+			image.Metadata.Properties["release"],
+			lxdImageDef.Release,
+		},
+		{
+			"Properties[description]",
+			image.Metadata.Properties["description"],
+			fmt.Sprintf("%s %s", strings.Title(lxdImageDef.Distribution),
+				lxdImageDef.Release),
+		},
+		{
+			"Properties[name]",
+			image.Metadata.Properties["name"],
+			fmt.Sprintf("%s-%s-%s-%s", strings.ToLower(lxdImageDef.Distribution),
+				lxdImageDef.Release, "x86_64", image.creationDate.Format("20060201_1504")),
+		},
+		{
+			"ExpiryDate",
+			fmt.Sprintf("%d", image.Metadata.ExpiryDate),
+			"1138763045",
+		},
+	}
+
+	for i, tt := range tests {
+		log.Printf("Running test #%d: %s", i, tt.name)
+		if tt.have != tt.expected {
+			t.Fatalf("Expected '%s', got '%s'", tt.expected, tt.have)
+		}
+	}
+}

From 92bb3540a70a4875bb99555c0fd05246ce35b34d Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Tue, 27 Feb 2018 18:23:05 +0100
Subject: [PATCH 2/2] image: Add LXC tests

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 image/lxc.go      |  36 +++++---
 image/lxc_test.go | 257 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 283 insertions(+), 10 deletions(-)
 create mode 100644 image/lxc_test.go

diff --git a/image/lxc.go b/image/lxc.go
index 449eba4..10b90f3 100644
--- a/image/lxc.go
+++ b/image/lxc.go
@@ -7,6 +7,7 @@ import (
 	"strings"
 	"time"
 
+	lxd "github.com/lxc/lxd/shared"
 	pongo2 "gopkg.in/flosch/pongo2.v3"
 
 	"github.com/lxc/distrobuilder/shared"
@@ -102,15 +103,24 @@ func (l *LXCImage) createMetadata() error {
 
 	var excludesUser string
 
-	filepath.Walk(filepath.Join(l.cacheDir, "rootfs", "dev"),
-		func(path string, info os.FileInfo, err error) error {
-			if info.Mode()&os.ModeDevice != 0 {
-				excludesUser += fmt.Sprintf("%s\n",
-					strings.TrimPrefix(path, filepath.Join(l.cacheDir, "rootfs")))
-			}
+	if lxd.PathExists(filepath.Join(l.cacheDir, "rootfs", "dev")) {
+		err := filepath.Walk(filepath.Join(l.cacheDir, "rootfs", "dev"),
+			func(path string, info os.FileInfo, err error) error {
+				if err != nil {
+					return err
+				}
 
-			return nil
-		})
+				if info.Mode()&os.ModeDevice != 0 {
+					excludesUser += fmt.Sprintf("%s\n",
+						strings.TrimPrefix(path, filepath.Join(l.cacheDir, "rootfs")))
+				}
+
+				return nil
+			})
+		if err != nil {
+			return fmt.Errorf("Error while walking /dev: %s", err)
+		}
+	}
 
 	err = l.writeMetadata(filepath.Join(metaDir, "excludes-user"), excludesUser)
 	if err != nil {
@@ -121,8 +131,14 @@ func (l *LXCImage) createMetadata() error {
 }
 
 func (l *LXCImage) packMetadata() error {
-	err := shared.Pack("meta.tar.xz", filepath.Join(l.cacheDir, "metadata"), "config",
-		"config-user", "create-message", "expiry", "templates", "excludes-user")
+	files := []string{"config", "config-user", "create-message", "expiry",
+		"excludes-user"}
+
+	if lxd.PathExists(filepath.Join(l.cacheDir, "metadata", "templates")) {
+		files = append(files, "templates")
+	}
+
+	err := shared.Pack("meta.tar.xz", filepath.Join(l.cacheDir, "metadata"), files...)
 	if err != nil {
 		return fmt.Errorf("Failed to create metadata: %s", err)
 	}
diff --git a/image/lxc_test.go b/image/lxc_test.go
new file mode 100644
index 0000000..803d73b
--- /dev/null
+++ b/image/lxc_test.go
@@ -0,0 +1,257 @@
+package image
+
+import (
+	"bytes"
+	"io"
+	"log"
+	"os"
+	"path/filepath"
+	"reflect"
+	"regexp"
+	"syscall"
+	"testing"
+
+	"github.com/lxc/distrobuilder/shared"
+)
+
+var lxcImageDef = shared.DefinitionImage{
+	Description:  "{{ image. Distribution|capfirst }} {{ image.Release }}",
+	Distribution: "ubuntu",
+	Release:      "17.10",
+	Arch:         "amd64",
+	Expiry:       "30d",
+	Name:         "{{ image.Distribution|lower }}-{{ image.Release }}-{{ image.Arch }}-{{ creation_date }}",
+}
+
+var lxcTarget = shared.DefinitionTargetLXC{
+	CreateMessage: "Welcome to {{ image.Distribution|capfirst}} {{ image.Release }}",
+	Config: `lxc.include = LXC_TEMPLATE_CONFIG/ubuntu.common.conf
+lxc.arch = x86_64`,
+	ConfigUser: `lxc.include = LXC_TEMPLATE_CONFIG/ubuntu.common.conf
+lxc.include = LXC_TEMPLATE_CONFIG/ubuntu.userns.conf
+lxc.arch = x86_64`,
+}
+
+func lxcCacheDir() string {
+	wd, _ := os.Getwd()
+	return filepath.Join(wd, "distrobuilder-test")
+}
+
+func setupLXC() *LXCImage {
+	return NewLXCImage(lxcCacheDir(), lxcImageDef, lxcTarget)
+}
+
+func teardownLXC() {
+	os.RemoveAll(lxcCacheDir())
+}
+
+func TestNewLXCImage(t *testing.T) {
+	image := NewLXCImage(lxcCacheDir(), lxcImageDef, lxcTarget)
+	defer teardownLXC()
+
+	if image.cacheDir != lxcCacheDir() {
+		t.Fatalf("Expected image.cacheDir to be '%s', got '%s'", lxcCacheDir(),
+			image.cacheDir)
+	}
+
+	if !reflect.DeepEqual(image.definition, lxcImageDef) {
+		t.Fatalf("lxcImageDef and image.definition are not equal")
+	}
+
+	if !reflect.DeepEqual(image.target, lxcTarget) {
+		t.Fatalf("lxcTarget and image.target are not equal")
+	}
+}
+
+func TestLXCAddTemplate(t *testing.T) {
+	image := setupLXC()
+	defer teardownLXC()
+
+	// Make sure templates file is empty.
+	info, err := os.Stat(filepath.Join(lxcCacheDir(), "metadata", "templates"))
+	if err == nil && info.Size() > 0 {
+		t.Fatalf("Expected file size to be 0, got %d", info.Size())
+	}
+
+	// Add first template entry.
+	image.AddTemplate("/path/file1")
+	file, err := os.Open(filepath.Join(lxcCacheDir(), "metadata", "templates"))
+	if err != nil {
+		t.Fatalf("Unexpected error: %s", err)
+	}
+
+	// Copy file content to buffer.
+	var buffer bytes.Buffer
+	io.Copy(&buffer, file)
+	file.Close()
+
+	if buffer.String() != "/path/file1\n" {
+		t.Fatalf("Expected templates content to be '%s', got '%s'",
+			"/path/file", buffer.String())
+	}
+
+	// Add second template entry.
+	image.AddTemplate("/path/file2")
+	file, err = os.Open(filepath.Join(lxcCacheDir(), "metadata", "templates"))
+	if err != nil {
+		t.Fatalf("Unexpected error: %s", err)
+	}
+
+	// Copy file content to buffer.
+	buffer.Reset()
+	io.Copy(&buffer, file)
+	file.Close()
+
+	if buffer.String() != "/path/file1\n/path/file2\n" {
+		t.Fatalf("Expected templates content to be '%s', got '%s'",
+			"/path/file1\n/path/file2", buffer.String())
+	}
+}
+
+func TestLXCBuild(t *testing.T) {
+	image := setupLXC()
+	defer teardownLXC()
+
+	err := os.MkdirAll(filepath.Join(lxcCacheDir(), "rootfs"), 0755)
+	if err != nil {
+		t.Fatalf("Unexpected error: %s", err)
+	}
+
+	err = image.Build()
+	if err != nil {
+		t.Fatalf("Unexpected error: %s", err)
+	}
+	defer func() {
+		os.Remove("meta.tar.xz")
+		os.Remove("rootfs.tar.xz")
+	}()
+}
+
+func TestLXCCreateMetadata(t *testing.T) {
+	defaultImage := setupLXC()
+	defer teardownLXC()
+
+	tests := []struct {
+		name          string
+		shouldFail    bool
+		expectedError string
+		prepareImage  func(LXCImage) *LXCImage
+	}{
+		{
+			"valid metadata",
+			false,
+			"",
+			func(l LXCImage) *LXCImage { return &l },
+		},
+		{
+			"invalid config template",
+			true,
+			"Error writing 'config': .+",
+			func(l LXCImage) *LXCImage {
+				l.target.Config = "{{ invalid }"
+				return &l
+			},
+		},
+		{
+			"invalid config-user template",
+			true,
+			"Error writing 'config-user': .+",
+			func(l LXCImage) *LXCImage {
+				l.target.ConfigUser = "{{ invalid }"
+				return &l
+			},
+		},
+		{
+			"invalid create-message template",
+			true,
+			"Error writing 'create-message': .+",
+			func(l LXCImage) *LXCImage {
+				l.target.CreateMessage = "{{ invalid }"
+				return &l
+			},
+		},
+		{
+			"existing dev directory",
+			false,
+			"",
+			func(l LXCImage) *LXCImage {
+				// Create /dev and device file.
+				os.MkdirAll(filepath.Join(lxcCacheDir(), "rootfs", "dev"), 0755)
+				syscall.Mknod(filepath.Join(lxcCacheDir(), "rootfs", "dev", "null"),
+					syscall.S_IFCHR, 0)
+				return &l
+			},
+		},
+	}
+
+	for i, tt := range tests {
+		log.Printf("Running test #%d: %s", i, tt.name)
+		image := tt.prepareImage(*defaultImage)
+		err := image.createMetadata()
+		if tt.shouldFail {
+			if err == nil {
+				t.Fatal("Expected to fail, but didn't")
+			}
+
+			match, _ := regexp.MatchString(tt.expectedError, err.Error())
+			if !match {
+				t.Fatalf("Expected to fail with '%s', got '%s'", tt.expectedError,
+					err.Error())
+			}
+		}
+		if !tt.shouldFail && err != nil {
+			t.Fatalf("Unexpected error: %s", err)
+		}
+	}
+}
+
+func TestLXCPackMetadata(t *testing.T) {
+	image := setupLXC()
+	defer func() {
+		teardownLXC()
+		os.Remove("meta.tar.xz")
+	}()
+
+	err := image.createMetadata()
+	if err != nil {
+		t.Fatalf("Unexpected error: %s", err)
+	}
+
+	err = image.packMetadata()
+	if err != nil {
+		t.Fatalf("Unexpected error: %s", err)
+	}
+
+	// Include templates directory.
+	image.AddTemplate("/path/file")
+	err = image.packMetadata()
+	if err != nil {
+		t.Fatalf("Unexpected error: %s", err)
+	}
+
+	// Provoke error by removing the metadata directory
+	os.RemoveAll(filepath.Join(lxcCacheDir(), "metadata"))
+	err = image.packMetadata()
+	if err == nil {
+		t.Fatal("Expected failure")
+	}
+
+}
+
+func TestLXCWriteMetadata(t *testing.T) {
+	image := setupLXC()
+	defer teardownLXC()
+
+	// Should fail due to invalid path
+	err := image.writeMetadata("/path/file", "")
+	if err == nil {
+		t.Fatal("Expected failure")
+	}
+
+	// Should succeed
+	err = image.writeMetadata("test", "metadata")
+	if err != nil {
+		t.Fatalf("Unexpected failure: %s", err)
+	}
+	os.Remove("test")
+}


More information about the lxc-devel mailing list