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 }