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