github.com/whamcloud/lemur@v0.0.0-20190827193804-4655df8a52af/cmd/lhsmd/agent_e2e_test.go (about)

     1  // Copyright (c) 2018 DDN. All rights reserved.
     2  // Use of this source code is governed by a MIT-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package main_test
     6  
     7  import (
     8  	"encoding/json"
     9  	"flag"
    10  	"fmt"
    11  	"os"
    12  	"path"
    13  	"testing"
    14  	"time"
    15  
    16  	"github.com/fortytw2/leaktest"
    17  	"github.com/pkg/errors"
    18  
    19  	"golang.org/x/net/context"
    20  
    21  	"github.com/intel-hpdd/go-lustre"
    22  	"github.com/intel-hpdd/go-lustre/fs"
    23  	"github.com/intel-hpdd/go-lustre/hsm"
    24  	"github.com/intel-hpdd/go-lustre/llapi"
    25  	"github.com/intel-hpdd/lemur/cmd/lhsmd/agent"
    26  	"github.com/intel-hpdd/lemur/cmd/lhsmd/agent/fileid"
    27  	"github.com/intel-hpdd/lemur/cmd/lhsmd/config"
    28  	_ "github.com/intel-hpdd/lemur/cmd/lhsmd/transport/grpc"
    29  	"github.com/intel-hpdd/lemur/dmplugin"
    30  	"github.com/intel-hpdd/lemur/pkg/fsroot"
    31  	"github.com/intel-hpdd/logging/debug"
    32  )
    33  
    34  const (
    35  	testSocketDir = "/tmp"
    36  	testArchiveID = 1
    37  )
    38  
    39  var (
    40  	enableLeakTest = false
    41  )
    42  
    43  func init() {
    44  	flag.BoolVar(&enableLeakTest, "leak", false, "enable leak check")
    45  	flag.Parse()
    46  	// swap in the dummy implementation
    47  	fileid.EnableTestMode()
    48  }
    49  
    50  type (
    51  	signalChan chan struct{}
    52  
    53  	testMover struct {
    54  		started        signalChan
    55  		receivedAction chan dmplugin.Action
    56  		plugin         *dmplugin.Plugin
    57  	}
    58  
    59  	testMoverData struct {
    60  		UUID        string
    61  		Length      int64
    62  		Errval      int
    63  		UpdateCount int
    64  	}
    65  )
    66  
    67  // Archive tests archive requests
    68  // * The request data is used as the fileID
    69  func (t *testMover) Archive(a dmplugin.Action) error {
    70  	debug.Printf("testMover received Archive action: %s", a)
    71  	t.receivedAction <- a
    72  	var data testMoverData
    73  	err := json.Unmarshal(a.Data(), &data)
    74  	if err != nil {
    75  		return errors.Wrap(err, fmt.Sprintf("parsing '%s'", string(a.Data())))
    76  	}
    77  
    78  	if data.UUID != "" {
    79  		a.SetUUID(data.UUID)
    80  	}
    81  	if data.Length > 0 {
    82  		a.SetActualLength(data.Length)
    83  	}
    84  
    85  	if data.UpdateCount > 0 {
    86  		var offset int64
    87  		length := data.Length / int64(data.UpdateCount)
    88  		for i := 0; i < data.UpdateCount; i++ {
    89  			a.Update(offset, length, data.Length)
    90  			offset += length
    91  		}
    92  	}
    93  	if data.Errval != 0 {
    94  		return errors.New("We failed")
    95  	}
    96  	return nil
    97  }
    98  
    99  func (t *testMover) Restore(a dmplugin.Action) error {
   100  	debug.Printf("testMover received Restore action: %s", a)
   101  	t.receivedAction <- a
   102  	var data testMoverData
   103  	err := json.Unmarshal(a.Data(), &data)
   104  	if err != nil {
   105  		return errors.Wrap(err, fmt.Sprintf("parsing '%s'", string(a.Data())))
   106  	}
   107  
   108  	if data.Length > 0 {
   109  		a.SetActualLength(data.Length)
   110  	}
   111  
   112  	if data.UpdateCount > 0 {
   113  		var offset int64
   114  		length := data.Length / int64(data.UpdateCount)
   115  		for i := 0; i < data.UpdateCount; i++ {
   116  			a.Update(offset, length, data.Length)
   117  			offset += length
   118  		}
   119  	}
   120  
   121  	if data.Errval != 0 {
   122  		return errors.New("We failed")
   123  	}
   124  
   125  	return nil
   126  }
   127  
   128  func (t *testMover) Remove(a dmplugin.Action) error {
   129  	debug.Printf("testMover received Remove action: %s", a)
   130  	t.receivedAction <- a
   131  
   132  	var data testMoverData
   133  	err := json.Unmarshal(a.Data(), &data)
   134  	if err != nil {
   135  		return errors.Wrap(err, fmt.Sprintf("parsing '%s'", string(a.Data())))
   136  	}
   137  
   138  	if data.Length > 0 {
   139  		a.SetActualLength(data.Length)
   140  	}
   141  
   142  	if data.UpdateCount > 0 {
   143  		var offset int64
   144  		length := data.Length / int64(data.UpdateCount)
   145  		for i := 0; i < data.UpdateCount; i++ {
   146  			a.Update(offset, length, data.Length)
   147  			offset += length
   148  		}
   149  	}
   150  
   151  	if data.Errval != 0 {
   152  		return errors.New("We failed")
   153  	}
   154  
   155  	return nil
   156  }
   157  
   158  func (t *testMover) Started() signalChan {
   159  	return t.started
   160  }
   161  
   162  func (t *testMover) Start() {
   163  	close(t.started)
   164  }
   165  
   166  func (t *testMover) ReceivedAction() chan dmplugin.Action {
   167  	return t.receivedAction
   168  }
   169  
   170  func (t *testMover) Stop() {
   171  	t.plugin.Stop()
   172  	t.plugin.Close()
   173  	close(t.receivedAction)
   174  }
   175  
   176  func newTestMover(p *dmplugin.Plugin) *testMover {
   177  	return &testMover{
   178  		started:        make(signalChan),
   179  		receivedAction: make(chan dmplugin.Action),
   180  		plugin:         p,
   181  	}
   182  }
   183  
   184  func testStartMover(t *testing.T) *testMover {
   185  	plugin, err := dmplugin.New(path.Base(os.Args[0]), func(path string) (fsroot.Client, error) {
   186  		return fsroot.Test(path), nil
   187  	})
   188  	if err != nil {
   189  		t.Fatal(err)
   190  	}
   191  
   192  	tm := newTestMover(plugin)
   193  	plugin.AddMover(&dmplugin.Config{
   194  		Mover:     tm,
   195  		ArchiveID: uint32(testArchiveID),
   196  	})
   197  	go plugin.Run()
   198  
   199  	// Wait for the mover to signal that it has been started
   200  	<-tm.Started()
   201  
   202  	return tm
   203  }
   204  
   205  func newTestAgent(t *testing.T, as hsm.ActionSource) *agent.HsmAgent {
   206  	// Ambivalent about doing this config here vs. in agent.TestAgent;
   207  	// leaving it here for now with the idea that tests may want to
   208  	// supply their own implementations of these things.
   209  	cfg := agent.DefaultConfig()
   210  	cfg.Transport.SocketDir = testSocketDir
   211  
   212  	// Configure environment to launch plugins
   213  	os.Setenv(config.AgentConnEnvVar, cfg.Transport.ConnectionString())
   214  	os.Setenv(config.PluginMountpointEnvVar, "/tmp")
   215  	os.Setenv(config.ConfigDirEnvVar, "/tmp")
   216  
   217  	a, err := agent.New(cfg, fsroot.Test(cfg.AgentMountpoint()), as)
   218  	if err != nil {
   219  		t.Fatal(err)
   220  	}
   221  
   222  	return a
   223  }
   224  
   225  func testStartAgent(t *testing.T, as hsm.ActionSource) *agent.HsmAgent {
   226  	ta := newTestAgent(t, as)
   227  	go func() {
   228  		if err := ta.Start(context.Background()); err != nil {
   229  			t.Fatalf("Test agent startup failed: %s", err)
   230  		}
   231  	}()
   232  
   233  	// Wait for the agent to signal that it has started
   234  	ta.StartWaitFor(5 * time.Second)
   235  
   236  	return ta
   237  }
   238  
   239  func testGenFid(t *testing.T, id int) *lustre.Fid {
   240  	testFid, err := lustre.ParseFid(fmt.Sprintf("0xdead:0x%x:0x0", id))
   241  	if err != nil {
   242  		t.Fatalf("error generating test fid: %s", err)
   243  	}
   244  	return testFid
   245  }
   246  
   247  func TestArchiveEndToEnd(t *testing.T) {
   248  	// NB: Leaktest finds a leak in the go-metrics library, but everything
   249  	// else seems fine.
   250  	if enableLeakTest {
   251  		defer leaktest.Check(t)()
   252  	}
   253  
   254  	// First, start a test agent to delegate work to test data movers.
   255  	as := hsm.NewTestSource()
   256  
   257  	ta := testStartAgent(t, as)
   258  	defer ta.Stop()
   259  
   260  	// Now, start a data mover plugin which will connect to our
   261  	// test agent to receive an injected action.
   262  	tm := testStartMover(t)
   263  	defer tm.Stop()
   264  
   265  	cases := []testMoverData{
   266  		{Length: 100, UpdateCount: 1},
   267  		{Length: 10000, UpdateCount: 5},
   268  		{UpdateCount: 1, Errval: -1},
   269  		{Errval: -1},
   270  	}
   271  
   272  	for i, expected := range cases {
   273  		testFid := testGenFid(t, i)
   274  		if expected.UUID == "" {
   275  			expected.UUID = fmt.Sprintf("testid-%x", i)
   276  		}
   277  		adata, err := agent.MarshalActionData(nil, &expected)
   278  		if err != nil {
   279  			t.Fatal(err)
   280  		}
   281  
   282  		// Inject an action
   283  		tr := hsm.NewTestRequest(uint(testArchiveID), llapi.HsmActionArchive, testFid, adata)
   284  		as.Inject(tr)
   285  
   286  		// Wait for the mover to signal that it has received the action
   287  		// on the other side of the RPC interface
   288  		action := <-tm.ReceivedAction()
   289  		actionPath := action.PrimaryPath()
   290  		fidPath := fs.FidRelativePath(testFid)
   291  		if actionPath != fidPath {
   292  			debug.Printf("%d: received nil action", i)
   293  			t.Fatalf("expected path %s, got %s", fidPath, actionPath)
   294  		}
   295  
   296  		// Wait for the mover to send a progress update on the action
   297  		updateCount := 0
   298  		for update := range tr.ProgressUpdates() {
   299  			updateCount++
   300  			debug.Printf("Update: %v", update)
   301  			if update.Cookie != tr.Cookie() {
   302  				t.Fatalf("cookie mismatch request: %v  update: %v", tr.Cookie(), update.Cookie)
   303  			}
   304  			if update.Complete {
   305  				if expected.Errval != 0 {
   306  					if update.Errval != expected.Errval {
   307  						t.Fatalf("Errval expected %v != %v", expected.Errval, update.Errval)
   308  					}
   309  					continue
   310  				}
   311  
   312  				buf, _ := fileid.UUID.GetByFid(fs.RootDir{}, testFid)
   313  				if string(buf) != expected.UUID {
   314  					t.Fatalf("fileID invalid '%s'", buf)
   315  				}
   316  				if update.Length != expected.Length {
   317  					t.Fatalf("Length expected %v != %v", expected.Length, update.Length)
   318  				}
   319  			}
   320  		}
   321  		if updateCount-1 != expected.UpdateCount {
   322  			t.Fatalf("UpdateCount expected %v != %v", expected.UpdateCount, updateCount-1)
   323  		}
   324  	}
   325  }
   326  
   327  func TestRestoreEndToEnd(t *testing.T) {
   328  	if enableLeakTest {
   329  		defer leaktest.Check(t)()
   330  	}
   331  
   332  	// First, start a test agent to delegate work to test data movers.
   333  	as := hsm.NewTestSource()
   334  	ta := testStartAgent(t, as)
   335  	defer ta.Stop()
   336  
   337  	// Now, start a data mover plugin which will connect to our
   338  	// test agent to receive an injected action.
   339  	tm := testStartMover(t)
   340  	defer tm.Stop()
   341  
   342  	cases := []testMoverData{
   343  		{Length: 100, UpdateCount: 1},
   344  		{Length: 10000, UpdateCount: 5},
   345  		{UpdateCount: 1, Errval: -1},
   346  		{Errval: -1},
   347  	}
   348  
   349  	for i, expected := range cases {
   350  		testFid := testGenFid(t, i)
   351  
   352  		fileid.UUID.Set(fs.FidRelativePath(testFid), []byte("moo"))
   353  		// Inject an action
   354  		adata, err := agent.MarshalActionData(nil, &expected)
   355  		if err != nil {
   356  			t.Fatal(err)
   357  		}
   358  
   359  		tr := hsm.NewTestRequest(uint(testArchiveID), llapi.HsmActionRestore, testFid, adata)
   360  		as.Inject(tr)
   361  
   362  		// Wait for the mover to signal that it has received the action
   363  		// on the other side of the RPC interface
   364  		action := <-tm.ReceivedAction()
   365  
   366  		actionPath := action.PrimaryPath()
   367  		fidPath := fs.FidRelativePath(testFid)
   368  		if actionPath != fidPath {
   369  			t.Fatalf("expected path %s, got %s", fidPath, actionPath)
   370  		}
   371  
   372  		// Wait for the mover to send a progress update on the action
   373  		updateCount := 0
   374  		for update := range tr.ProgressUpdates() {
   375  			updateCount++
   376  			debug.Printf("Update: %v", update)
   377  			if update.Cookie != tr.Cookie() {
   378  				t.Fatalf("cookie mismatch request: %v  update: %v", tr.Cookie(), update.Cookie)
   379  			}
   380  			if update.Complete {
   381  				if expected.Errval != 0 {
   382  					if update.Errval != expected.Errval {
   383  						t.Fatalf("Errval expected %v != %v", expected.Errval, update.Errval)
   384  					}
   385  					continue
   386  				}
   387  
   388  				if update.Length != expected.Length {
   389  					t.Fatalf("Length expected %v != %v", expected.Length, update.Length)
   390  				}
   391  			}
   392  		}
   393  		if updateCount-1 != expected.UpdateCount {
   394  			t.Fatalf("UpdateCount expected %v != %v", expected.UpdateCount, updateCount-1)
   395  		}
   396  	}
   397  }
   398  
   399  func TestRemoveEndToEnd(t *testing.T) {
   400  	if enableLeakTest {
   401  		defer leaktest.Check(t)()
   402  	}
   403  
   404  	// First, start a test agent to delegate work to test data movers.
   405  	as := hsm.NewTestSource()
   406  	ta := testStartAgent(t, as)
   407  	defer ta.Stop()
   408  
   409  	// Now, start a data mover plugin which will connect to our
   410  	// test agent to receive an injected action.
   411  	tm := testStartMover(t)
   412  	defer tm.Stop()
   413  
   414  	cases := []testMoverData{
   415  		{Length: 100},
   416  		{Length: 10000},
   417  		{Errval: -1},
   418  		{Errval: -1},
   419  	}
   420  
   421  	for i, expected := range cases {
   422  		testFid := testGenFid(t, i)
   423  		fileid.UUID.Set(fs.FidRelativePath(testFid), []byte("moo"))
   424  		// Inject an action
   425  		adata, err := agent.MarshalActionData(nil, &expected)
   426  		if err != nil {
   427  			t.Fatal(err)
   428  		}
   429  
   430  		tr := hsm.NewTestRequest(uint(testArchiveID), llapi.HsmActionRemove, testFid, adata)
   431  		as.Inject(tr)
   432  
   433  		// Wait for the mover to signal that it has received the action
   434  		// on the other side of the RPC interface
   435  		action := <-tm.ReceivedAction()
   436  
   437  		actionPath := action.PrimaryPath()
   438  		fidPath := fs.FidRelativePath(testFid)
   439  		if actionPath != fidPath {
   440  			t.Fatalf("expected path %s, got %s", fidPath, actionPath)
   441  		}
   442  
   443  		// Wait for the mover to send a progress update on the action
   444  		updateCount := 0
   445  		for update := range tr.ProgressUpdates() {
   446  			updateCount++
   447  			debug.Printf("Update: %v", update)
   448  			if update.Cookie != tr.Cookie() {
   449  				t.Fatalf("cookie mismatch request: %v  update: %v", tr.Cookie(), update.Cookie)
   450  			}
   451  			if update.Complete {
   452  				if expected.Errval != 0 {
   453  					if update.Errval != expected.Errval {
   454  						t.Fatalf("Errval expected %v != %v", expected.Errval, update.Errval)
   455  					}
   456  					continue
   457  				}
   458  
   459  				if update.Length != expected.Length {
   460  					t.Fatalf("Length expected %v != %v", expected.Length, update.Length)
   461  				}
   462  			}
   463  		}
   464  
   465  		// The -1 is because End message always happens.
   466  		if updateCount-1 != expected.UpdateCount {
   467  			t.Fatalf("UpdateCount expected %v != %v", expected.UpdateCount, updateCount-1)
   468  		}
   469  	}
   470  }