github.com/whamcloud/lemur@v0.0.0-20190827193804-4655df8a52af/dmplugin/dmclient.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 dmplugin 6 7 import ( 8 "fmt" 9 "io" 10 "sync" 11 "syscall" 12 13 "github.com/pkg/errors" 14 15 pb "github.com/intel-hpdd/lemur/pdm" 16 "github.com/intel-hpdd/logging/alert" 17 "github.com/intel-hpdd/logging/debug" 18 "golang.org/x/net/context" 19 ) 20 21 type ( 22 // ActionHandler is function that implements one of the commands 23 ActionHandler func(Action) error 24 25 // DataMoverClient is the data mover client to the HSM agent 26 DataMoverClient struct { 27 plugin *Plugin 28 rpcClient pb.DataMoverClient 29 status chan *pb.ActionStatus 30 mover Mover 31 config *Config 32 actions map[pb.Command]ActionHandler 33 } 34 35 // Config defines configuration for a DatamMoverClient 36 Config struct { 37 Mover Mover 38 NumThreads int 39 ArchiveID uint32 40 } 41 42 // Action is a data movement action 43 dmAction struct { 44 status chan *pb.ActionStatus 45 item *pb.ActionItem 46 actualLength *int64 47 uuid string 48 hash []byte 49 url string 50 } 51 52 // Action defines an interface for dm actions 53 Action interface { 54 // Update sends an action status update 55 Update(offset, length, max int64) error 56 // ID returns the action item's ID 57 ID() uint64 58 // Offset returns the current offset of the action item 59 Offset() int64 60 // Length returns the expected length of the action item's file 61 Length() int64 62 // Data returns a byte slice of the action item's data 63 Data() []byte 64 // PrimaryPath returns the action item's primary file path 65 PrimaryPath() string 66 67 // WritePath returns the action item's write path (e.g. for restores) 68 WritePath() string 69 70 // UUID returns the action item's file id 71 UUID() string 72 73 // Hash returns the action item's file id 74 Hash() []byte 75 76 // URL returns the action item's file id 77 URL() string 78 79 // SetUUID sets the action's file id 80 SetUUID(id string) 81 82 // SetHash sets the action's file id 83 SetHash(hash []byte) 84 85 // SetURL sets the action's file id 86 SetURL(id string) 87 88 // SetActualLength sets the action's actual file length 89 SetActualLength(length int64) 90 } 91 92 // Mover defines an interface for data mover implementations 93 Mover interface { 94 Start() 95 } 96 97 // Archiver defines an interface for data movers capable of 98 // fulfilling Archive requests 99 Archiver interface { 100 Archive(Action) error 101 } 102 103 // Restorer defines an interface for data movers capable of 104 // fulfilling Restore requests 105 Restorer interface { 106 Restore(Action) error 107 } 108 109 // Remover defines an interface for data movers capable of 110 // fulfilling Remove requests 111 Remover interface { 112 Remove(Action) error 113 } 114 ) 115 116 type key int 117 118 var handleKey key 119 120 const ( 121 defaultNumThreads = 4 122 ) 123 124 func withHandle(ctx context.Context, handle *pb.Handle) context.Context { 125 return context.WithValue(ctx, handleKey, handle) 126 } 127 128 func getHandle(ctx context.Context) (*pb.Handle, bool) { 129 handle, ok := ctx.Value(handleKey).(*pb.Handle) 130 return handle, ok 131 } 132 133 func (a *dmAction) String() string { 134 return fmt.Sprintf("%v uuid:'%s' actualSize:%v", a.item, a.uuid, a.actualLength) 135 } 136 137 // Update sends an action status update 138 func (a *dmAction) Update(offset, length, max int64) error { 139 a.status <- &pb.ActionStatus{ 140 Id: a.item.Id, 141 Offset: offset, 142 Length: length, 143 } 144 return nil 145 } 146 147 // Finish finalizes the action. 148 func (a *dmAction) Finish(err error) { 149 if err != nil { 150 a.fail(err) 151 } else { 152 a.complete() 153 } 154 } 155 156 // Complete signals that the action has completed 157 func (a *dmAction) complete() error { 158 status := &pb.ActionStatus{ 159 Id: a.item.Id, 160 Completed: true, 161 Offset: a.item.Offset, 162 Length: a.item.Length, 163 Uuid: a.uuid, 164 Hash: a.hash, 165 Url: a.url, 166 } 167 if a.actualLength != nil { 168 status.Length = *a.actualLength 169 } 170 a.status <- status 171 return nil 172 } 173 174 func getErrno(err error) int32 { 175 if errno, ok := err.(syscall.Errno); ok { 176 return int32(errno) 177 } 178 return -1 179 } 180 181 // Fail signals that the action has failed 182 func (a *dmAction) fail(err error) error { 183 alert.Warnf("fail: id:%d %v", a.item.Id, err) 184 a.status <- &pb.ActionStatus{ 185 Id: a.item.Id, 186 Completed: true, 187 188 Error: getErrno(err), 189 } 190 return nil 191 } 192 193 // ID returns the action item's ID 194 func (a *dmAction) ID() uint64 { 195 return a.item.Id 196 } 197 198 // Offset returns the current offset of the action item 199 func (a *dmAction) Offset() int64 { 200 return a.item.Offset 201 } 202 203 // Length returns the expected length of the action item's file 204 func (a *dmAction) Length() int64 { 205 return a.item.Length 206 } 207 208 // Data returns a byte slice of the action item's data 209 func (a *dmAction) Data() []byte { 210 return a.item.Data 211 } 212 213 // PrimaryPath returns the action item's primary file path 214 func (a *dmAction) PrimaryPath() string { 215 return a.item.PrimaryPath 216 } 217 218 // WritePath returns the action item's write path (e.g. for restores) 219 func (a *dmAction) WritePath() string { 220 return a.item.WritePath 221 } 222 223 // UUID returns the action item's file id 224 func (a *dmAction) UUID() string { 225 return a.item.Uuid 226 } 227 228 // Hash returns the action item's file id 229 func (a *dmAction) Hash() []byte { 230 return a.item.Hash 231 } 232 233 // URL returns the action item's file id 234 func (a *dmAction) URL() string { 235 return a.item.Url 236 } 237 238 // SetUUID sets the action's file uuid 239 func (a *dmAction) SetUUID(id string) { 240 a.uuid = id 241 } 242 243 // SetHash sets the action's file hash 244 func (a *dmAction) SetHash(h []byte) { 245 a.hash = h 246 } 247 248 // SetURL sets the action's file id 249 func (a *dmAction) SetURL(u string) { 250 a.url = u 251 } 252 253 // SetActualLength sets the action's actual file length 254 func (a *dmAction) SetActualLength(length int64) { 255 a.actualLength = &length 256 } 257 258 // NewMover returns a new *DataMoverClient 259 func NewMover(plugin *Plugin, cli pb.DataMoverClient, config *Config) *DataMoverClient { 260 actions := make(map[pb.Command]ActionHandler) 261 262 if archiver, ok := config.Mover.(Archiver); ok { 263 actions[pb.Command_ARCHIVE] = archiver.Archive 264 } 265 if restorer, ok := config.Mover.(Restorer); ok { 266 actions[pb.Command_RESTORE] = restorer.Restore 267 } 268 if remover, ok := config.Mover.(Remover); ok { 269 actions[pb.Command_REMOVE] = remover.Remove 270 } 271 272 return &DataMoverClient{ 273 plugin: plugin, 274 rpcClient: cli, 275 mover: config.Mover, 276 status: make(chan *pb.ActionStatus, config.NumThreads), 277 config: config, 278 actions: actions, 279 } 280 } 281 282 // Run begins listening for and processing incoming action items 283 func (dm *DataMoverClient) Run(ctx context.Context) { 284 var wg sync.WaitGroup 285 286 handle, err := dm.registerEndpoint(ctx) 287 if err != nil { 288 alert.Abort(errors.Wrap(err, "register endpoint failed")) 289 } 290 ctx = withHandle(ctx, handle) 291 actions := dm.processActions(ctx) 292 dm.processStatus(ctx) 293 294 n := defaultNumThreads 295 if dm.config.NumThreads > 0 { 296 n = dm.config.NumThreads 297 } 298 299 for i := 0; i < n; i++ { 300 wg.Add(1) 301 go func(i int) { 302 dm.handler(fmt.Sprintf("handler-%d", i), actions) 303 wg.Done() 304 }(i) 305 } 306 307 // Signal to the mover that it should begin any async processing 308 dm.config.Mover.Start() 309 310 wg.Wait() 311 debug.Printf("Shutting down Data Mover") 312 close(dm.status) 313 } 314 315 func (dm *DataMoverClient) registerEndpoint(ctx context.Context) (*pb.Handle, error) { 316 317 handle, err := dm.rpcClient.Register(ctx, &pb.Endpoint{ 318 FsUrl: dm.plugin.FsName(), 319 Archive: dm.config.ArchiveID, 320 }) 321 if err != nil { 322 return nil, err 323 } 324 debug.Printf("Registered archive %d, cookie %x", dm.config.ArchiveID, handle.Id) 325 return handle, nil 326 } 327 328 func (dm *DataMoverClient) processActions(ctx context.Context) chan *pb.ActionItem { 329 actions := make(chan *pb.ActionItem) 330 331 go func() { 332 defer close(actions) 333 handle, ok := getHandle(ctx) 334 if !ok { 335 alert.Warn(errors.New("No context")) 336 return 337 } 338 stream, err := dm.rpcClient.GetActions(ctx, handle) 339 if err != nil { 340 alert.Warn(errors.Wrap(err, "GetActions() failed")) 341 return 342 } 343 for { 344 action, err := stream.Recv() 345 if err != nil { 346 if err == io.EOF { 347 debug.Print("Shutting down dmclient action stream") 348 return 349 } 350 alert.Warnf("Shutting down dmclient action stream due to error on Recv(): %v", err) 351 return 352 } 353 // debug.Printf("Got message id:%d op: %v %v", action.Id, action.Op, action.PrimaryPath) 354 355 actions <- action 356 } 357 358 }() 359 360 return actions 361 362 } 363 364 func (dm *DataMoverClient) processStatus(ctx context.Context) { 365 go func() { 366 handle, ok := getHandle(ctx) 367 if !ok { 368 alert.Abort(errors.New("No context")) 369 } 370 acks, err := dm.rpcClient.StatusStream(ctx) 371 if err != nil { 372 alert.Warn(errors.Wrap(err, "StatusStream() failed")) 373 return 374 } 375 for reply := range dm.status { 376 reply.Handle = handle 377 // debug.Printf("Sent reply %x error: %#v", reply.Id, reply.Error) 378 err := acks.Send(reply) 379 if err != nil { 380 alert.Warn(errors.Wrapf(err, "Failed to ack message %x", reply.Id)) 381 return 382 } 383 } 384 }() 385 return 386 } 387 388 // getActionHandler returns the mover's action function for the comamnd, or err 389 // if there is no handler for that command. 390 func (dm *DataMoverClient) getActionHandler(op pb.Command) (ActionHandler, error) { 391 fn, ok := dm.actions[op] 392 if !ok { 393 return nil, errors.New("Command not supported") 394 } 395 return fn, nil 396 } 397 398 func (dm *DataMoverClient) handler(name string, actions chan *pb.ActionItem) { 399 for item := range actions { 400 action := &dmAction{ 401 status: dm.status, 402 item: item, 403 } 404 405 actionFn, err := dm.getActionHandler(item.Op) 406 if err == nil { 407 err = actionFn(action) 408 } 409 // debug.Printf("completed (action: %v) %v ", action, ret) 410 action.Finish(err) 411 } 412 debug.Printf("%s: stopping", name) 413 }