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  }