github.com/blixtra/nomad@v0.7.2-0.20171221000451-da9a1d7bb050/client/driver/qemu_test.go (about)

     1  package driver
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  	"strings"
     8  	"syscall"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/hashicorp/nomad/client/config"
    13  	"github.com/hashicorp/nomad/nomad/structs"
    14  	"github.com/hashicorp/nomad/testutil"
    15  
    16  	ctestutils "github.com/hashicorp/nomad/client/testutil"
    17  )
    18  
    19  // The fingerprinter test should always pass, even if QEMU is not installed.
    20  func TestQemuDriver_Fingerprint(t *testing.T) {
    21  	if !testutil.IsTravis() {
    22  		t.Parallel()
    23  	}
    24  	ctestutils.QemuCompatible(t)
    25  	task := &structs.Task{
    26  		Name:      "foo",
    27  		Driver:    "qemu",
    28  		Resources: structs.DefaultResources(),
    29  	}
    30  	ctx := testDriverContexts(t, task)
    31  	defer ctx.AllocDir.Destroy()
    32  	d := NewQemuDriver(ctx.DriverCtx)
    33  
    34  	node := &structs.Node{
    35  		Attributes: make(map[string]string),
    36  	}
    37  	apply, err := d.Fingerprint(&config.Config{}, node)
    38  	if err != nil {
    39  		t.Fatalf("err: %v", err)
    40  	}
    41  	if !apply {
    42  		t.Fatalf("should apply")
    43  	}
    44  	if node.Attributes[qemuDriverAttr] == "" {
    45  		t.Fatalf("Missing Qemu driver")
    46  	}
    47  	if node.Attributes[qemuDriverVersionAttr] == "" {
    48  		t.Fatalf("Missing Qemu driver version")
    49  	}
    50  }
    51  
    52  func TestQemuDriver_StartOpen_Wait(t *testing.T) {
    53  	logger := testLogger()
    54  	if !testutil.IsTravis() {
    55  		t.Parallel()
    56  	}
    57  	ctestutils.QemuCompatible(t)
    58  	task := &structs.Task{
    59  		Name:   "linux",
    60  		Driver: "qemu",
    61  		Config: map[string]interface{}{
    62  			"image_path":        "linux-0.2.img",
    63  			"accelerator":       "tcg",
    64  			"graceful_shutdown": false,
    65  			"port_map": []map[string]int{{
    66  				"main": 22,
    67  				"web":  8080,
    68  			}},
    69  			"args": []string{"-nodefconfig", "-nodefaults"},
    70  		},
    71  		LogConfig: &structs.LogConfig{
    72  			MaxFiles:      10,
    73  			MaxFileSizeMB: 10,
    74  		},
    75  		Resources: &structs.Resources{
    76  			CPU:      500,
    77  			MemoryMB: 512,
    78  			Networks: []*structs.NetworkResource{
    79  				{
    80  					ReservedPorts: []structs.Port{{Label: "main", Value: 22000}, {Label: "web", Value: 80}},
    81  				},
    82  			},
    83  		},
    84  	}
    85  
    86  	ctx := testDriverContexts(t, task)
    87  	defer ctx.AllocDir.Destroy()
    88  	d := NewQemuDriver(ctx.DriverCtx)
    89  
    90  	// Copy the test image into the task's directory
    91  	dst := ctx.ExecCtx.TaskDir.Dir
    92  
    93  	copyFile("./test-resources/qemu/linux-0.2.img", filepath.Join(dst, "linux-0.2.img"), t)
    94  
    95  	if _, err := d.Prestart(ctx.ExecCtx, task); err != nil {
    96  		t.Fatalf("Prestart failed: %v", err)
    97  	}
    98  
    99  	resp, err := d.Start(ctx.ExecCtx, task)
   100  	if err != nil {
   101  		t.Fatalf("err: %v", err)
   102  	}
   103  
   104  	// Ensure that sending a Signal returns an error
   105  	if err := resp.Handle.Signal(syscall.SIGINT); err == nil {
   106  		t.Fatalf("Expect an error when signalling")
   107  	}
   108  
   109  	// Attempt to open
   110  	handle2, err := d.Open(ctx.ExecCtx, resp.Handle.ID())
   111  	if err != nil {
   112  		t.Fatalf("err: %v", err)
   113  	}
   114  	if handle2 == nil {
   115  		t.Fatalf("missing handle")
   116  	}
   117  
   118  	// Clean up
   119  	if err := resp.Handle.Kill(); err != nil {
   120  		logger.Printf("Error killing Qemu test: %s", err)
   121  	}
   122  }
   123  
   124  func TestQemuDriver_GracefulShutdown(t *testing.T) {
   125  	logger := testLogger()
   126  	if !testutil.IsTravis() {
   127  		t.Parallel()
   128  	}
   129  	ctestutils.QemuCompatible(t)
   130  	ctestutils.RequireRoot(t)
   131  	task := &structs.Task{
   132  		Name:   "linux",
   133  		Driver: "qemu",
   134  		Config: map[string]interface{}{
   135  			"image_path":        "linux-0.2.img",
   136  			"accelerator":       "tcg",
   137  			"graceful_shutdown": true,
   138  			"port_map": []map[string]int{{
   139  				"main": 22,
   140  				"web":  8080,
   141  			}},
   142  			"args": []string{"-nodefconfig", "-nodefaults"},
   143  		},
   144  		// With the use of tcg acceleration, it's very unlikely a qemu instance
   145  		// will boot (and gracefully halt) in a reasonable amount of time, so
   146  		// this timeout is kept low to reduce test execution time.
   147  		KillTimeout: time.Duration(1 * time.Second),
   148  		LogConfig: &structs.LogConfig{
   149  			MaxFiles:      10,
   150  			MaxFileSizeMB: 10,
   151  		},
   152  		Resources: &structs.Resources{
   153  			CPU:      500,
   154  			MemoryMB: 512,
   155  			Networks: []*structs.NetworkResource{
   156  				{
   157  					ReservedPorts: []structs.Port{{Label: "main", Value: 22000}, {Label: "web", Value: 80}},
   158  				},
   159  			},
   160  		},
   161  	}
   162  
   163  	ctx := testDriverContexts(t, task)
   164  	defer ctx.AllocDir.Destroy()
   165  	d := NewQemuDriver(ctx.DriverCtx)
   166  
   167  	apply, err := d.Fingerprint(&config.Config{}, ctx.DriverCtx.node)
   168  	if err != nil {
   169  		t.Fatalf("err: %v", err)
   170  	}
   171  	if !apply {
   172  		t.Fatalf("should apply")
   173  	}
   174  
   175  	dst := ctx.ExecCtx.TaskDir.Dir
   176  
   177  	copyFile("./test-resources/qemu/linux-0.2.img", filepath.Join(dst, "linux-0.2.img"), t)
   178  
   179  	if _, err := d.Prestart(ctx.ExecCtx, task); err != nil {
   180  		t.Fatalf("Prestart failed: %v", err)
   181  	}
   182  
   183  	resp, err := d.Start(ctx.ExecCtx, task)
   184  	if err != nil {
   185  		t.Fatalf("err: %v", err)
   186  	}
   187  
   188  	// Clean up
   189  	defer func() {
   190  		if err := resp.Handle.Kill(); err != nil {
   191  			logger.Printf("Error killing Qemu test: %s", err)
   192  		}
   193  	}()
   194  
   195  	// The monitor socket will not exist immediately, so we'll wait up to
   196  	// 5 seconds for it to become available.
   197  	monitorPath := fmt.Sprintf("%s/linux/%s", ctx.AllocDir.AllocDir, qemuMonitorSocketName)
   198  	monitorPathExists := false
   199  	for i := 0; i < 100; i++ {
   200  		if _, err := os.Stat(monitorPath); !os.IsNotExist(err) {
   201  			logger.Printf("monitor socket exists at %q\n", monitorPath)
   202  			monitorPathExists = true
   203  			break
   204  		}
   205  		time.Sleep(200 * time.Millisecond)
   206  	}
   207  	if monitorPathExists == false {
   208  		t.Fatalf("monitor socket did not exist after waiting 20 seconds")
   209  	}
   210  
   211  	// userPid supplied in sendQemuShutdown calls is bogus (it's used only
   212  	// for log output)
   213  	if err := sendQemuShutdown(ctx.DriverCtx.logger, "", 0); err == nil {
   214  		t.Fatalf("sendQemuShutdown should return an error if monitorPath parameter is empty")
   215  	}
   216  
   217  	if err := sendQemuShutdown(ctx.DriverCtx.logger, "/path/that/does/not/exist", 0); err == nil {
   218  		t.Fatalf("sendQemuShutdown should return an error if file does not exist at monitorPath")
   219  	}
   220  
   221  	if err := sendQemuShutdown(ctx.DriverCtx.logger, monitorPath, 0); err != nil {
   222  		t.Fatalf("unexpected error from sendQemuShutdown: %s", err)
   223  	}
   224  }
   225  
   226  func TestQemuDriverUser(t *testing.T) {
   227  	if !testutil.IsTravis() {
   228  		t.Parallel()
   229  	}
   230  	ctestutils.QemuCompatible(t)
   231  	tasks := []*structs.Task{
   232  		{
   233  			Name:   "linux",
   234  			Driver: "qemu",
   235  			User:   "alice",
   236  			Config: map[string]interface{}{
   237  				"image_path":        "linux-0.2.img",
   238  				"accelerator":       "tcg",
   239  				"graceful_shutdown": false,
   240  				"port_map": []map[string]int{{
   241  					"main": 22,
   242  					"web":  8080,
   243  				}},
   244  				"args": []string{"-nodefconfig", "-nodefaults"},
   245  				"msg":  "unknown user alice",
   246  			},
   247  			LogConfig: &structs.LogConfig{
   248  				MaxFiles:      10,
   249  				MaxFileSizeMB: 10,
   250  			},
   251  			Resources: &structs.Resources{
   252  				CPU:      500,
   253  				MemoryMB: 512,
   254  				Networks: []*structs.NetworkResource{
   255  					{
   256  						ReservedPorts: []structs.Port{{Label: "main", Value: 22000}, {Label: "web", Value: 80}},
   257  					},
   258  				},
   259  			},
   260  		},
   261  		{
   262  			Name:   "linux",
   263  			Driver: "qemu",
   264  			User:   "alice",
   265  			Config: map[string]interface{}{
   266  				"image_path":  "linux-0.2.img",
   267  				"accelerator": "tcg",
   268  				"port_map": []map[string]int{{
   269  					"main": 22,
   270  					"web":  8080,
   271  				}},
   272  				"args": []string{"-nodefconfig", "-nodefaults"},
   273  				"msg":  "Qemu memory assignment out of bounds",
   274  			},
   275  			LogConfig: &structs.LogConfig{
   276  				MaxFiles:      10,
   277  				MaxFileSizeMB: 10,
   278  			},
   279  			Resources: &structs.Resources{
   280  				CPU:      500,
   281  				MemoryMB: -1,
   282  				Networks: []*structs.NetworkResource{
   283  					{
   284  						ReservedPorts: []structs.Port{{Label: "main", Value: 22000}, {Label: "web", Value: 80}},
   285  					},
   286  				},
   287  			},
   288  		},
   289  	}
   290  
   291  	for _, task := range tasks {
   292  		ctx := testDriverContexts(t, task)
   293  		defer ctx.AllocDir.Destroy()
   294  		d := NewQemuDriver(ctx.DriverCtx)
   295  
   296  		if _, err := d.Prestart(ctx.ExecCtx, task); err != nil {
   297  			t.Fatalf("Prestart faild: %v", err)
   298  		}
   299  
   300  		resp, err := d.Start(ctx.ExecCtx, task)
   301  		if err == nil {
   302  			resp.Handle.Kill()
   303  			t.Fatalf("Should've failed")
   304  		}
   305  
   306  		msg := task.Config["msg"].(string)
   307  		if !strings.Contains(err.Error(), msg) {
   308  			t.Fatalf("Expecting '%v' in '%v'", msg, err)
   309  		}
   310  	}
   311  }
   312  
   313  func TestQemuDriverGetMonitorPathOldQemu(t *testing.T) {
   314  	task := &structs.Task{
   315  		Name:   "linux",
   316  		Driver: "qemu",
   317  		Config: map[string]interface{}{
   318  			"image_path":        "linux-0.2.img",
   319  			"accelerator":       "tcg",
   320  			"graceful_shutdown": true,
   321  			"port_map": []map[string]int{{
   322  				"main": 22,
   323  				"web":  8080,
   324  			}},
   325  			"args": []string{"-nodefconfig", "-nodefaults"},
   326  		},
   327  		KillTimeout: time.Duration(1 * time.Second),
   328  		LogConfig: &structs.LogConfig{
   329  			MaxFiles:      10,
   330  			MaxFileSizeMB: 10,
   331  		},
   332  		Resources: &structs.Resources{
   333  			CPU:      500,
   334  			MemoryMB: 512,
   335  			Networks: []*structs.NetworkResource{
   336  				{
   337  					ReservedPorts: []structs.Port{{Label: "main", Value: 22000}, {Label: "web", Value: 80}},
   338  				},
   339  			},
   340  		},
   341  	}
   342  
   343  	ctx := testDriverContexts(t, task)
   344  	defer ctx.AllocDir.Destroy()
   345  
   346  	// Simulate an older version of qemu which does not support long monitor socket paths
   347  	ctx.DriverCtx.node.Attributes[qemuDriverVersionAttr] = "2.0.0"
   348  
   349  	d := &QemuDriver{DriverContext: *ctx.DriverCtx}
   350  
   351  	shortPath := strings.Repeat("x", 10)
   352  	_, err := d.getMonitorPath(shortPath)
   353  	if err != nil {
   354  		t.Fatal("Should not have returned an error")
   355  	}
   356  
   357  	longPath := strings.Repeat("x", qemuLegacyMaxMonitorPathLen+100)
   358  	_, err = d.getMonitorPath(longPath)
   359  	if err == nil {
   360  		t.Fatal("Should have returned an error")
   361  	}
   362  
   363  	// Max length includes the '/' separator and socket name
   364  	maxLengthCount := qemuLegacyMaxMonitorPathLen - len(qemuMonitorSocketName) - 1
   365  	maxLengthLegacyPath := strings.Repeat("x", maxLengthCount)
   366  	_, err = d.getMonitorPath(maxLengthLegacyPath)
   367  	if err != nil {
   368  		t.Fatalf("Should not have returned an error: %s", err)
   369  	}
   370  }
   371  
   372  func TestQemuDriverGetMonitorPathNewQemu(t *testing.T) {
   373  	task := &structs.Task{
   374  		Name:   "linux",
   375  		Driver: "qemu",
   376  		Config: map[string]interface{}{
   377  			"image_path":        "linux-0.2.img",
   378  			"accelerator":       "tcg",
   379  			"graceful_shutdown": true,
   380  			"port_map": []map[string]int{{
   381  				"main": 22,
   382  				"web":  8080,
   383  			}},
   384  			"args": []string{"-nodefconfig", "-nodefaults"},
   385  		},
   386  		KillTimeout: time.Duration(1 * time.Second),
   387  		LogConfig: &structs.LogConfig{
   388  			MaxFiles:      10,
   389  			MaxFileSizeMB: 10,
   390  		},
   391  		Resources: &structs.Resources{
   392  			CPU:      500,
   393  			MemoryMB: 512,
   394  			Networks: []*structs.NetworkResource{
   395  				{
   396  					ReservedPorts: []structs.Port{{Label: "main", Value: 22000}, {Label: "web", Value: 80}},
   397  				},
   398  			},
   399  		},
   400  	}
   401  
   402  	ctx := testDriverContexts(t, task)
   403  	defer ctx.AllocDir.Destroy()
   404  
   405  	// Simulate a version of qemu which supports long monitor socket paths
   406  	ctx.DriverCtx.node.Attributes[qemuDriverVersionAttr] = "2.99.99"
   407  
   408  	d := &QemuDriver{DriverContext: *ctx.DriverCtx}
   409  
   410  	shortPath := strings.Repeat("x", 10)
   411  	_, err := d.getMonitorPath(shortPath)
   412  	if err != nil {
   413  		t.Fatal("Should not have returned an error")
   414  	}
   415  
   416  	longPath := strings.Repeat("x", qemuLegacyMaxMonitorPathLen+100)
   417  	_, err = d.getMonitorPath(longPath)
   418  	if err != nil {
   419  		t.Fatal("Should not have returned an error")
   420  	}
   421  
   422  	maxLengthCount := qemuLegacyMaxMonitorPathLen - len(qemuMonitorSocketName) - 1
   423  	maxLengthLegacyPath := strings.Repeat("x", maxLengthCount)
   424  	_, err = d.getMonitorPath(maxLengthLegacyPath)
   425  	if err != nil {
   426  		t.Fatal("Should not have returned an error")
   427  	}
   428  }