github.com/swiftstack/proxyfs@v0.0.0-20201223034610-5434d919416e/mkproxyfs/api.go (about)

     1  package mkproxyfs
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net/http"
     7  	"os"
     8  	"strings"
     9  	"time"
    10  
    11  	etcd "go.etcd.io/etcd/clientv3"
    12  
    13  	"github.com/swiftstack/ProxyFS/blunder"
    14  	"github.com/swiftstack/ProxyFS/conf"
    15  	"github.com/swiftstack/ProxyFS/headhunter"
    16  	"github.com/swiftstack/ProxyFS/logger"
    17  	"github.com/swiftstack/ProxyFS/swiftclient"
    18  	"github.com/swiftstack/ProxyFS/transitions"
    19  	"github.com/swiftstack/ProxyFS/version"
    20  )
    21  
    22  type Mode int
    23  
    24  const (
    25  	ModeNew Mode = iota
    26  	ModeOnlyIfNeeded
    27  	ModeReformat
    28  )
    29  
    30  func Format(mode Mode, volumeNameToFormat string, confFile string, confStrings []string, execArgs []string) (err error) {
    31  	var (
    32  		accountName           string
    33  		cancel                context.CancelFunc
    34  		checkpointEtcdKeyName string
    35  		confMap               conf.ConfMap
    36  		containerList         []string
    37  		containerName         string
    38  		ctx                   context.Context
    39  		etcdAutoSyncInterval  time.Duration
    40  		etcdClient            *etcd.Client
    41  		etcdDialTimeout       time.Duration
    42  		etcdEnabled           bool
    43  		etcdEndpoints         []string
    44  		etcdKV                etcd.KV
    45  		etcdOpTimeout         time.Duration
    46  		getResponse           *etcd.GetResponse
    47  		isEmpty               bool
    48  		objectList            []string
    49  		objectName            string
    50  		replayLogFileName     string
    51  		whoAmI                string
    52  	)
    53  
    54  	// Valid mode?
    55  
    56  	switch mode {
    57  	case ModeNew:
    58  	case ModeOnlyIfNeeded:
    59  	case ModeReformat:
    60  	default:
    61  		err = fmt.Errorf("mode (%v) must be one of ModeNew (%v), ModeOnlyIfNeeded (%v), or ModeReformat (%v)", mode, ModeNew, ModeOnlyIfNeeded, ModeReformat)
    62  		return
    63  	}
    64  
    65  	// Load confFile & confStrings (overrides)
    66  
    67  	confMap, err = conf.MakeConfMapFromFile(confFile)
    68  	if nil != err {
    69  		err = fmt.Errorf("failed to load config: %v", err)
    70  		return
    71  	}
    72  
    73  	err = confMap.UpdateFromStrings(confStrings)
    74  	if nil != err {
    75  		err = fmt.Errorf("failed to apply config overrides: %v", err)
    76  		return
    77  	}
    78  
    79  	// Upgrade confMap if necessary
    80  	// [should be removed once backwards compatibility is no longer required...]
    81  
    82  	err = transitions.UpgradeConfMapIfNeeded(confMap)
    83  	if nil != err {
    84  		err = fmt.Errorf("failed to upgrade config: %v", err)
    85  		return
    86  	}
    87  
    88  	// Update confMap to specify an empty FSGlobals.VolumeGroupList
    89  
    90  	err = confMap.UpdateFromString("FSGlobals.VolumeGroupList=")
    91  	if nil != err {
    92  		err = fmt.Errorf("failed to empty config VolumeGroupList: %v", err)
    93  		return
    94  	}
    95  
    96  	// Fetch confMap particulars needed below
    97  
    98  	accountName, err = confMap.FetchOptionValueString("Volume:"+volumeNameToFormat, "AccountName")
    99  	if nil != err {
   100  		return
   101  	}
   102  
   103  	etcdEnabled, err = confMap.FetchOptionValueBool("FSGlobals", "EtcdEnabled")
   104  	if nil != err {
   105  		etcdEnabled = false // Current default
   106  	}
   107  
   108  	if etcdEnabled {
   109  		etcdEndpoints, err = confMap.FetchOptionValueStringSlice("FSGlobals", "EtcdEndpoints")
   110  		if nil != err {
   111  			return
   112  		}
   113  		etcdAutoSyncInterval, err = confMap.FetchOptionValueDuration("FSGlobals", "EtcdAutoSyncInterval")
   114  		if nil != err {
   115  			return
   116  		}
   117  		etcdDialTimeout, err = confMap.FetchOptionValueDuration("FSGlobals", "EtcdDialTimeout")
   118  		if nil != err {
   119  			return
   120  		}
   121  		etcdOpTimeout, err = confMap.FetchOptionValueDuration("FSGlobals", "EtcdOpTimeout")
   122  		if nil != err {
   123  			return
   124  		}
   125  
   126  		checkpointEtcdKeyName, err = confMap.FetchOptionValueString("Volume:"+volumeNameToFormat, "CheckpointEtcdKeyName")
   127  		if nil != err {
   128  			return
   129  		}
   130  
   131  		// Initialize etcd Client & KV objects
   132  
   133  		etcdClient, err = etcd.New(etcd.Config{
   134  			Endpoints:        etcdEndpoints,
   135  			AutoSyncInterval: etcdAutoSyncInterval,
   136  			DialTimeout:      etcdDialTimeout,
   137  		})
   138  		if nil != err {
   139  			return
   140  		}
   141  
   142  		etcdKV = etcd.NewKV(etcdClient)
   143  	}
   144  
   145  	// Call transitions.Up() with empty FSGlobals.VolumeGroupList
   146  
   147  	err = transitions.Up(confMap)
   148  	if nil != err {
   149  		return
   150  	}
   151  
   152  	logger.Infof("mkproxyfs is starting up (version %s) (PID %d); invoked as '%s'",
   153  		version.ProxyFSVersion, os.Getpid(), strings.Join(execArgs, "' '"))
   154  
   155  	// Determine if underlying accountName is empty
   156  
   157  	_, containerList, err = swiftclient.AccountGet(accountName)
   158  	if nil == err {
   159  		// accountName exists (possibly auto-created)... consider it empty only if no containers therein
   160  		isEmpty = (0 == len(containerList))
   161  	} else {
   162  		if http.StatusNotFound == blunder.HTTPCode(err) {
   163  			// accountName does not exist, so accountName is empty
   164  			isEmpty = true
   165  		} else {
   166  			_ = transitions.Down(confMap)
   167  			err = fmt.Errorf("failed to GET %v: %v", accountName, err)
   168  			return
   169  		}
   170  	}
   171  
   172  	// Adjust isEmpty based on etcd
   173  
   174  	if isEmpty && etcdEnabled {
   175  		ctx, cancel = context.WithTimeout(context.Background(), etcdOpTimeout)
   176  		getResponse, err = etcdKV.Get(ctx, checkpointEtcdKeyName)
   177  		cancel()
   178  		if nil != err {
   179  			err = fmt.Errorf("Error contacting etcd [Case 1]: %v", err)
   180  			return
   181  		}
   182  
   183  		if 0 < getResponse.Count {
   184  			isEmpty = false
   185  		}
   186  	}
   187  
   188  	if !isEmpty {
   189  		switch mode {
   190  		case ModeNew:
   191  			// If Swift Account is not empty && ModeNew, exit with failure
   192  
   193  			_ = transitions.Down(confMap)
   194  			err = fmt.Errorf("%v found to be non-empty with mode == ModeNew (%v)", accountName, ModeNew)
   195  			return
   196  		case ModeOnlyIfNeeded:
   197  			// If Swift Account is not empty && ModeOnlyIfNeeded, exit successfully
   198  
   199  			_ = transitions.Down(confMap)
   200  			err = nil
   201  			return
   202  		case ModeReformat:
   203  			// If Swift Account is not empty && ModeReformat, clear out accountName
   204  
   205  			for !isEmpty {
   206  				for _, containerName = range containerList {
   207  					_, objectList, err = swiftclient.ContainerGet(accountName, containerName)
   208  					if nil != err {
   209  						_ = transitions.Down(confMap)
   210  						err = fmt.Errorf("failed to GET %v/%v: %v", accountName, containerName, err)
   211  						return
   212  					}
   213  
   214  					isEmpty = (0 == len(objectList))
   215  
   216  					for !isEmpty {
   217  						for _, objectName = range objectList {
   218  							err = swiftclient.ObjectDelete(accountName, containerName, objectName, 0)
   219  							if nil != err {
   220  								_ = transitions.Down(confMap)
   221  								err = fmt.Errorf("failed to DELETE %v/%v/%v: %v", accountName, containerName, objectName, err)
   222  								return
   223  							}
   224  						}
   225  
   226  						_, objectList, err = swiftclient.ContainerGet(accountName, containerName)
   227  						if nil != err {
   228  							_ = transitions.Down(confMap)
   229  							err = fmt.Errorf("failed to GET %v/%v: %v", accountName, containerName, err)
   230  							return
   231  						}
   232  
   233  						isEmpty = (0 == len(objectList))
   234  					}
   235  
   236  					err = swiftclient.ContainerDelete(accountName, containerName)
   237  					if nil != err {
   238  						_ = transitions.Down(confMap)
   239  						err = fmt.Errorf("failed to DELETE %v/%v: %v", accountName, containerName, err)
   240  						return
   241  					}
   242  				}
   243  
   244  				_, containerList, err = swiftclient.AccountGet(accountName)
   245  				if nil != err {
   246  					_ = transitions.Down(confMap)
   247  					err = fmt.Errorf("failed to GET %v: %v", accountName, err)
   248  					return
   249  				}
   250  
   251  				isEmpty = (0 == len(containerList))
   252  			}
   253  
   254  			// Clear etcd if it exists...
   255  
   256  			if etcdEnabled {
   257  				ctx, cancel = context.WithTimeout(context.Background(), etcdOpTimeout)
   258  				_, err = etcdKV.Delete(ctx, checkpointEtcdKeyName)
   259  				cancel()
   260  				if nil != err {
   261  					err = fmt.Errorf("Error contacting etcd [Case 2]: %v", err)
   262  					return
   263  				}
   264  			}
   265  
   266  			replayLogFileName, err = confMap.FetchOptionValueString("Volume:"+volumeNameToFormat, "ReplayLogFileName")
   267  			if nil == err {
   268  				if "" != replayLogFileName {
   269  					removeReplayLogFileErr := os.Remove(replayLogFileName)
   270  					if nil != removeReplayLogFileErr {
   271  						if !os.IsNotExist(removeReplayLogFileErr) {
   272  							_ = transitions.Down(confMap)
   273  							err = fmt.Errorf("os.Remove(replayLogFileName == \"%v\") returned unexpected error: %v", replayLogFileName, removeReplayLogFileErr)
   274  							return
   275  						}
   276  					}
   277  				}
   278  			}
   279  		}
   280  	}
   281  
   282  	// Call transitions.Down() before restarting to do format
   283  
   284  	err = transitions.Down(confMap)
   285  	if nil != err {
   286  		return
   287  	}
   288  
   289  	// Update confMap to specify only volumeNameToFormat with AuthFormat set to true
   290  
   291  	whoAmI, err = confMap.FetchOptionValueString("Cluster", "WhoAmI")
   292  	if nil != err {
   293  		return
   294  	}
   295  
   296  	err = confMap.UpdateFromStrings([]string{
   297  		"FSGlobals.VolumeGroupList=MKPROXYFS",
   298  		"VolumeGroup:MKPROXYFS.VolumeList=" + volumeNameToFormat,
   299  		"VolumeGroup:MKPROXYFS.VirtualIPAddr=",
   300  		"VolumeGroup:MKPROXYFS.PrimaryPeer=" + whoAmI,
   301  		"Volume:" + volumeNameToFormat + ".AutoFormat=true"})
   302  	if nil != err {
   303  		err = fmt.Errorf("failed to retarget config at only %s: %v", volumeNameToFormat, err)
   304  		return
   305  	}
   306  
   307  	// Restart... this time AutoFormat of volumeNameToFormat will be applied
   308  
   309  	err = transitions.Up(confMap)
   310  	if nil != err {
   311  		return
   312  	}
   313  
   314  	// Make reference to package headhunter to ensure it gets registered with package transitions
   315  
   316  	_, err = headhunter.FetchVolumeHandle(volumeNameToFormat)
   317  	if nil != err {
   318  		return
   319  	}
   320  
   321  	// With format complete, we can shutdown for good
   322  
   323  	err = transitions.Down(confMap)
   324  	if nil != err {
   325  		return
   326  	}
   327  
   328  	// Close down etcd
   329  
   330  	if etcdEnabled {
   331  		etcdKV = nil
   332  
   333  		err = etcdClient.Close()
   334  		if nil != err {
   335  			return
   336  		}
   337  	}
   338  
   339  	return
   340  }