github.com/whamcloud/lemur@v0.0.0-20190827193804-4655df8a52af/cmd/lhsmd/agent/mountpoints.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 agent
     6  
     7  import (
     8  	"fmt"
     9  	"os"
    10  	"time"
    11  
    12  	"github.com/pkg/errors"
    13  
    14  	"golang.org/x/net/context"
    15  	"golang.org/x/sys/unix"
    16  
    17  	"github.com/intel-hpdd/logging/audit"
    18  	"github.com/intel-hpdd/logging/debug"
    19  	"github.com/intel-hpdd/go-lustre/pkg/mntent"
    20  )
    21  
    22  // UnmountTimeout is the time, in seconds, that an unmount will be retried
    23  // before failing with an error.
    24  const UnmountTimeout = 10
    25  
    26  type (
    27  	mountConfig struct {
    28  		Device    string
    29  		Directory string
    30  		Type      string
    31  		Options   clientMountOptions
    32  		Flags     uintptr
    33  	}
    34  )
    35  
    36  func (mc *mountConfig) String() string {
    37  	return fmt.Sprintf("%s %s %s %s (%d)", mc.Device, mc.Directory, mc.Type, mc.Options, mc.Flags)
    38  }
    39  
    40  func mountClient(cfg *mountConfig) error {
    41  	if err := os.MkdirAll(cfg.Directory, 0700); err != nil {
    42  		return errors.Wrap(err, "mkdir failed")
    43  	}
    44  
    45  	return unix.Mount(cfg.Device, cfg.Directory, cfg.Type, cfg.Flags, cfg.Options.String())
    46  }
    47  
    48  func createMountConfigs(cfg *Config) []*mountConfig {
    49  	device := cfg.ClientDevice.String()
    50  	// this is what mount_lustre.c does...
    51  	opts := append(cfg.ClientMountOptions, "device="+device)
    52  
    53  	var flags uintptr
    54  	// LU-1783 -- force strictatime until a kernel vfs bug is fixed
    55  	flags |= unix.MS_STRICTATIME
    56  
    57  	// Create the agent mountpoint first, then add per-plugin mountpoints
    58  	configs := []*mountConfig{
    59  		&mountConfig{
    60  			Device:    device,
    61  			Directory: cfg.AgentMountpoint(),
    62  			Type:      "lustre",
    63  			Options:   opts,
    64  			Flags:     flags,
    65  		},
    66  	}
    67  
    68  	for _, plugin := range cfg.Plugins() {
    69  		configs = append(configs, &mountConfig{
    70  			Device:    device,
    71  			Directory: plugin.ClientMount,
    72  			Type:      "lustre",
    73  			Options:   opts,
    74  			Flags:     flags,
    75  		})
    76  	}
    77  
    78  	return configs
    79  }
    80  
    81  // ConfigureMounts configures a set of Lustre client mounts; one for the agent
    82  // and one for each configure data mover.
    83  func ConfigureMounts(cfg *Config) error {
    84  	entries, err := mntent.GetMounted()
    85  	if err != nil {
    86  		return errors.Wrap(err, "failed to get list of mounted filesystems")
    87  	}
    88  
    89  	for _, mc := range createMountConfigs(cfg) {
    90  		if _, err := entries.ByDir(mc.Directory); err == nil {
    91  			continue
    92  		}
    93  
    94  		debug.Printf("Mounting client at %s", mc.Directory)
    95  		if err := mountClient(mc); err != nil {
    96  			return errors.Wrap(err, "mount client failed")
    97  		}
    98  	}
    99  
   100  	return nil
   101  }
   102  
   103  func doTimedUnmount(dir string) error {
   104  	done := make(chan struct{})
   105  	lastError := make(chan error)
   106  
   107  	// This feels a little baroque, but it accomplishes two goals:
   108  	// 1) Don't leak this goroutine if we time out
   109  	// 2) Make sure that we safely get an error from unix.Unmount
   110  	//    back out to the caller
   111  	ctx, cancel := context.WithCancel(context.Background())
   112  	go func(ctx context.Context) {
   113  		var err error
   114  		for {
   115  			select {
   116  			case <-ctx.Done():
   117  				lastError <- err
   118  				return
   119  			default:
   120  				err = unix.Unmount(dir, 0)
   121  				if err == nil {
   122  					close(done)
   123  					lastError <- err
   124  					return
   125  				}
   126  				audit.Logf("Waiting for %s to be unmounted", dir)
   127  				time.Sleep(1 * time.Second)
   128  			}
   129  		}
   130  	}(ctx)
   131  
   132  	for {
   133  		select {
   134  		case <-done:
   135  			return <-lastError
   136  		case <-time.After(time.Duration(UnmountTimeout) * time.Second):
   137  			cancel()
   138  			return errors.Wrapf(<-lastError, "Unmount of %s timed out after %d seconds", dir, UnmountTimeout)
   139  		}
   140  	}
   141  }
   142  
   143  // CleanupMounts unmounts the Lustre client mounts configured by
   144  // ConfigureMounts().
   145  func CleanupMounts(cfg *Config) error {
   146  	entries, err := mntent.GetMounted()
   147  	if err != nil {
   148  		return errors.Wrap(err, "failed to get list of mounted filesystems")
   149  	}
   150  
   151  	// Reverse the generated slice to perform mover unmounts first,
   152  	// finishing with the agent unmount.
   153  	mcList := createMountConfigs(cfg)
   154  	revList := make([]*mountConfig, len(mcList))
   155  	for i := range mcList {
   156  		revList[i] = mcList[len(mcList)-1-i]
   157  	}
   158  
   159  	for _, mc := range revList {
   160  		if _, err := entries.ByDir(mc.Directory); err != nil {
   161  			continue
   162  		}
   163  
   164  		debug.Printf("Cleaning up %s", mc.Directory)
   165  		if err := doTimedUnmount(mc.Directory); err != nil {
   166  			return errors.Wrapf(err, "Failed to unmount %s", mc.Directory)
   167  		}
   168  	}
   169  
   170  	return nil
   171  }