github.com/anuvu/nomad@v0.8.7-atom1/client/driver/lxc_test.go (about)

     1  //+build linux,lxc
     2  
     3  package driver
     4  
     5  import (
     6  	"bytes"
     7  	"fmt"
     8  	"io/ioutil"
     9  	"os"
    10  	"os/exec"
    11  	"path/filepath"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/hashicorp/nomad/client/config"
    16  	cstructs "github.com/hashicorp/nomad/client/structs"
    17  	ctestutil "github.com/hashicorp/nomad/client/testutil"
    18  	"github.com/hashicorp/nomad/nomad/structs"
    19  	"github.com/hashicorp/nomad/testutil"
    20  	lxc "gopkg.in/lxc/go-lxc.v2"
    21  )
    22  
    23  func TestLxcDriver_Fingerprint(t *testing.T) {
    24  	t.Parallel()
    25  	if !lxcPresent(t) {
    26  		t.Skip("lxc not present")
    27  	}
    28  
    29  	task := &structs.Task{
    30  		Name:      "foo",
    31  		Driver:    "lxc",
    32  		Resources: structs.DefaultResources(),
    33  	}
    34  
    35  	ctx := testDriverContexts(t, task)
    36  	defer ctx.AllocDir.Destroy()
    37  	d := NewLxcDriver(ctx.DriverCtx)
    38  
    39  	node := &structs.Node{
    40  		Attributes: map[string]string{},
    41  	}
    42  
    43  	// test with an empty config
    44  	{
    45  		request := &cstructs.FingerprintRequest{Config: &config.Config{}, Node: node}
    46  		var response cstructs.FingerprintResponse
    47  		err := d.Fingerprint(request, &response)
    48  		if err != nil {
    49  			t.Fatalf("err: %v", err)
    50  		}
    51  	}
    52  
    53  	// test when lxc is enable din the config
    54  	{
    55  		conf := &config.Config{Options: map[string]string{lxcConfigOption: "1"}}
    56  		request := &cstructs.FingerprintRequest{Config: conf, Node: node}
    57  		var response cstructs.FingerprintResponse
    58  		err := d.Fingerprint(request, &response)
    59  		if err != nil {
    60  			t.Fatalf("err: %v", err)
    61  		}
    62  
    63  		if !response.Detected {
    64  			t.Fatalf("expected response to be applicable")
    65  		}
    66  
    67  		if response.Attributes["driver.lxc"] == "" {
    68  			t.Fatalf("missing driver")
    69  		}
    70  	}
    71  }
    72  
    73  func TestLxcDriver_Start_Wait(t *testing.T) {
    74  	if !testutil.IsTravis() {
    75  		t.Parallel()
    76  	}
    77  	if !lxcPresent(t) {
    78  		t.Skip("lxc not present")
    79  	}
    80  	ctestutil.RequireRoot(t)
    81  
    82  	task := &structs.Task{
    83  		Name:   "foo",
    84  		Driver: "lxc",
    85  		Config: map[string]interface{}{
    86  			"template": "/usr/share/lxc/templates/lxc-busybox",
    87  			"volumes":  []string{"/tmp/:mnt/tmp"},
    88  		},
    89  		KillTimeout: 10 * time.Second,
    90  		Resources:   structs.DefaultResources(),
    91  	}
    92  
    93  	testFileContents := []byte("this should be visible under /mnt/tmp")
    94  	tmpFile, err := ioutil.TempFile("/tmp", "testlxcdriver_start_wait")
    95  	if err != nil {
    96  		t.Fatalf("error writing temp file: %v", err)
    97  	}
    98  	defer os.Remove(tmpFile.Name())
    99  	if _, err := tmpFile.Write(testFileContents); err != nil {
   100  		t.Fatalf("error writing temp file: %v", err)
   101  	}
   102  	if err := tmpFile.Close(); err != nil {
   103  		t.Fatalf("error closing temp file: %v", err)
   104  	}
   105  
   106  	ctx := testDriverContexts(t, task)
   107  	defer ctx.AllocDir.Destroy()
   108  	d := NewLxcDriver(ctx.DriverCtx)
   109  
   110  	if _, err := d.Prestart(ctx.ExecCtx, task); err != nil {
   111  		t.Fatalf("prestart err: %v", err)
   112  	}
   113  	sresp, err := d.Start(ctx.ExecCtx, task)
   114  	if err != nil {
   115  		t.Fatalf("err: %v", err)
   116  	}
   117  
   118  	lxcHandle, _ := sresp.Handle.(*lxcDriverHandle)
   119  
   120  	// Destroy the container after the test
   121  	defer func() {
   122  		lxcHandle.container.Stop()
   123  		lxcHandle.container.Destroy()
   124  	}()
   125  
   126  	testutil.WaitForResult(func() (bool, error) {
   127  		state := lxcHandle.container.State()
   128  		if state == lxc.RUNNING {
   129  			return true, nil
   130  		}
   131  		return false, fmt.Errorf("container in state: %v", state)
   132  	}, func(err error) {
   133  		t.Fatalf("err: %v", err)
   134  	})
   135  
   136  	// Look for mounted directories in their proper location
   137  	containerName := fmt.Sprintf("%s-%s", task.Name, ctx.DriverCtx.allocID)
   138  	for _, mnt := range []string{"alloc", "local", "secrets", "mnt/tmp"} {
   139  		fullpath := filepath.Join(lxcHandle.lxcPath, containerName, "rootfs", mnt)
   140  		stat, err := os.Stat(fullpath)
   141  		if err != nil {
   142  			t.Fatalf("err %v", err)
   143  		}
   144  		if !stat.IsDir() {
   145  			t.Fatalf("expected %q to be a dir", fullpath)
   146  		}
   147  	}
   148  
   149  	// Test that /mnt/tmp/$tempFile exists in the container:
   150  	mountedContents, err := exec.Command("lxc-attach", "-n", containerName, "--", "cat", filepath.Join("/mnt/", tmpFile.Name())).Output()
   151  	if err != nil {
   152  		t.Fatalf("err reading temp file in bind mount: %v", err)
   153  	}
   154  
   155  	if !bytes.Equal(mountedContents, testFileContents) {
   156  		t.Fatalf("contents of temp bind mounted file did not match, was '%s'", mountedContents)
   157  	}
   158  
   159  	// Destroy the container
   160  	if err := sresp.Handle.Kill(); err != nil {
   161  		t.Fatalf("err: %v", err)
   162  	}
   163  
   164  	select {
   165  	case res := <-sresp.Handle.WaitCh():
   166  		if !res.Successful() {
   167  			t.Fatalf("err: %v", res)
   168  		}
   169  	case <-time.After(time.Duration(testutil.TestMultiplier()*5) * time.Second):
   170  		t.Fatalf("timeout")
   171  	}
   172  }
   173  
   174  func TestLxcDriver_Open_Wait(t *testing.T) {
   175  	if !testutil.IsTravis() {
   176  		t.Parallel()
   177  	}
   178  	if !lxcPresent(t) {
   179  		t.Skip("lxc not present")
   180  	}
   181  	ctestutil.RequireRoot(t)
   182  
   183  	task := &structs.Task{
   184  		Name:   "foo",
   185  		Driver: "lxc",
   186  		Config: map[string]interface{}{
   187  			"template": "/usr/share/lxc/templates/lxc-busybox",
   188  		},
   189  		KillTimeout: 10 * time.Second,
   190  		Resources:   structs.DefaultResources(),
   191  	}
   192  
   193  	ctx := testDriverContexts(t, task)
   194  	defer ctx.AllocDir.Destroy()
   195  	d := NewLxcDriver(ctx.DriverCtx)
   196  
   197  	if _, err := d.Prestart(ctx.ExecCtx, task); err != nil {
   198  		t.Fatalf("prestart err: %v", err)
   199  	}
   200  	sresp, err := d.Start(ctx.ExecCtx, task)
   201  	if err != nil {
   202  		t.Fatalf("err: %v", err)
   203  	}
   204  
   205  	// Destroy the container after the test
   206  	lh := sresp.Handle.(*lxcDriverHandle)
   207  	defer func() {
   208  		lh.container.Stop()
   209  		lh.container.Destroy()
   210  	}()
   211  
   212  	handle2, err := d.Open(ctx.ExecCtx, lh.ID())
   213  	if err != nil {
   214  		t.Fatalf("err: %v", err)
   215  	}
   216  
   217  	if handle2 == nil {
   218  		t.Fatalf("missing handle on open")
   219  	}
   220  
   221  	lxcHandle, _ := handle2.(*lxcDriverHandle)
   222  
   223  	testutil.WaitForResult(func() (bool, error) {
   224  		state := lxcHandle.container.State()
   225  		if state == lxc.RUNNING {
   226  			return true, nil
   227  		}
   228  		return false, fmt.Errorf("container in state: %v", state)
   229  	}, func(err error) {
   230  		t.Fatalf("err: %v", err)
   231  	})
   232  
   233  	// Destroy the container
   234  	if err := handle2.Kill(); err != nil {
   235  		t.Fatalf("err: %v", err)
   236  	}
   237  }
   238  
   239  func lxcPresent(t *testing.T) bool {
   240  	return lxc.Version() != ""
   241  }
   242  
   243  func TestLxcDriver_Volumes_ConfigValidation(t *testing.T) {
   244  	if !testutil.IsTravis() {
   245  		t.Parallel()
   246  	}
   247  	if !lxcPresent(t) {
   248  		t.Skip("lxc not present")
   249  	}
   250  	ctestutil.RequireRoot(t)
   251  
   252  	brokenVolumeConfigs := [][]string{
   253  		{
   254  			"foo:/var",
   255  		},
   256  		{
   257  			":",
   258  		},
   259  		{
   260  			"abc:",
   261  		},
   262  		{
   263  			":def",
   264  		},
   265  		{
   266  			"abc:def:ghi",
   267  		},
   268  	}
   269  
   270  	for _, bc := range brokenVolumeConfigs {
   271  		if err := testVolumeConfig(t, bc); err == nil {
   272  			t.Fatalf("error expected in validate for config %+v", bc)
   273  		}
   274  	}
   275  	if err := testVolumeConfig(t, []string{"abc:def"}); err != nil {
   276  		t.Fatalf("error in validate for syntactically valid config abc:def was %v", err)
   277  	}
   278  }
   279  
   280  func testVolumeConfig(t *testing.T, volConfig []string) error {
   281  	task := &structs.Task{
   282  		Name:        "voltest",
   283  		Driver:      "lxc",
   284  		KillTimeout: 10 * time.Second,
   285  		Resources:   structs.DefaultResources(),
   286  		Config: map[string]interface{}{
   287  			"template": "busybox",
   288  		},
   289  	}
   290  	task.Config["volumes"] = volConfig
   291  
   292  	ctx := testDriverContexts(t, task)
   293  	defer ctx.AllocDir.Destroy()
   294  
   295  	driver := NewLxcDriver(ctx.DriverCtx)
   296  
   297  	err := driver.Validate(task.Config)
   298  	return err
   299  
   300  }
   301  
   302  func TestLxcDriver_Start_NoVolumes(t *testing.T) {
   303  	if !testutil.IsTravis() {
   304  		t.Parallel()
   305  	}
   306  	if !lxcPresent(t) {
   307  		t.Skip("lxc not present")
   308  	}
   309  	ctestutil.RequireRoot(t)
   310  
   311  	task := &structs.Task{
   312  		Name:   "foo",
   313  		Driver: "lxc",
   314  		Config: map[string]interface{}{
   315  			"template": "/usr/share/lxc/templates/lxc-busybox",
   316  			"volumes":  []string{"/tmp/:mnt/tmp"},
   317  		},
   318  		KillTimeout: 10 * time.Second,
   319  		Resources:   structs.DefaultResources(),
   320  	}
   321  
   322  	ctx := testDriverContexts(t, task)
   323  	defer ctx.AllocDir.Destroy()
   324  
   325  	// set lxcVolumesConfigOption to false to disallow absolute paths as the source for the bind mount
   326  	ctx.DriverCtx.config.Options = map[string]string{lxcVolumesConfigOption: "false"}
   327  
   328  	d := NewLxcDriver(ctx.DriverCtx)
   329  
   330  	if _, err := d.Prestart(ctx.ExecCtx, task); err != nil {
   331  		t.Fatalf("prestart err: %v", err)
   332  	}
   333  
   334  	// expect the "absolute bind-mount volume in config.. " error
   335  	_, err := d.Start(ctx.ExecCtx, task)
   336  	if err == nil {
   337  		t.Fatalf("expected error in start, got nil.")
   338  	}
   339  
   340  	// Because the container was created but not started before
   341  	// the expected error, we can test that the destroy-only
   342  	// cleanup is done here.
   343  	containerName := fmt.Sprintf("%s-%s", task.Name, ctx.DriverCtx.allocID)
   344  	if err := exec.Command("bash", "-c", fmt.Sprintf("lxc-ls -1 | grep -q %s", containerName)).Run(); err == nil {
   345  		t.Fatalf("error, container '%s' is still around", containerName)
   346  	}
   347  
   348  }