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