github.com/zhizhiboom/nomad@v0.8.5-0.20180907175415-f28fd3a1a056/client/driver/qemu_test.go (about)

     1  package driver
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"net"
     7  	"path/filepath"
     8  	"strconv"
     9  	"strings"
    10  	"syscall"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/hashicorp/consul/lib/freeport"
    15  	"github.com/hashicorp/nomad/client/config"
    16  	cstructs "github.com/hashicorp/nomad/client/structs"
    17  	"github.com/hashicorp/nomad/helper/testlog"
    18  	"github.com/hashicorp/nomad/nomad/structs"
    19  	"github.com/hashicorp/nomad/testutil"
    20  
    21  	ctestutils "github.com/hashicorp/nomad/client/testutil"
    22  )
    23  
    24  // The fingerprinter test should always pass, even if QEMU is not installed.
    25  func TestQemuDriver_Fingerprint(t *testing.T) {
    26  	if !testutil.IsTravis() {
    27  		t.Parallel()
    28  	}
    29  	ctestutils.QemuCompatible(t)
    30  	task := &structs.Task{
    31  		Name:      "foo",
    32  		Driver:    "qemu",
    33  		Resources: structs.DefaultResources(),
    34  	}
    35  	ctx := testDriverContexts(t, task)
    36  	defer ctx.AllocDir.Destroy()
    37  	d := NewQemuDriver(ctx.DriverCtx)
    38  
    39  	node := &structs.Node{
    40  		Attributes: make(map[string]string),
    41  	}
    42  
    43  	request := &cstructs.FingerprintRequest{Config: &config.Config{}, Node: node}
    44  	var response cstructs.FingerprintResponse
    45  	err := d.Fingerprint(request, &response)
    46  	if err != nil {
    47  		t.Fatalf("err: %v", err)
    48  	}
    49  
    50  	if !response.Detected {
    51  		t.Fatalf("expected response to be applicable")
    52  	}
    53  
    54  	attributes := response.Attributes
    55  	if attributes == nil {
    56  		t.Fatalf("attributes should not be nil")
    57  	}
    58  
    59  	if attributes[qemuDriverAttr] == "" {
    60  		t.Fatalf("Missing Qemu driver")
    61  	}
    62  
    63  	if attributes[qemuDriverVersionAttr] == "" {
    64  		t.Fatalf("Missing Qemu driver version")
    65  	}
    66  }
    67  
    68  func TestQemuDriver_StartOpen_Wait(t *testing.T) {
    69  	logger := testlog.Logger(t)
    70  	if !testutil.IsTravis() {
    71  		t.Parallel()
    72  	}
    73  	ctestutils.QemuCompatible(t)
    74  	task := &structs.Task{
    75  		Name:   "linux",
    76  		Driver: "qemu",
    77  		Config: map[string]interface{}{
    78  			"image_path":        "linux-0.2.img",
    79  			"accelerator":       "tcg",
    80  			"graceful_shutdown": false,
    81  			"port_map": []map[string]int{{
    82  				"main": 22,
    83  				"web":  8080,
    84  			}},
    85  			"args": []string{"-nodefconfig", "-nodefaults"},
    86  		},
    87  		LogConfig: &structs.LogConfig{
    88  			MaxFiles:      10,
    89  			MaxFileSizeMB: 10,
    90  		},
    91  		Resources: &structs.Resources{
    92  			CPU:      500,
    93  			MemoryMB: 512,
    94  			Networks: []*structs.NetworkResource{
    95  				{
    96  					ReservedPorts: []structs.Port{{Label: "main", Value: 22000}, {Label: "web", Value: 80}},
    97  				},
    98  			},
    99  		},
   100  	}
   101  
   102  	ctx := testDriverContexts(t, task)
   103  	defer ctx.AllocDir.Destroy()
   104  	d := NewQemuDriver(ctx.DriverCtx)
   105  
   106  	// Copy the test image into the task's directory
   107  	dst := ctx.ExecCtx.TaskDir.Dir
   108  
   109  	copyFile("./test-resources/qemu/linux-0.2.img", filepath.Join(dst, "linux-0.2.img"), t)
   110  
   111  	if _, err := d.Prestart(ctx.ExecCtx, task); err != nil {
   112  		t.Fatalf("Prestart failed: %v", err)
   113  	}
   114  
   115  	resp, err := d.Start(ctx.ExecCtx, task)
   116  	if err != nil {
   117  		t.Fatalf("err: %v", err)
   118  	}
   119  
   120  	// Ensure that sending a Signal returns an error
   121  	if err := resp.Handle.Signal(syscall.SIGINT); err == nil {
   122  		t.Fatalf("Expect an error when signalling")
   123  	}
   124  
   125  	// Attempt to open
   126  	handle2, err := d.Open(ctx.ExecCtx, resp.Handle.ID())
   127  	if err != nil {
   128  		t.Fatalf("err: %v", err)
   129  	}
   130  	if handle2 == nil {
   131  		t.Fatalf("missing handle")
   132  	}
   133  
   134  	// Clean up
   135  	if err := resp.Handle.Kill(); err != nil {
   136  		logger.Printf("Error killing Qemu test: %s", err)
   137  	}
   138  }
   139  
   140  func TestQemuDriver_GracefulShutdown(t *testing.T) {
   141  	testutil.SkipSlow(t)
   142  	if !testutil.IsTravis() {
   143  		t.Parallel()
   144  	}
   145  	ctestutils.QemuCompatible(t)
   146  
   147  	logger := testlog.Logger(t)
   148  
   149  	// Graceful shutdown may be really slow unfortunately
   150  	killTimeout := 3 * time.Minute
   151  
   152  	// Grab a free port so we can tell when the image has started
   153  	port := freeport.GetT(t, 1)[0]
   154  
   155  	task := &structs.Task{
   156  		Name:   "alpine-shutdown-test",
   157  		Driver: "qemu",
   158  		Config: map[string]interface{}{
   159  			"image_path":        "alpine.qcow2",
   160  			"graceful_shutdown": true,
   161  			"args":              []string{"-nodefconfig", "-nodefaults"},
   162  			"port_map": []map[string]int{{
   163  				"ssh": 22,
   164  			}},
   165  		},
   166  		LogConfig: &structs.LogConfig{
   167  			MaxFiles:      10,
   168  			MaxFileSizeMB: 10,
   169  		},
   170  		Resources: &structs.Resources{
   171  			CPU:      1000,
   172  			MemoryMB: 256,
   173  			Networks: []*structs.NetworkResource{
   174  				{
   175  					ReservedPorts: []structs.Port{{Label: "ssh", Value: port}},
   176  				},
   177  			},
   178  		},
   179  		KillTimeout: killTimeout,
   180  	}
   181  
   182  	ctx := testDriverContexts(t, task)
   183  	ctx.DriverCtx.config.MaxKillTimeout = killTimeout
   184  	defer ctx.AllocDir.Destroy()
   185  	d := NewQemuDriver(ctx.DriverCtx)
   186  
   187  	request := &cstructs.FingerprintRequest{Config: &config.Config{}, Node: ctx.DriverCtx.node}
   188  	var response cstructs.FingerprintResponse
   189  	err := d.Fingerprint(request, &response)
   190  	if err != nil {
   191  		t.Fatalf("err: %v", err)
   192  	}
   193  
   194  	for name, value := range response.Attributes {
   195  		ctx.DriverCtx.node.Attributes[name] = value
   196  	}
   197  
   198  	dst := ctx.ExecCtx.TaskDir.Dir
   199  
   200  	copyFile("./test-resources/qemu/alpine.qcow2", filepath.Join(dst, "alpine.qcow2"), t)
   201  
   202  	if _, err := d.Prestart(ctx.ExecCtx, task); err != nil {
   203  		t.Fatalf("Prestart failed: %v", err)
   204  	}
   205  
   206  	resp, err := d.Start(ctx.ExecCtx, task)
   207  	if err != nil {
   208  		t.Fatalf("err: %v", err)
   209  	}
   210  
   211  	// Clean up
   212  	defer func() {
   213  		select {
   214  		case <-resp.Handle.WaitCh():
   215  			// Already exited
   216  			return
   217  		default:
   218  		}
   219  
   220  		if err := resp.Handle.Kill(); err != nil {
   221  			logger.Printf("[TEST] Error killing Qemu test: %s", err)
   222  		}
   223  	}()
   224  
   225  	// Wait until sshd starts before attempting to do a graceful shutdown
   226  	testutil.WaitForResult(func() (bool, error) {
   227  		conn, err := net.Dial("tcp", net.JoinHostPort("127.0.0.1", strconv.Itoa(port)))
   228  		if err != nil {
   229  			return false, err
   230  		}
   231  
   232  		// Since the connection will be accepted by the QEMU process
   233  		// before sshd actually starts, we need to block until we can
   234  		// read the "SSH" magic bytes
   235  		header := make([]byte, 3)
   236  		conn.SetReadDeadline(time.Now().Add(10 * time.Second))
   237  		_, err = conn.Read(header)
   238  		if err != nil {
   239  			return false, err
   240  		}
   241  		if !bytes.Equal(header, []byte{'S', 'S', 'H'}) {
   242  			return false, fmt.Errorf("expected 'SSH' but received: %q %v", string(header), header)
   243  		}
   244  
   245  		logger.Printf("[TEST] connected to sshd in VM")
   246  		conn.Close()
   247  		return true, nil
   248  	}, func(err error) {
   249  		t.Fatalf("failed to connect to sshd in VM: %v", err)
   250  	})
   251  
   252  	monitorPath := filepath.Join(ctx.AllocDir.AllocDir, task.Name, qemuMonitorSocketName)
   253  
   254  	// userPid supplied in sendQemuShutdown calls is bogus (it's used only
   255  	// for log output)
   256  	if err := sendQemuShutdown(ctx.DriverCtx.logger, "", 0); err == nil {
   257  		t.Fatalf("sendQemuShutdown should return an error if monitorPath parameter is empty")
   258  	}
   259  
   260  	if err := sendQemuShutdown(ctx.DriverCtx.logger, "/path/that/does/not/exist", 0); err == nil {
   261  		t.Fatalf("sendQemuShutdown should return an error if file does not exist at monitorPath")
   262  	}
   263  
   264  	if err := sendQemuShutdown(ctx.DriverCtx.logger, monitorPath, 0); err != nil {
   265  		t.Fatalf("unexpected error from sendQemuShutdown: %s", err)
   266  	}
   267  
   268  	select {
   269  	case <-resp.Handle.WaitCh():
   270  		logger.Printf("[TEST] VM exited gracefully as expected")
   271  	case <-time.After(killTimeout):
   272  		t.Fatalf("VM did not exit gracefully exit before timeout: %s", killTimeout)
   273  	}
   274  }
   275  
   276  func TestQemuDriverUser(t *testing.T) {
   277  	if !testutil.IsTravis() {
   278  		t.Parallel()
   279  	}
   280  	ctestutils.QemuCompatible(t)
   281  	tasks := []*structs.Task{
   282  		{
   283  			Name:   "linux",
   284  			Driver: "qemu",
   285  			User:   "alice",
   286  			Config: map[string]interface{}{
   287  				"image_path":        "linux-0.2.img",
   288  				"accelerator":       "tcg",
   289  				"graceful_shutdown": false,
   290  				"port_map": []map[string]int{{
   291  					"main": 22,
   292  					"web":  8080,
   293  				}},
   294  				"args": []string{"-nodefconfig", "-nodefaults"},
   295  				"msg":  "unknown user alice",
   296  			},
   297  			LogConfig: &structs.LogConfig{
   298  				MaxFiles:      10,
   299  				MaxFileSizeMB: 10,
   300  			},
   301  			Resources: &structs.Resources{
   302  				CPU:      500,
   303  				MemoryMB: 512,
   304  				Networks: []*structs.NetworkResource{
   305  					{
   306  						ReservedPorts: []structs.Port{{Label: "main", Value: 22000}, {Label: "web", Value: 80}},
   307  					},
   308  				},
   309  			},
   310  		},
   311  		{
   312  			Name:   "linux",
   313  			Driver: "qemu",
   314  			User:   "alice",
   315  			Config: map[string]interface{}{
   316  				"image_path":  "linux-0.2.img",
   317  				"accelerator": "tcg",
   318  				"port_map": []map[string]int{{
   319  					"main": 22,
   320  					"web":  8080,
   321  				}},
   322  				"args": []string{"-nodefconfig", "-nodefaults"},
   323  				"msg":  "Qemu memory assignment out of bounds",
   324  			},
   325  			LogConfig: &structs.LogConfig{
   326  				MaxFiles:      10,
   327  				MaxFileSizeMB: 10,
   328  			},
   329  			Resources: &structs.Resources{
   330  				CPU:      500,
   331  				MemoryMB: -1,
   332  				Networks: []*structs.NetworkResource{
   333  					{
   334  						ReservedPorts: []structs.Port{{Label: "main", Value: 22000}, {Label: "web", Value: 80}},
   335  					},
   336  				},
   337  			},
   338  		},
   339  	}
   340  
   341  	for _, task := range tasks {
   342  		ctx := testDriverContexts(t, task)
   343  		defer ctx.AllocDir.Destroy()
   344  		d := NewQemuDriver(ctx.DriverCtx)
   345  
   346  		if _, err := d.Prestart(ctx.ExecCtx, task); err != nil {
   347  			t.Fatalf("Prestart faild: %v", err)
   348  		}
   349  
   350  		resp, err := d.Start(ctx.ExecCtx, task)
   351  		if err == nil {
   352  			resp.Handle.Kill()
   353  			t.Fatalf("Should've failed")
   354  		}
   355  
   356  		msg := task.Config["msg"].(string)
   357  		if !strings.Contains(err.Error(), msg) {
   358  			t.Fatalf("Expecting '%v' in '%v'", msg, err)
   359  		}
   360  	}
   361  }
   362  
   363  func TestQemuDriverGetMonitorPathOldQemu(t *testing.T) {
   364  	task := &structs.Task{
   365  		Name:   "linux",
   366  		Driver: "qemu",
   367  		Config: map[string]interface{}{
   368  			"image_path":        "linux-0.2.img",
   369  			"accelerator":       "tcg",
   370  			"graceful_shutdown": true,
   371  			"port_map": []map[string]int{{
   372  				"main": 22,
   373  				"web":  8080,
   374  			}},
   375  			"args": []string{"-nodefconfig", "-nodefaults"},
   376  		},
   377  		KillTimeout: time.Duration(1 * time.Second),
   378  		LogConfig: &structs.LogConfig{
   379  			MaxFiles:      10,
   380  			MaxFileSizeMB: 10,
   381  		},
   382  		Resources: &structs.Resources{
   383  			CPU:      500,
   384  			MemoryMB: 512,
   385  			Networks: []*structs.NetworkResource{
   386  				{
   387  					ReservedPorts: []structs.Port{{Label: "main", Value: 22000}, {Label: "web", Value: 80}},
   388  				},
   389  			},
   390  		},
   391  	}
   392  
   393  	ctx := testDriverContexts(t, task)
   394  	defer ctx.AllocDir.Destroy()
   395  
   396  	// Simulate an older version of qemu which does not support long monitor socket paths
   397  	ctx.DriverCtx.node.Attributes[qemuDriverVersionAttr] = "2.0.0"
   398  
   399  	d := &QemuDriver{DriverContext: *ctx.DriverCtx}
   400  
   401  	shortPath := strings.Repeat("x", 10)
   402  	_, err := d.getMonitorPath(shortPath)
   403  	if err != nil {
   404  		t.Fatal("Should not have returned an error")
   405  	}
   406  
   407  	longPath := strings.Repeat("x", qemuLegacyMaxMonitorPathLen+100)
   408  	_, err = d.getMonitorPath(longPath)
   409  	if err == nil {
   410  		t.Fatal("Should have returned an error")
   411  	}
   412  
   413  	// Max length includes the '/' separator and socket name
   414  	maxLengthCount := qemuLegacyMaxMonitorPathLen - len(qemuMonitorSocketName) - 1
   415  	maxLengthLegacyPath := strings.Repeat("x", maxLengthCount)
   416  	_, err = d.getMonitorPath(maxLengthLegacyPath)
   417  	if err != nil {
   418  		t.Fatalf("Should not have returned an error: %s", err)
   419  	}
   420  }
   421  
   422  func TestQemuDriverGetMonitorPathNewQemu(t *testing.T) {
   423  	task := &structs.Task{
   424  		Name:   "linux",
   425  		Driver: "qemu",
   426  		Config: map[string]interface{}{
   427  			"image_path":        "linux-0.2.img",
   428  			"accelerator":       "tcg",
   429  			"graceful_shutdown": true,
   430  			"port_map": []map[string]int{{
   431  				"main": 22,
   432  				"web":  8080,
   433  			}},
   434  			"args": []string{"-nodefconfig", "-nodefaults"},
   435  		},
   436  		KillTimeout: time.Duration(1 * time.Second),
   437  		LogConfig: &structs.LogConfig{
   438  			MaxFiles:      10,
   439  			MaxFileSizeMB: 10,
   440  		},
   441  		Resources: &structs.Resources{
   442  			CPU:      500,
   443  			MemoryMB: 512,
   444  			Networks: []*structs.NetworkResource{
   445  				{
   446  					ReservedPorts: []structs.Port{{Label: "main", Value: 22000}, {Label: "web", Value: 80}},
   447  				},
   448  			},
   449  		},
   450  	}
   451  
   452  	ctx := testDriverContexts(t, task)
   453  	defer ctx.AllocDir.Destroy()
   454  
   455  	// Simulate a version of qemu which supports long monitor socket paths
   456  	ctx.DriverCtx.node.Attributes[qemuDriverVersionAttr] = "2.99.99"
   457  
   458  	d := &QemuDriver{DriverContext: *ctx.DriverCtx}
   459  
   460  	shortPath := strings.Repeat("x", 10)
   461  	_, err := d.getMonitorPath(shortPath)
   462  	if err != nil {
   463  		t.Fatal("Should not have returned an error")
   464  	}
   465  
   466  	longPath := strings.Repeat("x", qemuLegacyMaxMonitorPathLen+100)
   467  	_, err = d.getMonitorPath(longPath)
   468  	if err != nil {
   469  		t.Fatal("Should not have returned an error")
   470  	}
   471  
   472  	maxLengthCount := qemuLegacyMaxMonitorPathLen - len(qemuMonitorSocketName) - 1
   473  	maxLengthLegacyPath := strings.Repeat("x", maxLengthCount)
   474  	_, err = d.getMonitorPath(maxLengthLegacyPath)
   475  	if err != nil {
   476  		t.Fatal("Should not have returned an error")
   477  	}
   478  }