Merge pull request #21 from monstermunchkin/issues/19

Add missing tests for lxc/lxd image generation
This commit is contained in:
Christian Brauner 2018-02-28 11:23:38 +01:00 committed by GitHub
commit 4faff0b0df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 486 additions and 16 deletions

View File

@ -6,6 +6,10 @@ os:
go:
- 1.9
before_install:
- "sudo apt-get -qq update"
- "sudo apt-get install -y squashfs-tools"
install:
- "mkdir -p $GOPATH/github.com/lxc"
- "rsync -az ${TRAVIS_BUILD_DIR}/ $HOME/gopath/src/github.com/lxc/distrobuilder/"

View File

@ -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)
}

257
image/lxc_test.go Normal file
View File

@ -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")
}

View File

@ -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

179
image/lxd_test.go Normal file
View File

@ -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)
}
}
}