github.com/bigcommerce/nomad@v0.9.3-bc/drivers/qemu/driver_test.go (about)

     1  package qemu
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"os"
     7  	"path/filepath"
     8  	"strings"
     9  	"testing"
    10  	"time"
    11  
    12  	ctestutil "github.com/hashicorp/nomad/client/testutil"
    13  	"github.com/hashicorp/nomad/helper/pluginutils/hclutils"
    14  	"github.com/hashicorp/nomad/helper/testlog"
    15  	"github.com/hashicorp/nomad/helper/uuid"
    16  	"github.com/hashicorp/nomad/nomad/structs"
    17  	"github.com/hashicorp/nomad/plugins/drivers"
    18  	dtestutil "github.com/hashicorp/nomad/plugins/drivers/testutils"
    19  	pstructs "github.com/hashicorp/nomad/plugins/shared/structs"
    20  	"github.com/hashicorp/nomad/testutil"
    21  	"github.com/stretchr/testify/require"
    22  )
    23  
    24  // TODO(preetha) - tests remaining
    25  // using monitor socket for graceful shutdown
    26  
    27  // Verifies starting a qemu image and stopping it
    28  func TestQemuDriver_Start_Wait_Stop(t *testing.T) {
    29  	ctestutil.QemuCompatible(t)
    30  	if !testutil.IsCI() {
    31  		t.Parallel()
    32  	}
    33  
    34  	require := require.New(t)
    35  	d := NewQemuDriver(testlog.HCLogger(t))
    36  	harness := dtestutil.NewDriverHarness(t, d)
    37  
    38  	task := &drivers.TaskConfig{
    39  		ID:   uuid.Generate(),
    40  		Name: "linux",
    41  		Resources: &drivers.Resources{
    42  			NomadResources: &structs.AllocatedTaskResources{
    43  				Memory: structs.AllocatedMemoryResources{
    44  					MemoryMB: 512,
    45  				},
    46  				Cpu: structs.AllocatedCpuResources{
    47  					CpuShares: 100,
    48  				},
    49  				Networks: []*structs.NetworkResource{
    50  					{
    51  						ReservedPorts: []structs.Port{{Label: "main", Value: 22000}, {Label: "web", Value: 80}},
    52  					},
    53  				},
    54  			},
    55  		},
    56  	}
    57  
    58  	tc := &TaskConfig{
    59  		ImagePath:        "linux-0.2.img",
    60  		Accelerator:      "tcg",
    61  		GracefulShutdown: false,
    62  		PortMap: map[string]int{
    63  			"main": 22,
    64  			"web":  8080,
    65  		},
    66  		Args: []string{"-nodefconfig", "-nodefaults"},
    67  	}
    68  	require.NoError(task.EncodeConcreteDriverConfig(&tc))
    69  	cleanup := harness.MkAllocDir(task, true)
    70  	defer cleanup()
    71  
    72  	taskDir := filepath.Join(task.AllocDir, task.Name)
    73  
    74  	copyFile("./test-resources/linux-0.2.img", filepath.Join(taskDir, "linux-0.2.img"), t)
    75  
    76  	handle, _, err := harness.StartTask(task)
    77  	require.NoError(err)
    78  
    79  	require.NotNil(handle)
    80  
    81  	// Ensure that sending a Signal returns an error
    82  	err = d.SignalTask(task.ID, "SIGINT")
    83  	require.NotNil(err)
    84  
    85  	require.NoError(harness.DestroyTask(task.ID, true))
    86  
    87  }
    88  
    89  // Verifies monitor socket path for old qemu
    90  func TestQemuDriver_GetMonitorPathOldQemu(t *testing.T) {
    91  	ctestutil.QemuCompatible(t)
    92  	if !testutil.IsCI() {
    93  		t.Parallel()
    94  	}
    95  
    96  	require := require.New(t)
    97  	d := NewQemuDriver(testlog.HCLogger(t))
    98  	harness := dtestutil.NewDriverHarness(t, d)
    99  
   100  	task := &drivers.TaskConfig{
   101  		ID:   uuid.Generate(),
   102  		Name: "linux",
   103  		Resources: &drivers.Resources{
   104  			NomadResources: &structs.AllocatedTaskResources{
   105  				Memory: structs.AllocatedMemoryResources{
   106  					MemoryMB: 512,
   107  				},
   108  				Cpu: structs.AllocatedCpuResources{
   109  					CpuShares: 100,
   110  				},
   111  				Networks: []*structs.NetworkResource{
   112  					{
   113  						ReservedPorts: []structs.Port{{Label: "main", Value: 22000}, {Label: "web", Value: 80}},
   114  					},
   115  				},
   116  			},
   117  		},
   118  	}
   119  
   120  	cleanup := harness.MkAllocDir(task, true)
   121  	defer cleanup()
   122  
   123  	fingerPrint := &drivers.Fingerprint{
   124  		Attributes: map[string]*pstructs.Attribute{
   125  			driverVersionAttr: pstructs.NewStringAttribute("2.0.0"),
   126  		},
   127  	}
   128  	shortPath := strings.Repeat("x", 10)
   129  	qemuDriver := d.(*Driver)
   130  	_, err := qemuDriver.getMonitorPath(shortPath, fingerPrint)
   131  	require.Nil(err)
   132  
   133  	longPath := strings.Repeat("x", qemuLegacyMaxMonitorPathLen+100)
   134  	_, err = qemuDriver.getMonitorPath(longPath, fingerPrint)
   135  	require.NotNil(err)
   136  
   137  	// Max length includes the '/' separator and socket name
   138  	maxLengthCount := qemuLegacyMaxMonitorPathLen - len(qemuMonitorSocketName) - 1
   139  	maxLengthLegacyPath := strings.Repeat("x", maxLengthCount)
   140  	_, err = qemuDriver.getMonitorPath(maxLengthLegacyPath, fingerPrint)
   141  	require.Nil(err)
   142  }
   143  
   144  // Verifies monitor socket path for new qemu version
   145  func TestQemuDriver_GetMonitorPathNewQemu(t *testing.T) {
   146  	ctestutil.QemuCompatible(t)
   147  	if !testutil.IsCI() {
   148  		t.Parallel()
   149  	}
   150  
   151  	require := require.New(t)
   152  	d := NewQemuDriver(testlog.HCLogger(t))
   153  	harness := dtestutil.NewDriverHarness(t, d)
   154  
   155  	task := &drivers.TaskConfig{
   156  		ID:   uuid.Generate(),
   157  		Name: "linux",
   158  		Resources: &drivers.Resources{
   159  			NomadResources: &structs.AllocatedTaskResources{
   160  				Memory: structs.AllocatedMemoryResources{
   161  					MemoryMB: 512,
   162  				},
   163  				Cpu: structs.AllocatedCpuResources{
   164  					CpuShares: 100,
   165  				},
   166  				Networks: []*structs.NetworkResource{
   167  					{
   168  						ReservedPorts: []structs.Port{{Label: "main", Value: 22000}, {Label: "web", Value: 80}},
   169  					},
   170  				},
   171  			},
   172  		},
   173  	}
   174  
   175  	cleanup := harness.MkAllocDir(task, true)
   176  	defer cleanup()
   177  
   178  	fingerPrint := &drivers.Fingerprint{
   179  		Attributes: map[string]*pstructs.Attribute{
   180  			driverVersionAttr: pstructs.NewStringAttribute("2.99.99"),
   181  		},
   182  	}
   183  	shortPath := strings.Repeat("x", 10)
   184  	qemuDriver := d.(*Driver)
   185  	_, err := qemuDriver.getMonitorPath(shortPath, fingerPrint)
   186  	require.Nil(err)
   187  
   188  	// Should not return an error in this qemu version
   189  	longPath := strings.Repeat("x", qemuLegacyMaxMonitorPathLen+100)
   190  	_, err = qemuDriver.getMonitorPath(longPath, fingerPrint)
   191  	require.Nil(err)
   192  
   193  	// Max length includes the '/' separator and socket name
   194  	maxLengthCount := qemuLegacyMaxMonitorPathLen - len(qemuMonitorSocketName) - 1
   195  	maxLengthLegacyPath := strings.Repeat("x", maxLengthCount)
   196  	_, err = qemuDriver.getMonitorPath(maxLengthLegacyPath, fingerPrint)
   197  	require.Nil(err)
   198  }
   199  
   200  // copyFile moves an existing file to the destination
   201  func copyFile(src, dst string, t *testing.T) {
   202  	in, err := os.Open(src)
   203  	if err != nil {
   204  		t.Fatalf("copying %v -> %v failed: %v", src, dst, err)
   205  	}
   206  	defer in.Close()
   207  	out, err := os.Create(dst)
   208  	if err != nil {
   209  		t.Fatalf("copying %v -> %v failed: %v", src, dst, err)
   210  	}
   211  	defer func() {
   212  		if err := out.Close(); err != nil {
   213  			t.Fatalf("copying %v -> %v failed: %v", src, dst, err)
   214  		}
   215  	}()
   216  	if _, err = io.Copy(out, in); err != nil {
   217  		t.Fatalf("copying %v -> %v failed: %v", src, dst, err)
   218  	}
   219  	if err := out.Sync(); err != nil {
   220  		t.Fatalf("copying %v -> %v failed: %v", src, dst, err)
   221  	}
   222  }
   223  
   224  // Verifies starting a qemu image and stopping it
   225  func TestQemuDriver_User(t *testing.T) {
   226  	ctestutil.QemuCompatible(t)
   227  	if !testutil.IsCI() {
   228  		t.Parallel()
   229  	}
   230  
   231  	require := require.New(t)
   232  	d := NewQemuDriver(testlog.HCLogger(t))
   233  	harness := dtestutil.NewDriverHarness(t, d)
   234  
   235  	task := &drivers.TaskConfig{
   236  		ID:   uuid.Generate(),
   237  		Name: "linux",
   238  		User: "alice",
   239  		Resources: &drivers.Resources{
   240  			NomadResources: &structs.AllocatedTaskResources{
   241  				Memory: structs.AllocatedMemoryResources{
   242  					MemoryMB: 512,
   243  				},
   244  				Cpu: structs.AllocatedCpuResources{
   245  					CpuShares: 100,
   246  				},
   247  				Networks: []*structs.NetworkResource{
   248  					{
   249  						ReservedPorts: []structs.Port{{Label: "main", Value: 22000}, {Label: "web", Value: 80}},
   250  					},
   251  				},
   252  			},
   253  		},
   254  	}
   255  
   256  	tc := &TaskConfig{
   257  		ImagePath:        "linux-0.2.img",
   258  		Accelerator:      "tcg",
   259  		GracefulShutdown: false,
   260  		PortMap: map[string]int{
   261  			"main": 22,
   262  			"web":  8080,
   263  		},
   264  		Args: []string{"-nodefconfig", "-nodefaults"},
   265  	}
   266  	require.NoError(task.EncodeConcreteDriverConfig(&tc))
   267  	cleanup := harness.MkAllocDir(task, true)
   268  	defer cleanup()
   269  
   270  	taskDir := filepath.Join(task.AllocDir, task.Name)
   271  
   272  	copyFile("./test-resources/linux-0.2.img", filepath.Join(taskDir, "linux-0.2.img"), t)
   273  
   274  	_, _, err := harness.StartTask(task)
   275  	require.Error(err)
   276  	require.Contains(err.Error(), "unknown user alice", err.Error())
   277  
   278  }
   279  
   280  //  Verifies getting resource usage stats
   281  // TODO(preetha) this test needs random sleeps to pass
   282  func TestQemuDriver_Stats(t *testing.T) {
   283  	ctestutil.QemuCompatible(t)
   284  	if !testutil.IsCI() {
   285  		t.Parallel()
   286  	}
   287  
   288  	require := require.New(t)
   289  	d := NewQemuDriver(testlog.HCLogger(t))
   290  	harness := dtestutil.NewDriverHarness(t, d)
   291  
   292  	task := &drivers.TaskConfig{
   293  		ID:   uuid.Generate(),
   294  		Name: "linux",
   295  		Resources: &drivers.Resources{
   296  			NomadResources: &structs.AllocatedTaskResources{
   297  				Memory: structs.AllocatedMemoryResources{
   298  					MemoryMB: 512,
   299  				},
   300  				Cpu: structs.AllocatedCpuResources{
   301  					CpuShares: 100,
   302  				},
   303  				Networks: []*structs.NetworkResource{
   304  					{
   305  						ReservedPorts: []structs.Port{{Label: "main", Value: 22000}, {Label: "web", Value: 80}},
   306  					},
   307  				},
   308  			},
   309  		},
   310  	}
   311  
   312  	tc := &TaskConfig{
   313  		ImagePath:        "linux-0.2.img",
   314  		Accelerator:      "tcg",
   315  		GracefulShutdown: false,
   316  		PortMap: map[string]int{
   317  			"main": 22,
   318  			"web":  8080,
   319  		},
   320  		Args: []string{"-nodefconfig", "-nodefaults"},
   321  	}
   322  	require.NoError(task.EncodeConcreteDriverConfig(&tc))
   323  	cleanup := harness.MkAllocDir(task, true)
   324  	defer cleanup()
   325  
   326  	taskDir := filepath.Join(task.AllocDir, task.Name)
   327  
   328  	copyFile("./test-resources/linux-0.2.img", filepath.Join(taskDir, "linux-0.2.img"), t)
   329  
   330  	handle, _, err := harness.StartTask(task)
   331  	require.NoError(err)
   332  
   333  	require.NotNil(handle)
   334  
   335  	// Wait for task to start
   336  	_, err = harness.WaitTask(context.Background(), handle.Config.ID)
   337  	require.NoError(err)
   338  
   339  	// Wait until task started
   340  	require.NoError(harness.WaitUntilStarted(task.ID, 1*time.Second))
   341  	time.Sleep(30 * time.Second)
   342  	statsCtx, cancel := context.WithCancel(context.Background())
   343  	defer cancel()
   344  	statsCh, err := harness.TaskStats(statsCtx, task.ID, time.Second*10)
   345  	require.NoError(err)
   346  
   347  	select {
   348  	case stats := <-statsCh:
   349  		t.Logf("CPU:%+v Memory:%+v\n", stats.ResourceUsage.CpuStats, stats.ResourceUsage.MemoryStats)
   350  		require.NotZero(stats.ResourceUsage.MemoryStats.RSS)
   351  		require.NoError(harness.DestroyTask(task.ID, true))
   352  	case <-time.After(time.Second * 1):
   353  		require.Fail("timeout receiving from stats")
   354  	}
   355  
   356  }
   357  
   358  func TestQemuDriver_Fingerprint(t *testing.T) {
   359  	require := require.New(t)
   360  
   361  	ctestutil.QemuCompatible(t)
   362  	if !testutil.IsCI() {
   363  		t.Parallel()
   364  	}
   365  
   366  	d := NewQemuDriver(testlog.HCLogger(t))
   367  	harness := dtestutil.NewDriverHarness(t, d)
   368  
   369  	fingerCh, err := harness.Fingerprint(context.Background())
   370  	require.NoError(err)
   371  	select {
   372  	case finger := <-fingerCh:
   373  		require.Equal(drivers.HealthStateHealthy, finger.Health)
   374  		require.True(finger.Attributes["driver.qemu"].GetBool())
   375  	case <-time.After(time.Duration(testutil.TestMultiplier()*5) * time.Second):
   376  		require.Fail("timeout receiving fingerprint")
   377  	}
   378  }
   379  
   380  func TestConfig_ParseAllHCL(t *testing.T) {
   381  	cfgStr := `
   382  config {
   383    image_path = "/tmp/image_path"
   384    accelerator = "kvm"
   385    args = ["arg1", "arg2"]
   386    port_map {
   387      http = 80
   388      https = 443
   389    }
   390    graceful_shutdown = true
   391  }`
   392  
   393  	expected := &TaskConfig{
   394  		ImagePath:   "/tmp/image_path",
   395  		Accelerator: "kvm",
   396  		Args:        []string{"arg1", "arg2"},
   397  		PortMap: map[string]int{
   398  			"http":  80,
   399  			"https": 443,
   400  		},
   401  		GracefulShutdown: true,
   402  	}
   403  
   404  	var tc *TaskConfig
   405  	hclutils.NewConfigParser(taskConfigSpec).ParseHCL(t, cfgStr, &tc)
   406  
   407  	require.EqualValues(t, expected, tc)
   408  }