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 }