github.com/swiftstack/proxyfs@v0.0.0-20201223034610-5434d919416e/httpserver/request_handler.go (about) 1 package httpserver 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io/ioutil" 8 "math" 9 "net/http" 10 "net/http/pprof" 11 "net/url" 12 "runtime" 13 "strconv" 14 "strings" 15 "time" 16 17 "github.com/swiftstack/sortedmap" 18 19 "github.com/swiftstack/ProxyFS/bucketstats" 20 "github.com/swiftstack/ProxyFS/fs" 21 "github.com/swiftstack/ProxyFS/halter" 22 "github.com/swiftstack/ProxyFS/headhunter" 23 "github.com/swiftstack/ProxyFS/inode" 24 "github.com/swiftstack/ProxyFS/jrpcfs" 25 "github.com/swiftstack/ProxyFS/liveness" 26 "github.com/swiftstack/ProxyFS/logger" 27 "github.com/swiftstack/ProxyFS/stats" 28 "github.com/swiftstack/ProxyFS/utils" 29 "github.com/swiftstack/ProxyFS/version" 30 ) 31 32 type httpRequestHandler struct{} 33 34 type requestStateStruct struct { 35 pathSplit []string 36 numPathParts int 37 formatResponseAsJSON bool 38 formatResponseCompactly bool 39 performValidation bool 40 percentRange string 41 startNonce uint64 42 volume *volumeStruct 43 } 44 45 func serveHTTP() { 46 _ = http.Serve(globals.netListener, httpRequestHandler{}) 47 globals.wg.Done() 48 } 49 50 func (h httpRequestHandler) ServeHTTP(responseWriter http.ResponseWriter, request *http.Request) { 51 globals.Lock() 52 if globals.active { 53 switch request.Method { 54 case http.MethodDelete: 55 doDelete(responseWriter, request) 56 case http.MethodGet: 57 doGet(responseWriter, request) 58 case http.MethodPost: 59 doPost(responseWriter, request) 60 case http.MethodPut: 61 doPut(responseWriter, request) 62 default: 63 responseWriter.WriteHeader(http.StatusMethodNotAllowed) 64 } 65 } else { 66 responseWriter.WriteHeader(http.StatusServiceUnavailable) 67 } 68 globals.Unlock() 69 } 70 71 func doDelete(responseWriter http.ResponseWriter, request *http.Request) { 72 switch { 73 case strings.HasPrefix(request.URL.Path, "/volume"): 74 doDeleteOfVolume(responseWriter, request) 75 default: 76 responseWriter.WriteHeader(http.StatusNotFound) 77 } 78 } 79 80 func doDeleteOfVolume(responseWriter http.ResponseWriter, request *http.Request) { 81 var ( 82 err error 83 numPathParts int 84 ok bool 85 pathSplit []string 86 snapShotID uint64 87 volume *volumeStruct 88 volumeAsValue sortedmap.Value 89 volumeName string 90 ) 91 92 pathSplit = strings.Split(request.URL.Path, "/") // leading "/" places "" in pathSplit[0] 93 // pathSplit[1] should be "volume" based on how we got here 94 // trailing "/" places "" in pathSplit[len(pathSplit)-1] 95 numPathParts = len(pathSplit) - 1 96 if "" == pathSplit[numPathParts] { 97 numPathParts-- 98 } 99 100 if "volume" != pathSplit[1] { 101 responseWriter.WriteHeader(http.StatusNotFound) 102 return 103 } 104 105 if 4 != numPathParts { 106 responseWriter.WriteHeader(http.StatusNotFound) 107 return 108 } 109 110 volumeName = pathSplit[2] 111 112 volumeAsValue, ok, err = globals.volumeLLRB.GetByKey(volumeName) 113 if nil != err { 114 logger.Fatalf("HTTP Server Logic Error: %v", err) 115 } 116 if !ok { 117 responseWriter.WriteHeader(http.StatusNotFound) 118 return 119 } 120 volume = volumeAsValue.(*volumeStruct) 121 122 if "snapshot" != pathSplit[3] { 123 responseWriter.WriteHeader(http.StatusNotFound) 124 return 125 } 126 127 // Form: /volume/<volume-name>/snapshot/<snapshot-id> 128 129 snapShotID, err = strconv.ParseUint(pathSplit[4], 10, 64) 130 if nil != err { 131 responseWriter.WriteHeader(http.StatusNotFound) 132 return 133 } 134 135 err = volume.inodeVolumeHandle.SnapShotDelete(snapShotID) 136 if nil == err { 137 responseWriter.WriteHeader(http.StatusNoContent) 138 } else { 139 responseWriter.WriteHeader(http.StatusNotFound) 140 } 141 } 142 143 func doGet(responseWriter http.ResponseWriter, request *http.Request) { 144 path := strings.TrimRight(request.URL.Path, "/") 145 146 switch { 147 case "" == path: 148 doGetOfIndexDotHTML(responseWriter, request) 149 case "/bootstrap.min.css" == path: 150 responseWriter.Header().Set("Content-Type", bootstrapDotCSSContentType) 151 responseWriter.WriteHeader(http.StatusOK) 152 _, _ = responseWriter.Write([]byte(bootstrapDotCSSContent)) 153 case "/bootstrap.min.js" == path: 154 responseWriter.Header().Set("Content-Type", bootstrapDotJSContentType) 155 responseWriter.WriteHeader(http.StatusOK) 156 _, _ = responseWriter.Write([]byte(bootstrapDotJSContent)) 157 case "/config" == path: 158 doGetOfConfig(responseWriter, request) 159 case "/index.html" == path: 160 doGetOfIndexDotHTML(responseWriter, request) 161 case "/jquery.min.js" == path: 162 responseWriter.Header().Set("Content-Type", jqueryDotJSContentType) 163 responseWriter.WriteHeader(http.StatusOK) 164 _, _ = responseWriter.Write([]byte(jqueryDotJSContent)) 165 case "/jsontree.js" == path: 166 responseWriter.Header().Set("Content-Type", jsontreeDotJSContentType) 167 responseWriter.WriteHeader(http.StatusOK) 168 _, _ = responseWriter.Write([]byte(jsontreeDotJSContent)) 169 case "/liveness" == path: 170 doGetOfLiveness(responseWriter, request) 171 case "/metrics" == path: 172 doGetOfMetrics(responseWriter, request) 173 case "/stats" == path: 174 doGetOfStats(responseWriter, request) 175 case "/debug/pprof/cmdline" == path: 176 pprof.Cmdline(responseWriter, request) 177 case "/debug/pprof/profile" == path: 178 pprof.Profile(responseWriter, request) 179 case "/debug/pprof/symbol" == path: 180 pprof.Symbol(responseWriter, request) 181 case "/debug/pprof/trace" == path: 182 pprof.Trace(responseWriter, request) 183 case strings.HasPrefix(request.URL.Path, "/debug/pprof"): 184 pprof.Index(responseWriter, request) 185 case "/open-iconic/font/css/open-iconic-bootstrap.min.css" == path: 186 responseWriter.Header().Set("Content-Type", openIconicBootstrapDotCSSContentType) 187 responseWriter.WriteHeader(http.StatusOK) 188 _, _ = responseWriter.Write([]byte(openIconicBootstrapDotCSSContent)) 189 case "/open-iconic/font/fonts/open-iconic.eot" == path: 190 responseWriter.Header().Set("Content-Type", openIconicDotEOTContentType) 191 responseWriter.WriteHeader(http.StatusOK) 192 _, _ = responseWriter.Write(openIconicDotEOTContent) 193 case "/open-iconic/font/fonts/open-iconic.otf" == path: 194 responseWriter.Header().Set("Content-Type", openIconicDotOTFContentType) 195 responseWriter.WriteHeader(http.StatusOK) 196 _, _ = responseWriter.Write(openIconicDotOTFContent) 197 case "/open-iconic/font/fonts/open-iconic.svg" == path: 198 responseWriter.Header().Set("Content-Type", openIconicDotSVGContentType) 199 responseWriter.WriteHeader(http.StatusOK) 200 _, _ = responseWriter.Write([]byte(openIconicDotSVGContent)) 201 case "/open-iconic/font/fonts/open-iconic.ttf" == path: 202 responseWriter.Header().Set("Content-Type", openIconicDotTTFContentType) 203 responseWriter.WriteHeader(http.StatusOK) 204 _, _ = responseWriter.Write(openIconicDotTTFContent) 205 case "/open-iconic/font/fonts/open-iconic.woff" == path: 206 responseWriter.Header().Set("Content-Type", openIconicDotWOFFContentType) 207 responseWriter.WriteHeader(http.StatusOK) 208 _, _ = responseWriter.Write(openIconicDotWOFFContent) 209 case "/popper.min.js" == path: 210 responseWriter.Header().Set("Content-Type", popperDotJSContentType) 211 responseWriter.WriteHeader(http.StatusOK) 212 _, _ = responseWriter.Write(popperDotJSContent) 213 case "/styles.css" == path: 214 responseWriter.Header().Set("Content-Type", stylesDotCSSContentType) 215 responseWriter.WriteHeader(http.StatusOK) 216 _, _ = responseWriter.Write([]byte(stylesDotCSSContent)) 217 case "/version" == path: 218 responseWriter.Header().Set("Content-Type", "text/plain") 219 responseWriter.WriteHeader(http.StatusOK) 220 _, _ = responseWriter.Write([]byte(version.ProxyFSVersion)) 221 case strings.HasPrefix(request.URL.Path, "/trigger"): 222 doGetOfTrigger(responseWriter, request) 223 case strings.HasPrefix(request.URL.Path, "/volume"): 224 doGetOfVolume(responseWriter, request) 225 default: 226 responseWriter.WriteHeader(http.StatusNotFound) 227 } 228 } 229 230 func doGetOfIndexDotHTML(responseWriter http.ResponseWriter, request *http.Request) { 231 responseWriter.Header().Set("Content-Type", "text/html") 232 responseWriter.WriteHeader(http.StatusOK) 233 _, _ = responseWriter.Write([]byte(fmt.Sprintf(indexDotHTMLTemplate, version.ProxyFSVersion, globals.ipAddrTCPPort))) 234 } 235 236 func doGetOfConfig(responseWriter http.ResponseWriter, request *http.Request) { 237 var ( 238 acceptHeader string 239 confMapJSON bytes.Buffer 240 confMapJSONPacked []byte 241 formatResponseAsJSON bool 242 ok bool 243 paramList []string 244 sendPackedConfig bool 245 ) 246 247 paramList, ok = request.URL.Query()["compact"] 248 if ok { 249 if 0 == len(paramList) { 250 sendPackedConfig = false 251 } else { 252 sendPackedConfig = !((paramList[0] == "") || (paramList[0] == "0") || (paramList[0] == "false")) 253 } 254 } else { 255 sendPackedConfig = false 256 } 257 258 acceptHeader = request.Header.Get("Accept") 259 260 if strings.Contains(acceptHeader, "application/json") { 261 formatResponseAsJSON = true 262 } else if strings.Contains(acceptHeader, "text/html") { 263 formatResponseAsJSON = false 264 } else if strings.Contains(acceptHeader, "*/*") { 265 formatResponseAsJSON = true 266 } else if strings.Contains(acceptHeader, "") { 267 formatResponseAsJSON = true 268 } else { 269 responseWriter.WriteHeader(http.StatusNotAcceptable) 270 return 271 } 272 273 confMapJSONPacked, _ = json.Marshal(globals.confMap) 274 275 if formatResponseAsJSON { 276 responseWriter.Header().Set("Content-Type", "application/json") 277 responseWriter.WriteHeader(http.StatusOK) 278 279 if sendPackedConfig { 280 _, _ = responseWriter.Write(confMapJSONPacked) 281 } else { 282 json.Indent(&confMapJSON, confMapJSONPacked, "", "\t") 283 _, _ = responseWriter.Write(confMapJSON.Bytes()) 284 _, _ = responseWriter.Write([]byte("\n")) 285 } 286 } else { 287 responseWriter.Header().Set("Content-Type", "text/html") 288 responseWriter.WriteHeader(http.StatusOK) 289 290 _, _ = responseWriter.Write([]byte(fmt.Sprintf(configTemplate, version.ProxyFSVersion, globals.ipAddrTCPPort, utils.ByteSliceToString(confMapJSONPacked)))) 291 } 292 } 293 294 func doGetOfLiveness(responseWriter http.ResponseWriter, request *http.Request) { 295 var ( 296 livenessReportAsJSON bytes.Buffer 297 livenessReportAsJSONPacked []byte 298 livenessReportAsStruct *liveness.LivenessReportStruct 299 ok bool 300 paramList []string 301 sendPackedReport bool 302 ) 303 304 // TODO: For now, assume JSON reponse requested 305 306 livenessReportAsStruct = liveness.FetchLivenessReport() 307 308 if nil == livenessReportAsStruct { 309 responseWriter.WriteHeader(http.StatusServiceUnavailable) 310 return 311 } 312 313 livenessReportAsJSONPacked, _ = json.Marshal(livenessReportAsStruct) 314 315 responseWriter.Header().Set("Content-Type", "application/json") 316 responseWriter.WriteHeader(http.StatusOK) 317 318 paramList, ok = request.URL.Query()["compact"] 319 if ok { 320 if 0 == len(paramList) { 321 sendPackedReport = false 322 } else { 323 sendPackedReport = !((paramList[0] == "") || (paramList[0] == "0") || (paramList[0] == "false")) 324 } 325 } else { 326 sendPackedReport = false 327 } 328 329 if sendPackedReport { 330 _, _ = responseWriter.Write(livenessReportAsJSONPacked) 331 } else { 332 json.Indent(&livenessReportAsJSON, livenessReportAsJSONPacked, "", "\t") 333 _, _ = responseWriter.Write(livenessReportAsJSON.Bytes()) 334 _, _ = responseWriter.Write([]byte("\n")) 335 } 336 } 337 338 func doGetOfMetrics(responseWriter http.ResponseWriter, request *http.Request) { 339 var ( 340 acceptHeader string 341 err error 342 formatResponseAsHTML bool 343 formatResponseAsJSON bool 344 i int 345 memStats runtime.MemStats 346 metricKey string 347 metricValueAsString string 348 metricValueAsUint64 uint64 349 metricsJSON bytes.Buffer 350 metricsJSONPacked []byte 351 metricsLLRB sortedmap.LLRBTree 352 metricsMap map[string]uint64 353 ok bool 354 paramList []string 355 pauseNsAccumulator uint64 356 sendPackedMetrics bool 357 statKey string 358 statValue uint64 359 statsMap map[string]uint64 360 ) 361 362 runtime.ReadMemStats(&memStats) 363 364 metricsMap = make(map[string]uint64) 365 366 // General statistics. 367 metricsMap["go_runtime_MemStats_Alloc"] = memStats.Alloc 368 metricsMap["go_runtime_MemStats_TotalAlloc"] = memStats.TotalAlloc 369 metricsMap["go_runtime_MemStats_Sys"] = memStats.Sys 370 metricsMap["go_runtime_MemStats_Lookups"] = memStats.Lookups 371 metricsMap["go_runtime_MemStats_Mallocs"] = memStats.Mallocs 372 metricsMap["go_runtime_MemStats_Frees"] = memStats.Frees 373 374 // Main allocation heap statistics. 375 metricsMap["go_runtime_MemStats_HeapAlloc"] = memStats.HeapAlloc 376 metricsMap["go_runtime_MemStats_HeapSys"] = memStats.HeapSys 377 metricsMap["go_runtime_MemStats_HeapIdle"] = memStats.HeapIdle 378 metricsMap["go_runtime_MemStats_HeapInuse"] = memStats.HeapInuse 379 metricsMap["go_runtime_MemStats_HeapReleased"] = memStats.HeapReleased 380 metricsMap["go_runtime_MemStats_HeapObjects"] = memStats.HeapObjects 381 382 // Low-level fixed-size structure allocator statistics. 383 // Inuse is bytes used now. 384 // Sys is bytes obtained from system. 385 metricsMap["go_runtime_MemStats_StackInuse"] = memStats.StackInuse 386 metricsMap["go_runtime_MemStats_StackSys"] = memStats.StackSys 387 metricsMap["go_runtime_MemStats_MSpanInuse"] = memStats.MSpanInuse 388 metricsMap["go_runtime_MemStats_MSpanSys"] = memStats.MSpanSys 389 metricsMap["go_runtime_MemStats_MCacheInuse"] = memStats.MCacheInuse 390 metricsMap["go_runtime_MemStats_MCacheSys"] = memStats.MCacheSys 391 metricsMap["go_runtime_MemStats_BuckHashSys"] = memStats.BuckHashSys 392 metricsMap["go_runtime_MemStats_GCSys"] = memStats.GCSys 393 metricsMap["go_runtime_MemStats_OtherSys"] = memStats.OtherSys 394 395 // Garbage collector statistics (fixed portion). 396 metricsMap["go_runtime_MemStats_LastGC"] = memStats.LastGC 397 metricsMap["go_runtime_MemStats_PauseTotalNs"] = memStats.PauseTotalNs 398 metricsMap["go_runtime_MemStats_NumGC"] = uint64(memStats.NumGC) 399 metricsMap["go_runtime_MemStats_GCCPUPercentage"] = uint64(100.0 * memStats.GCCPUFraction) 400 401 // Garbage collector statistics (go_runtime_MemStats_PauseAverageNs). 402 if 0 == memStats.NumGC { 403 metricsMap["go_runtime_MemStats_PauseAverageNs"] = 0 404 } else { 405 pauseNsAccumulator = 0 406 if memStats.NumGC < 255 { 407 for i = 0; i < int(memStats.NumGC); i++ { 408 pauseNsAccumulator += memStats.PauseNs[i] 409 } 410 metricsMap["go_runtime_MemStats_PauseAverageNs"] = pauseNsAccumulator / uint64(memStats.NumGC) 411 } else { 412 for i = 0; i < 256; i++ { 413 pauseNsAccumulator += memStats.PauseNs[i] 414 } 415 metricsMap["go_runtime_MemStats_PauseAverageNs"] = pauseNsAccumulator / 256 416 } 417 } 418 419 statsMap = stats.Dump() 420 421 for statKey, statValue = range statsMap { 422 metricKey = strings.Replace(statKey, ".", "_", -1) 423 metricKey = strings.Replace(metricKey, "-", "_", -1) 424 metricsMap[metricKey] = statValue 425 } 426 427 acceptHeader = request.Header.Get("Accept") 428 429 if strings.Contains(acceptHeader, "application/json") { 430 formatResponseAsHTML = false 431 formatResponseAsJSON = true 432 } else if strings.Contains(acceptHeader, "text/html") { 433 formatResponseAsHTML = true 434 formatResponseAsJSON = true 435 } else if strings.Contains(acceptHeader, "text/plain") { 436 formatResponseAsHTML = false 437 formatResponseAsJSON = false 438 } else if strings.Contains(acceptHeader, "*/*") { 439 formatResponseAsHTML = false 440 formatResponseAsJSON = true 441 } else if strings.Contains(acceptHeader, "") { 442 formatResponseAsHTML = false 443 formatResponseAsJSON = true 444 } else { 445 responseWriter.WriteHeader(http.StatusNotAcceptable) 446 return 447 } 448 449 if formatResponseAsJSON { 450 metricsJSONPacked, _ = json.Marshal(metricsMap) 451 if formatResponseAsHTML { 452 responseWriter.Header().Set("Content-Type", "text/html") 453 responseWriter.WriteHeader(http.StatusOK) 454 455 _, _ = responseWriter.Write([]byte(fmt.Sprintf(metricsTemplate, version.ProxyFSVersion, globals.ipAddrTCPPort, utils.ByteSliceToString(metricsJSONPacked)))) 456 } else { 457 responseWriter.Header().Set("Content-Type", "application/json") 458 responseWriter.WriteHeader(http.StatusOK) 459 460 paramList, ok = request.URL.Query()["compact"] 461 if ok { 462 if 0 == len(paramList) { 463 sendPackedMetrics = false 464 } else { 465 sendPackedMetrics = !((paramList[0] == "") || (paramList[0] == "0") || (paramList[0] == "false")) 466 } 467 } else { 468 sendPackedMetrics = false 469 } 470 471 if sendPackedMetrics { 472 _, _ = responseWriter.Write(metricsJSONPacked) 473 } else { 474 json.Indent(&metricsJSON, metricsJSONPacked, "", "\t") 475 _, _ = responseWriter.Write(metricsJSON.Bytes()) 476 _, _ = responseWriter.Write([]byte("\n")) 477 } 478 } 479 } else { 480 metricsLLRB = sortedmap.NewLLRBTree(sortedmap.CompareString, nil) 481 482 for metricKey, metricValueAsUint64 = range metricsMap { 483 metricValueAsString = fmt.Sprintf("%v", metricValueAsUint64) 484 ok, err = metricsLLRB.Put(metricKey, metricValueAsString) 485 if nil != err { 486 err = fmt.Errorf("metricsLLRB.Put(%v, %v) failed: %v", metricKey, metricValueAsString, err) 487 logger.Fatalf("HTTP Server Logic Error: %v", err) 488 } 489 if !ok { 490 err = fmt.Errorf("metricsLLRB.Put(%v, %v) returned ok == false", metricKey, metricValueAsString) 491 logger.Fatalf("HTTP Server Logic Error: %v", err) 492 } 493 } 494 495 sortedTwoColumnResponseWriter(metricsLLRB, responseWriter) 496 } 497 } 498 499 func doGetOfStats(responseWriter http.ResponseWriter, request *http.Request) { 500 501 responseWriter.Header().Set("Content-Type", "text/plain") 502 responseWriter.WriteHeader(http.StatusOK) 503 504 _, _ = responseWriter.Write([]byte(bucketstats.SprintStats(bucketstats.StatFormatParsable1, "*", "*"))) 505 } 506 507 func doGetOfArmDisarmTrigger(responseWriter http.ResponseWriter, request *http.Request) { 508 var ( 509 availableTriggers []string 510 err error 511 haltTriggerString string 512 ok bool 513 triggersLLRB sortedmap.LLRBTree 514 ) 515 516 responseWriter.Header().Set("Content-Type", "text/html") 517 responseWriter.WriteHeader(http.StatusOK) 518 519 _, _ = responseWriter.Write([]byte("<!DOCTYPE html>\n")) 520 _, _ = responseWriter.Write([]byte("<html lang=\"en\">\n")) 521 _, _ = responseWriter.Write([]byte(" <head>\n")) 522 _, _ = responseWriter.Write([]byte(fmt.Sprintf(" <title>Trigger Arm/Disarm Page</title>\n"))) 523 _, _ = responseWriter.Write([]byte(" </head>\n")) 524 _, _ = responseWriter.Write([]byte(" <body>\n")) 525 _, _ = responseWriter.Write([]byte(" <form method=\"post\" action=\"/arm-disarm-trigger\">\n")) 526 _, _ = responseWriter.Write([]byte(" <select name=\"haltLabelString\">\n")) 527 _, _ = responseWriter.Write([]byte(" <option value=\"\">-- select one --</option>\n")) 528 529 availableTriggers = halter.List() 530 531 triggersLLRB = sortedmap.NewLLRBTree(sortedmap.CompareString, nil) 532 533 for _, haltTriggerString = range availableTriggers { 534 ok, err = triggersLLRB.Put(haltTriggerString, true) 535 if nil != err { 536 err = fmt.Errorf("triggersLLRB.Put(%v, true) failed: %v", haltTriggerString, err) 537 logger.Fatalf("HTTP Server Logic Error: %v", err) 538 } 539 if !ok { 540 err = fmt.Errorf("triggersLLRB.Put(%v, true) returned ok == false", haltTriggerString) 541 logger.Fatalf("HTTP Server Logic Error: %v", err) 542 } 543 _, _ = responseWriter.Write([]byte(fmt.Sprintf(" <option value=\"%v\">%v</option>\n", haltTriggerString, haltTriggerString))) 544 } 545 546 _, _ = responseWriter.Write([]byte(" </select>\n")) 547 _, _ = responseWriter.Write([]byte(" <input type=\"number\" name=\"haltAfterCount\" min=\"0\" max=\"4294967295\" required>\n")) 548 _, _ = responseWriter.Write([]byte(" <input type=\"submit\">\n")) 549 _, _ = responseWriter.Write([]byte(" </form>\n")) 550 _, _ = responseWriter.Write([]byte(" </body>\n")) 551 _, _ = responseWriter.Write([]byte("</html>\n")) 552 } 553 554 func doGetOfTrigger(responseWriter http.ResponseWriter, request *http.Request) { 555 var ( 556 triggerAllArmedOrDisarmedActiveString string 557 armedTriggers map[string]uint32 558 availableTriggers []string 559 err error 560 haltTriggerArmedStateAsBool bool 561 haltTriggerArmedStateAsString string 562 haltTriggerCount uint32 563 haltTriggerString string 564 i int 565 lenTriggersLLRB int 566 numPathParts int 567 key sortedmap.Key 568 ok bool 569 pathSplit []string 570 triggersLLRB sortedmap.LLRBTree 571 value sortedmap.Value 572 ) 573 574 pathSplit = strings.Split(request.URL.Path, "/") // leading "/" places "" in pathSplit[0] 575 // pathSplit[1] should be "trigger" based on how we got here 576 // trailing "/" places "" in pathSplit[len(pathSplit)-1] 577 578 numPathParts = len(pathSplit) - 1 579 if "" == pathSplit[numPathParts] { 580 numPathParts-- 581 } 582 583 if "trigger" != pathSplit[1] { 584 responseWriter.WriteHeader(http.StatusNotFound) 585 return 586 } 587 588 switch numPathParts { 589 case 1: 590 // Form: /trigger[?armed={true|false}] 591 592 haltTriggerArmedStateAsString = request.FormValue("armed") 593 594 if "" == haltTriggerArmedStateAsString { 595 triggerAllArmedOrDisarmedActiveString = triggerAllActive 596 armedTriggers = halter.Dump() 597 availableTriggers = halter.List() 598 599 triggersLLRB = sortedmap.NewLLRBTree(sortedmap.CompareString, nil) 600 601 for _, haltTriggerString = range availableTriggers { 602 haltTriggerCount, ok = armedTriggers[haltTriggerString] 603 if !ok { 604 haltTriggerCount = 0 605 } 606 ok, err = triggersLLRB.Put(haltTriggerString, haltTriggerCount) 607 if nil != err { 608 err = fmt.Errorf("triggersLLRB.Put(%v, %v) failed: %v", haltTriggerString, haltTriggerCount, err) 609 logger.Fatalf("HTTP Server Logic Error: %v", err) 610 } 611 if !ok { 612 err = fmt.Errorf("triggersLLRB.Put(%v, %v) returned ok == false", haltTriggerString, haltTriggerCount) 613 logger.Fatalf("HTTP Server Logic Error: %v", err) 614 } 615 } 616 } else { 617 haltTriggerArmedStateAsBool, err = strconv.ParseBool(haltTriggerArmedStateAsString) 618 if nil == err { 619 triggersLLRB = sortedmap.NewLLRBTree(sortedmap.CompareString, nil) 620 621 if haltTriggerArmedStateAsBool { 622 triggerAllArmedOrDisarmedActiveString = triggerArmedActive 623 armedTriggers = halter.Dump() 624 for haltTriggerString, haltTriggerCount = range armedTriggers { 625 ok, err = triggersLLRB.Put(haltTriggerString, haltTriggerCount) 626 if nil != err { 627 err = fmt.Errorf("triggersLLRB.Put(%v, %v) failed: %v", haltTriggerString, haltTriggerCount, err) 628 logger.Fatalf("HTTP Server Logic Error: %v", err) 629 } 630 if !ok { 631 err = fmt.Errorf("triggersLLRB.Put(%v, %v) returned ok == false", haltTriggerString, haltTriggerCount) 632 logger.Fatalf("HTTP Server Logic Error: %v", err) 633 } 634 } 635 } else { 636 triggerAllArmedOrDisarmedActiveString = triggerDisarmedActive 637 armedTriggers = halter.Dump() 638 availableTriggers = halter.List() 639 640 for _, haltTriggerString = range availableTriggers { 641 _, ok = armedTriggers[haltTriggerString] 642 if !ok { 643 ok, err = triggersLLRB.Put(haltTriggerString, uint32(0)) 644 if nil != err { 645 err = fmt.Errorf("triggersLLRB.Put(%v, %v) failed: %v", haltTriggerString, 0, err) 646 logger.Fatalf("HTTP Server Logic Error: %v", err) 647 } 648 if !ok { 649 err = fmt.Errorf("triggersLLRB.Put(%v, %v) returned ok == false", haltTriggerString, 0) 650 logger.Fatalf("HTTP Server Logic Error: %v", err) 651 } 652 } 653 } 654 } 655 } else { 656 responseWriter.WriteHeader(http.StatusBadRequest) 657 } 658 } 659 660 responseWriter.Header().Set("Content-Type", "text/html") 661 responseWriter.WriteHeader(http.StatusOK) 662 663 _, _ = responseWriter.Write([]byte(fmt.Sprintf(triggerTopTemplate, version.ProxyFSVersion, globals.ipAddrTCPPort))) 664 _, _ = responseWriter.Write([]byte(triggerAllArmedOrDisarmedActiveString)) 665 _, _ = responseWriter.Write([]byte(triggerTableTop)) 666 667 lenTriggersLLRB, err = triggersLLRB.Len() 668 if nil != err { 669 err = fmt.Errorf("triggersLLRB.Len()) failed: %v", err) 670 logger.Fatalf("HTTP Server Logic Error: %v", err) 671 } 672 673 for i = 0; i < lenTriggersLLRB; i++ { 674 key, value, ok, err = triggersLLRB.GetByIndex(i) 675 if nil != err { 676 err = fmt.Errorf("triggersLLRB.GetByIndex(%v) failed: %v", i, err) 677 logger.Fatalf("HTTP Server Logic Error: %v", err) 678 } 679 if !ok { 680 err = fmt.Errorf("triggersLLRB.GetByIndex(%v) returned ok == false", i) 681 logger.Fatalf("HTTP Server Logic Error: %v", err) 682 } 683 684 haltTriggerString = key.(string) 685 haltTriggerCount = value.(uint32) 686 687 _, _ = responseWriter.Write([]byte(fmt.Sprintf(triggerTableRowTemplate, haltTriggerString, haltTriggerCount))) 688 } 689 690 _, _ = responseWriter.Write([]byte(triggerBottom)) 691 case 2: 692 // Form: /trigger/<trigger-name> 693 694 haltTriggerString = pathSplit[2] 695 696 haltTriggerCount, err = halter.Stat(haltTriggerString) 697 if nil == err { 698 responseWriter.Header().Set("Content-Type", "text/plain") 699 responseWriter.WriteHeader(http.StatusOK) 700 701 _, _ = responseWriter.Write([]byte(fmt.Sprintf("%v\n", haltTriggerCount))) 702 } else { 703 responseWriter.WriteHeader(http.StatusNotFound) 704 } 705 default: 706 responseWriter.WriteHeader(http.StatusNotFound) 707 } 708 } 709 710 func doGetOfVolume(responseWriter http.ResponseWriter, request *http.Request) { 711 var ( 712 acceptHeader string 713 err error 714 formatResponseAsJSON bool 715 formatResponseCompactly bool 716 numPathParts int 717 ok bool 718 paramList []string 719 pathSplit []string 720 percentRange string 721 performValidation bool 722 requestState *requestStateStruct 723 startNonceAsString string 724 startNonceAsUint64 uint64 725 volumeAsValue sortedmap.Value 726 volumeList []string 727 volumeListIndex int 728 volumeListJSON bytes.Buffer 729 volumeListJSONPacked []byte 730 volumeListLen int 731 volumeName string 732 volumeNameAsKey sortedmap.Key 733 ) 734 735 pathSplit = strings.Split(request.URL.Path, "/") // leading "/" places "" in pathSplit[0] 736 // pathSplit[1] should be "volume" based on how we got here 737 // trailing "/" places "" in pathSplit[len(pathSplit)-1] 738 739 numPathParts = len(pathSplit) - 1 740 if "" == pathSplit[numPathParts] { 741 numPathParts-- 742 } 743 744 if "volume" != pathSplit[1] { 745 responseWriter.WriteHeader(http.StatusNotFound) 746 return 747 } 748 749 switch numPathParts { 750 case 1: 751 // Form: /volume 752 case 2: 753 responseWriter.WriteHeader(http.StatusNotFound) 754 return 755 case 3: 756 // Form: /volume/<volume-name>/extent-map 757 // Form: /volume/<volume-name>/fsck-job 758 // Form: /volume/<volume-name>/layout-report 759 // Form: /volume/<volume-name>/lease-report 760 // Form: /volume/<volume-name>/meta-defrag 761 // Form: /volume/<volume-name>/scrub-job 762 // Form: /volume/<volume-name>/snapshot 763 case 4: 764 // Form: /volume/<volume-name>/defrag/<basename> 765 // Form: /volume/<volume-name>/extent-map/<basename> 766 // Form: /volume/<volume-name>/find-subdir-inodes/<DirInodeNumberAs16HexDigits> 767 // Form: /volume/<volume-name>/fsck-job/<job-id> 768 // Form: /volume/<volume-name>/meta-defrag/<BPlusTreeType> 769 // Form: /volume/<volume-name>/scrub-job/<job-id> 770 default: 771 // Form: /volume/<volume-name>/defrag/<dir>/.../<basename> 772 // Form: /volume/<volume-name>/extent-map/<dir>/.../<basename> 773 // Form: /volume/<volume-name>/find-dir-inode/<dir>/.../<basename> 774 } 775 776 acceptHeader = request.Header.Get("Accept") 777 778 if strings.Contains(acceptHeader, "application/json") { 779 formatResponseAsJSON = true 780 } else if strings.Contains(acceptHeader, "text/html") { 781 formatResponseAsJSON = false 782 } else if strings.Contains(acceptHeader, "*/*") { 783 formatResponseAsJSON = true 784 } else if strings.Contains(acceptHeader, "") { 785 formatResponseAsJSON = true 786 } else { 787 responseWriter.WriteHeader(http.StatusNotAcceptable) 788 return 789 } 790 791 if formatResponseAsJSON { 792 paramList, ok = request.URL.Query()["compact"] 793 if ok { 794 if 0 == len(paramList) { 795 formatResponseCompactly = false 796 } else { 797 formatResponseCompactly = !((paramList[0] == "") || (paramList[0] == "0") || (paramList[0] == "false")) 798 } 799 } else { 800 formatResponseCompactly = false 801 } 802 } 803 804 paramList, ok = request.URL.Query()["validate"] 805 if ok { 806 if 0 == len(paramList) { 807 performValidation = false 808 } else { 809 performValidation = !((paramList[0] == "") || (paramList[0] == "0") || (paramList[0] == "false")) 810 } 811 } else { 812 performValidation = false 813 } 814 815 paramList, ok = request.URL.Query()["range"] 816 if ok { 817 if 0 == len(paramList) { 818 percentRange = "0-100" 819 } else { 820 percentRange = paramList[0] 821 if "" == percentRange { 822 percentRange = "0-100" 823 } 824 } 825 } else { 826 percentRange = "0-100" 827 } 828 829 paramList, ok = request.URL.Query()["start"] 830 if ok { 831 if 0 == len(paramList) { 832 startNonceAsUint64 = uint64(0) 833 } else { 834 startNonceAsString = paramList[0] 835 startNonceAsUint64, err = strconv.ParseUint(startNonceAsString, 16, 64) 836 if nil != err { 837 responseWriter.WriteHeader(http.StatusBadRequest) 838 return 839 } 840 } 841 } else { 842 startNonceAsUint64 = uint64(0) 843 } 844 845 if 1 == numPathParts { 846 volumeListLen, err = globals.volumeLLRB.Len() 847 if nil != err { 848 logger.Fatalf("HTTP Server Logic Error: %v", err) 849 } 850 851 volumeList = make([]string, 0, volumeListLen) 852 for volumeListIndex = 0; volumeListIndex < volumeListLen; volumeListIndex++ { 853 // GetByIndex(index int) (key Key, value Value, ok bool, err error) 854 volumeNameAsKey, _, ok, err = globals.volumeLLRB.GetByIndex(volumeListIndex) 855 if nil != err { 856 logger.Fatalf("HTTP Server Logic Error: %v", err) 857 } 858 if !ok { 859 err = fmt.Errorf("httpserver.doGetOfVolume() indexing globals.volumeLLRB failed") 860 logger.Fatalf("HTTP Server Logic Error: %v", err) 861 } 862 863 volumeName = volumeNameAsKey.(string) 864 volumeList = append(volumeList, volumeName) 865 } 866 867 if formatResponseAsJSON { 868 responseWriter.Header().Set("Content-Type", "application/json") 869 responseWriter.WriteHeader(http.StatusOK) 870 871 volumeListJSONPacked, err = json.Marshal(volumeList) 872 if nil != err { 873 logger.Fatalf("HTTP Server Logic Error: %v", err) 874 } 875 876 if formatResponseCompactly { 877 _, _ = responseWriter.Write(volumeListJSONPacked) 878 } else { 879 json.Indent(&volumeListJSON, volumeListJSONPacked, "", "\t") 880 _, _ = responseWriter.Write(volumeListJSON.Bytes()) 881 _, _ = responseWriter.Write([]byte("\n")) 882 } 883 } else { 884 responseWriter.Header().Set("Content-Type", "text/html") 885 responseWriter.WriteHeader(http.StatusOK) 886 887 _, _ = responseWriter.Write([]byte(fmt.Sprintf(volumeListTopTemplate, version.ProxyFSVersion, globals.ipAddrTCPPort))) 888 889 for volumeListIndex, volumeName = range volumeList { 890 _, _ = responseWriter.Write([]byte(fmt.Sprintf(volumeListPerVolumeTemplate, volumeName))) 891 } 892 893 _, _ = responseWriter.Write([]byte(volumeListBottom)) 894 } 895 896 return 897 } 898 899 // If we reach here, numPathParts is at least 3 900 901 volumeName = pathSplit[2] 902 903 volumeAsValue, ok, err = globals.volumeLLRB.GetByKey(volumeName) 904 if nil != err { 905 logger.Fatalf("HTTP Server Logic Error: %v", err) 906 } 907 if !ok { 908 responseWriter.WriteHeader(http.StatusNotFound) 909 return 910 } 911 912 requestState = &requestStateStruct{ 913 pathSplit: pathSplit, 914 numPathParts: numPathParts, 915 formatResponseAsJSON: formatResponseAsJSON, 916 formatResponseCompactly: formatResponseCompactly, 917 performValidation: performValidation, 918 percentRange: percentRange, 919 startNonce: startNonceAsUint64, 920 volume: volumeAsValue.(*volumeStruct), 921 } 922 923 requestState.pathSplit = pathSplit 924 requestState.numPathParts = numPathParts 925 requestState.formatResponseAsJSON = formatResponseAsJSON 926 requestState.formatResponseCompactly = formatResponseCompactly 927 928 switch pathSplit[3] { 929 case "defrag": 930 doDefrag(responseWriter, request, requestState) 931 932 case "find-dir-inode": 933 doFindDirInode(responseWriter, request, requestState) 934 935 case "find-subdir-inodes": 936 doFindSubDirInodes(responseWriter, request, requestState) 937 938 case "extent-map": 939 doExtentMap(responseWriter, request, requestState) 940 941 case "fsck-job": 942 doJob(fsckJobType, responseWriter, request, requestState) 943 944 case "layout-report": 945 doLayoutReport(responseWriter, request, requestState) 946 947 case "lease-report": 948 doLeaseReport(responseWriter, request, requestState) 949 950 case "meta-defrag": 951 doMetaDefrag(responseWriter, request, requestState) 952 953 case "scrub-job": 954 doJob(scrubJobType, responseWriter, request, requestState) 955 956 case "snapshot": 957 doGetOfSnapShot(responseWriter, request, requestState) 958 959 default: 960 responseWriter.WriteHeader(http.StatusNotFound) 961 return 962 } 963 964 return 965 } 966 967 func doFindDirInode(responseWriter http.ResponseWriter, request *http.Request, requestState *requestStateStruct) { 968 var ( 969 parentDirEntryBasename string 970 dirInodeNumber inode.InodeNumber 971 err error 972 parentDirInodeNumber inode.InodeNumber 973 pathSplitIndex int 974 pathSplitIndexMax int 975 ) 976 977 if 3 > requestState.numPathParts { 978 responseWriter.WriteHeader(http.StatusNotFound) 979 return 980 } 981 982 if "" == requestState.pathSplit[len(requestState.pathSplit)-1] { 983 pathSplitIndexMax = len(requestState.pathSplit) - 2 984 } else { 985 pathSplitIndexMax = len(requestState.pathSplit) - 1 986 } 987 988 parentDirInodeNumber = inode.RootDirInodeNumber 989 parentDirEntryBasename = "." 990 dirInodeNumber = inode.RootDirInodeNumber 991 992 for pathSplitIndex = 4; pathSplitIndex <= pathSplitIndexMax; pathSplitIndex++ { 993 parentDirInodeNumber = dirInodeNumber 994 parentDirEntryBasename = requestState.pathSplit[pathSplitIndex] 995 996 dirInodeNumber, err = requestState.volume.fsVolumeHandle.Lookup(inode.InodeRootUserID, inode.InodeGroupID(0), nil, parentDirInodeNumber, parentDirEntryBasename) 997 if nil != err { 998 responseWriter.WriteHeader(http.StatusNotFound) 999 return 1000 } 1001 } 1002 1003 responseWriter.Header().Set("Content-Type", "text/plain") 1004 responseWriter.WriteHeader(http.StatusOK) 1005 1006 _, _ = responseWriter.Write([]byte(fmt.Sprintf("(%016X) %s => %016X\n", parentDirInodeNumber, parentDirEntryBasename, dirInodeNumber))) 1007 } 1008 1009 func doFindSubDirInodes(responseWriter http.ResponseWriter, request *http.Request, requestState *requestStateStruct) { 1010 var ( 1011 dirInodeNumber inode.InodeNumber 1012 dirInodeNumberAsUint64 uint64 1013 err error 1014 lastInodeNumberAsUint64 uint64 1015 nextInodeNumberAsUint64 uint64 1016 ok bool 1017 subDirInodeNumber inode.InodeNumber 1018 subDirInodeNumbers []inode.InodeNumber 1019 subDirParentInodeNumber inode.InodeNumber 1020 ) 1021 1022 if 4 != requestState.numPathParts { 1023 responseWriter.WriteHeader(http.StatusNotFound) 1024 return 1025 } 1026 1027 dirInodeNumberAsUint64, err = strconv.ParseUint(requestState.pathSplit[4], 16, 64) 1028 if nil != err { 1029 responseWriter.WriteHeader(http.StatusNotFound) 1030 return 1031 } 1032 dirInodeNumber = inode.InodeNumber(dirInodeNumberAsUint64) 1033 1034 globals.Unlock() 1035 1036 subDirInodeNumbers = make([]inode.InodeNumber, 0) 1037 1038 lastInodeNumberAsUint64 = requestState.startNonce 1039 1040 for { 1041 nextInodeNumberAsUint64, ok, err = requestState.volume.headhunterVolumeHandle.NextInodeNumber(lastInodeNumberAsUint64) 1042 if nil != err { 1043 logger.FatalfWithError(err, "Unexpected failure walking Inode Table") 1044 } 1045 1046 if !ok { 1047 break 1048 } 1049 1050 subDirInodeNumber = inode.InodeNumber(nextInodeNumberAsUint64) 1051 1052 subDirParentInodeNumber, err = requestState.volume.fsVolumeHandle.Lookup(inode.InodeRootUserID, inode.InodeGroupID(0), nil, subDirInodeNumber, "..") 1053 if (nil == err) && (subDirParentInodeNumber == dirInodeNumber) { 1054 subDirInodeNumbers = append(subDirInodeNumbers, subDirInodeNumber) 1055 } 1056 1057 lastInodeNumberAsUint64 = nextInodeNumberAsUint64 1058 } 1059 1060 responseWriter.Header().Set("Content-Type", "text/plain") 1061 responseWriter.WriteHeader(http.StatusOK) 1062 1063 for _, subDirInodeNumber = range subDirInodeNumbers { 1064 _, _ = responseWriter.Write([]byte(fmt.Sprintf("%016X\n", subDirInodeNumber))) 1065 } 1066 1067 globals.Lock() 1068 } 1069 1070 func doMetaDefrag(responseWriter http.ResponseWriter, request *http.Request, requestState *requestStateStruct) { 1071 var ( 1072 bPlusTreeType string 1073 bPlusTreeTypeSlice []string 1074 err error 1075 percentRangeSplit []string 1076 percentRangeStartAsFloat64 float64 1077 percentRangeStopAsFloat64 float64 1078 ) 1079 1080 if 3 == requestState.numPathParts { 1081 bPlusTreeTypeSlice = []string{"InodeRecBPlusTree", "LogSegmentRecBPlusTree", "BPlusTreeObjectBPlusTree", "CreatedObjectsBPlusTree", "DeletedObjectsBPlusTree"} 1082 } else if 4 == requestState.numPathParts { 1083 bPlusTreeTypeSlice = []string{requestState.pathSplit[4]} 1084 } else { 1085 responseWriter.WriteHeader(http.StatusNotFound) 1086 return 1087 } 1088 1089 percentRangeSplit = strings.Split(requestState.percentRange, "-") 1090 1091 if 2 != len(percentRangeSplit) { 1092 responseWriter.WriteHeader(http.StatusRequestedRangeNotSatisfiable) 1093 return 1094 } 1095 1096 percentRangeStartAsFloat64, err = strconv.ParseFloat(percentRangeSplit[0], 64) 1097 if (nil != err) || (0 > percentRangeStartAsFloat64) || (100 <= percentRangeStartAsFloat64) { 1098 responseWriter.WriteHeader(http.StatusRequestedRangeNotSatisfiable) 1099 return 1100 } 1101 1102 percentRangeStopAsFloat64, err = strconv.ParseFloat(percentRangeSplit[1], 64) 1103 if (nil != err) || (percentRangeStartAsFloat64 >= percentRangeStopAsFloat64) || (100 < percentRangeStopAsFloat64) { 1104 responseWriter.WriteHeader(http.StatusRequestedRangeNotSatisfiable) 1105 return 1106 } 1107 1108 for _, bPlusTreeType = range bPlusTreeTypeSlice { 1109 switch bPlusTreeType { 1110 case "InodeRecBPlusTree": 1111 err = requestState.volume.headhunterVolumeHandle.DefragmentMetadata( 1112 headhunter.InodeRecBPlusTree, 1113 percentRangeStartAsFloat64, 1114 percentRangeStopAsFloat64) 1115 case "LogSegmentRecBPlusTree": 1116 err = requestState.volume.headhunterVolumeHandle.DefragmentMetadata( 1117 headhunter.LogSegmentRecBPlusTree, 1118 percentRangeStartAsFloat64, 1119 percentRangeStopAsFloat64) 1120 case "BPlusTreeObjectBPlusTree": 1121 err = requestState.volume.headhunterVolumeHandle.DefragmentMetadata( 1122 headhunter.BPlusTreeObjectBPlusTree, 1123 percentRangeStartAsFloat64, 1124 percentRangeStopAsFloat64) 1125 case "CreatedObjectsBPlusTree": 1126 err = requestState.volume.headhunterVolumeHandle.DefragmentMetadata( 1127 headhunter.CreatedObjectsBPlusTree, 1128 percentRangeStartAsFloat64, 1129 percentRangeStopAsFloat64) 1130 case "DeletedObjectsBPlusTree": 1131 err = requestState.volume.headhunterVolumeHandle.DefragmentMetadata( 1132 headhunter.DeletedObjectsBPlusTree, 1133 percentRangeStartAsFloat64, 1134 percentRangeStopAsFloat64) 1135 default: 1136 responseWriter.WriteHeader(http.StatusNotFound) 1137 return 1138 } 1139 1140 if nil != err { 1141 logger.Fatalf("Call to %s.headhunterVolumeHandle.DefragmentMetadata(%s,,) failed: %v", requestState.volume.name, bPlusTreeType, err) 1142 responseWriter.WriteHeader(http.StatusInternalServerError) 1143 return 1144 } 1145 } 1146 1147 responseWriter.WriteHeader(http.StatusNoContent) 1148 } 1149 1150 func doDefrag(responseWriter http.ResponseWriter, request *http.Request, requestState *requestStateStruct) { 1151 var ( 1152 alreadyInActiveDefragInodeNumberSet bool 1153 dirEntryInodeNumber inode.InodeNumber 1154 dirInodeNumber inode.InodeNumber 1155 err error 1156 pathPartIndex int 1157 ) 1158 1159 if 3 > requestState.numPathParts { 1160 err = fmt.Errorf("doDefrag() not passed enough requestState.numPathParts (%d)", requestState.numPathParts) 1161 logger.Fatalf("HTTP Server Logic Error: %v", err) 1162 } 1163 1164 dirEntryInodeNumber = inode.RootDirInodeNumber 1165 pathPartIndex = 3 1166 1167 for ; pathPartIndex < requestState.numPathParts; pathPartIndex++ { 1168 dirInodeNumber = dirEntryInodeNumber 1169 1170 dirEntryInodeNumber, err = requestState.volume.fsVolumeHandle.Lookup(inode.InodeRootUserID, inode.InodeGroupID(0), nil, dirInodeNumber, requestState.pathSplit[pathPartIndex+1]) 1171 if nil != err { 1172 responseWriter.WriteHeader(http.StatusNotFound) 1173 return 1174 } 1175 } 1176 1177 _, alreadyInActiveDefragInodeNumberSet = requestState.volume.activeDefragInodeNumberSet[dirEntryInodeNumber] 1178 if alreadyInActiveDefragInodeNumberSet { 1179 responseWriter.WriteHeader(http.StatusConflict) 1180 return 1181 } 1182 1183 requestState.volume.activeDefragInodeNumberSet[dirEntryInodeNumber] = struct{}{} 1184 1185 globals.Unlock() 1186 1187 err = requestState.volume.fsVolumeHandle.DefragmentFile(inode.InodeRootUserID, inode.InodeGroupID(0), nil, dirEntryInodeNumber) 1188 1189 if nil == err { 1190 responseWriter.WriteHeader(http.StatusOK) 1191 } else { 1192 responseWriter.WriteHeader(http.StatusConflict) 1193 } 1194 1195 globals.Lock() 1196 1197 delete(requestState.volume.activeDefragInodeNumberSet, dirEntryInodeNumber) 1198 } 1199 1200 func doExtentMap(responseWriter http.ResponseWriter, request *http.Request, requestState *requestStateStruct) { 1201 var ( 1202 dirEntryInodeNumber inode.InodeNumber 1203 dirInodeNumber inode.InodeNumber 1204 err error 1205 extentMap []ExtentMapElementStruct 1206 extentMapChunk *inode.ExtentMapChunkStruct 1207 extentMapEntry inode.ExtentMapEntryStruct 1208 extentMapJSONBuffer bytes.Buffer 1209 extentMapJSONPacked []byte 1210 path string 1211 pathDoubleQuoted string 1212 pathPartIndex int 1213 ) 1214 1215 if 3 > requestState.numPathParts { 1216 err = fmt.Errorf("doExtentMap() not passed enough requestState.numPathParts (%d)", requestState.numPathParts) 1217 logger.Fatalf("HTTP Server Logic Error: %v", err) 1218 } 1219 1220 if (3 == requestState.numPathParts) && requestState.formatResponseAsJSON { 1221 responseWriter.WriteHeader(http.StatusNotFound) 1222 return 1223 } 1224 1225 if 3 == requestState.numPathParts { 1226 responseWriter.Header().Set("Content-Type", "text/html") 1227 responseWriter.WriteHeader(http.StatusOK) 1228 1229 _, _ = responseWriter.Write([]byte(fmt.Sprintf(extentMapTemplate, version.ProxyFSVersion, globals.ipAddrTCPPort, requestState.volume.name, "null", "null", "false"))) 1230 return 1231 } 1232 1233 path = strings.Join(requestState.pathSplit[4:], "/") 1234 pathDoubleQuoted = "\"" + path + "\"" 1235 1236 dirEntryInodeNumber = inode.RootDirInodeNumber 1237 pathPartIndex = 3 1238 1239 for ; pathPartIndex < requestState.numPathParts; pathPartIndex++ { 1240 dirInodeNumber = dirEntryInodeNumber 1241 dirEntryInodeNumber, err = requestState.volume.fsVolumeHandle.Lookup(inode.InodeRootUserID, inode.InodeGroupID(0), nil, dirInodeNumber, requestState.pathSplit[pathPartIndex+1]) 1242 if nil != err { 1243 if requestState.formatResponseAsJSON { 1244 responseWriter.WriteHeader(http.StatusNotFound) 1245 return 1246 } else { 1247 responseWriter.Header().Set("Content-Type", "text/html") 1248 responseWriter.WriteHeader(http.StatusOK) 1249 1250 _, _ = responseWriter.Write([]byte(fmt.Sprintf(extentMapTemplate, version.ProxyFSVersion, globals.ipAddrTCPPort, requestState.volume.name, "null", pathDoubleQuoted, "true"))) 1251 return 1252 } 1253 } 1254 } 1255 1256 extentMapChunk, err = requestState.volume.fsVolumeHandle.FetchExtentMapChunk(inode.InodeRootUserID, inode.InodeGroupID(0), nil, dirEntryInodeNumber, uint64(0), math.MaxInt64, int64(0)) 1257 if nil != err { 1258 if requestState.formatResponseAsJSON { 1259 responseWriter.WriteHeader(http.StatusNotFound) 1260 return 1261 } else { 1262 responseWriter.Header().Set("Content-Type", "text/html") 1263 responseWriter.WriteHeader(http.StatusOK) 1264 1265 _, _ = responseWriter.Write([]byte(fmt.Sprintf(extentMapTemplate, version.ProxyFSVersion, globals.ipAddrTCPPort, requestState.volume.name, "null", pathDoubleQuoted, "true"))) 1266 return 1267 } 1268 } 1269 1270 extentMap = make([]ExtentMapElementStruct, 0, len(extentMapChunk.ExtentMapEntry)) 1271 1272 for _, extentMapEntry = range extentMapChunk.ExtentMapEntry { 1273 extentMap = append(extentMap, ExtentMapElementStruct{ 1274 FileOffset: extentMapEntry.FileOffset, 1275 ContainerName: extentMapEntry.ContainerName, 1276 ObjectName: extentMapEntry.ObjectName, 1277 ObjectOffset: extentMapEntry.LogSegmentOffset, 1278 Length: extentMapEntry.Length, 1279 }) 1280 } 1281 1282 extentMapJSONPacked, err = json.Marshal(extentMap) 1283 if nil != err { 1284 logger.Fatalf("HTTP Server Logic Error: %v", err) 1285 } 1286 1287 if requestState.formatResponseAsJSON { 1288 responseWriter.Header().Set("Content-Type", "application/json") 1289 responseWriter.WriteHeader(http.StatusOK) 1290 1291 if requestState.formatResponseCompactly { 1292 _, _ = responseWriter.Write(extentMapJSONPacked) 1293 } else { 1294 json.Indent(&extentMapJSONBuffer, extentMapJSONPacked, "", "\t") 1295 _, _ = responseWriter.Write(extentMapJSONBuffer.Bytes()) 1296 _, _ = responseWriter.Write([]byte("\n")) 1297 } 1298 } else { 1299 responseWriter.Header().Set("Content-Type", "text/html") 1300 responseWriter.WriteHeader(http.StatusOK) 1301 1302 _, _ = responseWriter.Write([]byte(fmt.Sprintf(extentMapTemplate, version.ProxyFSVersion, globals.ipAddrTCPPort, requestState.volume.name, utils.ByteSliceToString(extentMapJSONPacked), pathDoubleQuoted, "false"))) 1303 } 1304 } 1305 1306 func doJob(jobType jobTypeType, responseWriter http.ResponseWriter, request *http.Request, requestState *requestStateStruct) { 1307 var ( 1308 err error 1309 formatResponseAsJSON bool 1310 formatResponseCompactly bool 1311 inactive bool 1312 job *jobStruct 1313 jobAsValue sortedmap.Value 1314 jobErrorList []string 1315 jobID uint64 1316 jobIDAsKey sortedmap.Key 1317 jobPerJobTemplate string 1318 jobsIDListJSONBuffer bytes.Buffer 1319 jobsIDListJSONPacked []byte 1320 jobStatusJSONBuffer bytes.Buffer 1321 jobStatusJSONPacked []byte 1322 jobStatusJSONStruct *JobStatusJSONPackedStruct 1323 jobsCount int 1324 jobsIDList []uint64 1325 jobsIndex int 1326 ok bool 1327 numPathParts int 1328 pathSplit []string 1329 volume *volumeStruct 1330 volumeName string 1331 ) 1332 1333 if limitJobType <= jobType { 1334 err = fmt.Errorf("httpserver.doJob(jobtype==%v,,,) called for invalid jobType", jobType) 1335 logger.Fatalf("HTTP Server Logic Error: %v", err) 1336 } 1337 1338 volume = requestState.volume 1339 pathSplit = requestState.pathSplit 1340 numPathParts = requestState.numPathParts 1341 formatResponseAsJSON = requestState.formatResponseAsJSON 1342 formatResponseCompactly = requestState.formatResponseCompactly 1343 1344 volumeName = volume.name 1345 volume.Lock() 1346 1347 markJobsCompletedIfNoLongerActiveWhileLocked(volume) 1348 1349 if 3 == numPathParts { 1350 switch jobType { 1351 case fsckJobType: 1352 jobsCount, err = volume.fsckJobs.Len() 1353 case scrubJobType: 1354 jobsCount, err = volume.scrubJobs.Len() 1355 } 1356 if nil != err { 1357 logger.Fatalf("HTTP Server Logic Error: %v", err) 1358 } 1359 1360 inactive = (nil == volume.fsckActiveJob) && (nil == volume.scrubActiveJob) 1361 1362 volume.Unlock() 1363 1364 if formatResponseAsJSON { 1365 jobsIDList = make([]uint64, 0, jobsCount) 1366 1367 for jobsIndex = jobsCount - 1; jobsIndex >= 0; jobsIndex-- { 1368 switch jobType { 1369 case fsckJobType: 1370 jobIDAsKey, _, ok, err = volume.fsckJobs.GetByIndex(jobsIndex) 1371 case scrubJobType: 1372 jobIDAsKey, _, ok, err = volume.scrubJobs.GetByIndex(jobsIndex) 1373 } 1374 if nil != err { 1375 logger.Fatalf("HTTP Server Logic Error: %v", err) 1376 } 1377 if !ok { 1378 switch jobType { 1379 case fsckJobType: 1380 err = fmt.Errorf("httpserver.doGetOfVolume() indexing volume.fsckJobs failed") 1381 case scrubJobType: 1382 err = fmt.Errorf("httpserver.doGetOfVolume() indexing volume.scrubJobs failed") 1383 } 1384 logger.Fatalf("HTTP Server Logic Error: %v", err) 1385 } 1386 jobID = jobIDAsKey.(uint64) 1387 1388 jobsIDList = append(jobsIDList, jobID) 1389 } 1390 1391 responseWriter.Header().Set("Content-Type", "application/json") 1392 responseWriter.WriteHeader(http.StatusOK) 1393 1394 jobsIDListJSONPacked, err = json.Marshal(jobsIDList) 1395 if nil != err { 1396 logger.Fatalf("HTTP Server Logic Error: %v", err) 1397 } 1398 1399 if formatResponseCompactly { 1400 _, _ = responseWriter.Write(jobsIDListJSONPacked) 1401 } else { 1402 json.Indent(&jobsIDListJSONBuffer, jobsIDListJSONPacked, "", "\t") 1403 _, _ = responseWriter.Write(jobsIDListJSONBuffer.Bytes()) 1404 _, _ = responseWriter.Write([]byte("\n")) 1405 } 1406 } else { 1407 responseWriter.Header().Set("Content-Type", "text/html") 1408 responseWriter.WriteHeader(http.StatusOK) 1409 1410 switch jobType { 1411 case fsckJobType: 1412 _, _ = responseWriter.Write([]byte(fmt.Sprintf(jobsTopTemplate, version.ProxyFSVersion, globals.ipAddrTCPPort, volumeName, "FSCK"))) 1413 case scrubJobType: 1414 _, _ = responseWriter.Write([]byte(fmt.Sprintf(jobsTopTemplate, version.ProxyFSVersion, globals.ipAddrTCPPort, volumeName, "SCRUB"))) 1415 } 1416 1417 for jobsIndex = jobsCount - 1; jobsIndex >= 0; jobsIndex-- { 1418 switch jobType { 1419 case fsckJobType: 1420 jobIDAsKey, jobAsValue, ok, err = volume.fsckJobs.GetByIndex(jobsIndex) 1421 case scrubJobType: 1422 jobIDAsKey, jobAsValue, ok, err = volume.scrubJobs.GetByIndex(jobsIndex) 1423 } 1424 if nil != err { 1425 logger.Fatalf("HTTP Server Logic Error: %v", err) 1426 } 1427 if !ok { 1428 switch jobType { 1429 case fsckJobType: 1430 err = fmt.Errorf("httpserver.doGetOfVolume() indexing volume.fsckJobs failed") 1431 case scrubJobType: 1432 err = fmt.Errorf("httpserver.doGetOfVolume() indexing volume.scrubJobs failed") 1433 } 1434 logger.Fatalf("HTTP Server Logic Error: %v", err) 1435 } 1436 1437 jobID = jobIDAsKey.(uint64) 1438 job = jobAsValue.(*jobStruct) 1439 1440 if jobRunning == job.state { 1441 switch jobType { 1442 case fsckJobType: 1443 _, _ = responseWriter.Write([]byte(fmt.Sprintf(jobsPerRunningJobTemplate, jobID, job.startTime.Format(time.RFC3339), volumeName, "fsck"))) 1444 case scrubJobType: 1445 _, _ = responseWriter.Write([]byte(fmt.Sprintf(jobsPerRunningJobTemplate, jobID, job.startTime.Format(time.RFC3339), volumeName, "scrub"))) 1446 } 1447 } else { 1448 switch job.state { 1449 case jobHalted: 1450 jobPerJobTemplate = jobsPerHaltedJobTemplate 1451 case jobCompleted: 1452 jobErrorList = job.jobHandle.Error() 1453 if 0 == len(jobErrorList) { 1454 jobPerJobTemplate = jobsPerSuccessfulJobTemplate 1455 } else { 1456 jobPerJobTemplate = jobsPerFailedJobTemplate 1457 } 1458 } 1459 1460 switch jobType { 1461 case fsckJobType: 1462 _, _ = responseWriter.Write([]byte(fmt.Sprintf(jobPerJobTemplate, jobID, job.startTime.Format(time.RFC3339), job.endTime.Format(time.RFC3339), volumeName, "fsck"))) 1463 case scrubJobType: 1464 _, _ = responseWriter.Write([]byte(fmt.Sprintf(jobPerJobTemplate, jobID, job.startTime.Format(time.RFC3339), job.endTime.Format(time.RFC3339), volumeName, "scrub"))) 1465 } 1466 } 1467 } 1468 1469 _, _ = responseWriter.Write([]byte(jobsListBottom)) 1470 1471 if inactive { 1472 switch jobType { 1473 case fsckJobType: 1474 _, _ = responseWriter.Write([]byte(fmt.Sprintf(jobsStartJobButtonTemplate, volumeName, "fsck"))) 1475 case scrubJobType: 1476 _, _ = responseWriter.Write([]byte(fmt.Sprintf(jobsStartJobButtonTemplate, volumeName, "scrub"))) 1477 } 1478 } 1479 1480 _, _ = responseWriter.Write([]byte(jobsBottom)) 1481 } 1482 1483 return 1484 } 1485 1486 // If we reach here, numPathParts is 4 1487 1488 jobID, err = strconv.ParseUint(pathSplit[4], 10, 64) 1489 if nil != err { 1490 volume.Unlock() 1491 responseWriter.WriteHeader(http.StatusNotFound) 1492 return 1493 } 1494 1495 switch jobType { 1496 case fsckJobType: 1497 jobAsValue, ok, err = volume.fsckJobs.GetByKey(jobID) 1498 case scrubJobType: 1499 jobAsValue, ok, err = volume.scrubJobs.GetByKey(jobID) 1500 } 1501 if nil != err { 1502 logger.Fatalf("HTTP Server Logic Error: %v", err) 1503 } 1504 if !ok { 1505 volume.Unlock() 1506 responseWriter.WriteHeader(http.StatusNotFound) 1507 return 1508 } 1509 job = jobAsValue.(*jobStruct) 1510 1511 jobStatusJSONStruct = &JobStatusJSONPackedStruct{ 1512 StartTime: job.startTime.Format(time.RFC3339), 1513 ErrorList: job.jobHandle.Error(), 1514 InfoList: job.jobHandle.Info(), 1515 } 1516 1517 switch job.state { 1518 case jobRunning: 1519 // Nothing to add here 1520 case jobHalted: 1521 jobStatusJSONStruct.HaltTime = job.endTime.Format(time.RFC3339) 1522 case jobCompleted: 1523 jobStatusJSONStruct.DoneTime = job.endTime.Format(time.RFC3339) 1524 } 1525 1526 jobStatusJSONPacked, err = json.Marshal(jobStatusJSONStruct) 1527 if nil != err { 1528 logger.Fatalf("HTTP Server Logic Error: %v", err) 1529 } 1530 1531 if formatResponseAsJSON { 1532 responseWriter.Header().Set("Content-Type", "application/json") 1533 responseWriter.WriteHeader(http.StatusOK) 1534 1535 if formatResponseCompactly { 1536 _, _ = responseWriter.Write(jobStatusJSONPacked) 1537 } else { 1538 json.Indent(&jobStatusJSONBuffer, jobStatusJSONPacked, "", "\t") 1539 _, _ = responseWriter.Write(jobStatusJSONBuffer.Bytes()) 1540 _, _ = responseWriter.Write([]byte("\n")) 1541 } 1542 } else { 1543 responseWriter.Header().Set("Content-Type", "text/html") 1544 responseWriter.WriteHeader(http.StatusOK) 1545 1546 switch jobType { 1547 case fsckJobType: 1548 _, _ = responseWriter.Write([]byte(fmt.Sprintf(jobTemplate, version.ProxyFSVersion, globals.ipAddrTCPPort, volumeName, "FSCK", "fsck", job.id, utils.ByteSliceToString(jobStatusJSONPacked)))) 1549 case scrubJobType: 1550 _, _ = responseWriter.Write([]byte(fmt.Sprintf(jobTemplate, version.ProxyFSVersion, globals.ipAddrTCPPort, volumeName, "SCRUB", "scrub", job.id, utils.ByteSliceToString(jobStatusJSONPacked)))) 1551 } 1552 } 1553 1554 volume.Unlock() 1555 } 1556 1557 type layoutReportElementLayoutReportElementStruct struct { 1558 ObjectNumber uint64 1559 ObjectBytes uint64 1560 } 1561 1562 type layoutReportSetElementStruct struct { 1563 TreeName string 1564 Discrepencies uint64 1565 LayoutReport []layoutReportElementLayoutReportElementStruct 1566 } 1567 1568 type layoutReportSetElementWithoutDiscrepenciesStruct struct { 1569 TreeName string 1570 LayoutReport []layoutReportElementLayoutReportElementStruct 1571 } 1572 1573 func doLayoutReport(responseWriter http.ResponseWriter, request *http.Request, requestState *requestStateStruct) { 1574 var ( 1575 discrepencyFormatClass string 1576 err error 1577 layoutReportIndex int 1578 layoutReportMap sortedmap.LayoutReport 1579 layoutReportSet [6]*layoutReportSetElementStruct 1580 layoutReportSetElement *layoutReportSetElementStruct 1581 layoutReportSetJSON bytes.Buffer 1582 layoutReportSetJSONPacked []byte 1583 layoutReportSetWithoutDiscrepencies [6]*layoutReportSetElementWithoutDiscrepenciesStruct 1584 objectBytes uint64 1585 objectNumber uint64 1586 treeTypeIndex int 1587 ) 1588 1589 layoutReportSet[headhunter.MergedBPlusTree] = &layoutReportSetElementStruct{ 1590 TreeName: "Checkpoint (Trailer + B+Trees)", 1591 } 1592 layoutReportSet[headhunter.InodeRecBPlusTree] = &layoutReportSetElementStruct{ 1593 TreeName: "Inode Record B+Tree", 1594 } 1595 layoutReportSet[headhunter.LogSegmentRecBPlusTree] = &layoutReportSetElementStruct{ 1596 TreeName: "Log Segment Record B+Tree", 1597 } 1598 layoutReportSet[headhunter.BPlusTreeObjectBPlusTree] = &layoutReportSetElementStruct{ 1599 TreeName: "B+Plus Tree Objects B+Tree", 1600 } 1601 layoutReportSet[headhunter.CreatedObjectsBPlusTree] = &layoutReportSetElementStruct{ 1602 TreeName: "Created Objects B+Tree", 1603 } 1604 layoutReportSet[headhunter.DeletedObjectsBPlusTree] = &layoutReportSetElementStruct{ 1605 TreeName: "Deleted Objects B+Tree", 1606 } 1607 1608 for treeTypeIndex, layoutReportSetElement = range layoutReportSet { 1609 layoutReportMap, layoutReportSetElement.Discrepencies, err = requestState.volume.headhunterVolumeHandle.FetchLayoutReport(headhunter.BPlusTreeType(treeTypeIndex), requestState.performValidation) 1610 if nil != err { 1611 responseWriter.WriteHeader(http.StatusInternalServerError) 1612 return 1613 } 1614 1615 layoutReportSetElement.LayoutReport = make([]layoutReportElementLayoutReportElementStruct, len(layoutReportMap)) 1616 1617 layoutReportIndex = 0 1618 1619 for objectNumber, objectBytes = range layoutReportMap { 1620 layoutReportSetElement.LayoutReport[layoutReportIndex] = layoutReportElementLayoutReportElementStruct{objectNumber, objectBytes} 1621 layoutReportIndex++ 1622 } 1623 } 1624 1625 if requestState.formatResponseAsJSON { 1626 responseWriter.Header().Set("Content-Type", "application/json") 1627 responseWriter.WriteHeader(http.StatusOK) 1628 1629 if requestState.performValidation { 1630 layoutReportSetJSONPacked, err = json.Marshal(layoutReportSet) 1631 } else { 1632 for treeTypeIndex, layoutReportSetElement = range layoutReportSet { 1633 layoutReportSetWithoutDiscrepencies[treeTypeIndex] = &layoutReportSetElementWithoutDiscrepenciesStruct{ 1634 TreeName: layoutReportSetElement.TreeName, 1635 LayoutReport: layoutReportSetElement.LayoutReport, 1636 } 1637 } 1638 layoutReportSetJSONPacked, err = json.Marshal(layoutReportSetWithoutDiscrepencies) 1639 } 1640 if nil != err { 1641 responseWriter.WriteHeader(http.StatusInternalServerError) 1642 return 1643 } 1644 1645 if requestState.formatResponseCompactly { 1646 _, _ = responseWriter.Write(layoutReportSetJSONPacked) 1647 } else { 1648 json.Indent(&layoutReportSetJSON, layoutReportSetJSONPacked, "", "\t") 1649 _, _ = responseWriter.Write(layoutReportSetJSON.Bytes()) 1650 _, _ = responseWriter.Write([]byte("\n")) 1651 } 1652 } else { 1653 responseWriter.Header().Set("Content-Type", "text/html") 1654 responseWriter.WriteHeader(http.StatusOK) 1655 1656 _, _ = responseWriter.Write([]byte(fmt.Sprintf(layoutReportTopTemplate, version.ProxyFSVersion, globals.ipAddrTCPPort, requestState.volume.name))) 1657 1658 for _, layoutReportSetElement = range layoutReportSet { 1659 if 0 == layoutReportSetElement.Discrepencies { 1660 discrepencyFormatClass = "success" 1661 } else { 1662 discrepencyFormatClass = "danger" 1663 } 1664 _, _ = responseWriter.Write([]byte(fmt.Sprintf(layoutReportTableTopTemplate, layoutReportSetElement.TreeName, layoutReportSetElement.Discrepencies, discrepencyFormatClass))) 1665 1666 for layoutReportIndex = 0; layoutReportIndex < len(layoutReportSetElement.LayoutReport); layoutReportIndex++ { 1667 _, _ = responseWriter.Write([]byte(fmt.Sprintf(layoutReportTableRowTemplate, layoutReportSetElement.LayoutReport[layoutReportIndex].ObjectNumber, layoutReportSetElement.LayoutReport[layoutReportIndex].ObjectBytes))) 1668 } 1669 1670 _, _ = responseWriter.Write([]byte(layoutReportTableBottom)) 1671 } 1672 1673 _, _ = responseWriter.Write([]byte(layoutReportBottom)) 1674 } 1675 } 1676 1677 func doLeaseReport(responseWriter http.ResponseWriter, request *http.Request, requestState *requestStateStruct) { 1678 var ( 1679 err error 1680 leaseReport []*jrpcfs.LeaseReportElementStruct 1681 leaseReportJSON bytes.Buffer 1682 leaseReportJSONPacked []byte 1683 ) 1684 1685 leaseReport, err = jrpcfs.FetchLeaseReport(requestState.volume.name) 1686 if nil != err { 1687 responseWriter.WriteHeader(http.StatusNotFound) 1688 return 1689 } 1690 1691 leaseReportJSONPacked, err = json.Marshal(leaseReport) 1692 if nil != err { 1693 responseWriter.WriteHeader(http.StatusInternalServerError) 1694 return 1695 } 1696 1697 // TODO: Need to align this logic and cleanly format page... but for now... 1698 1699 responseWriter.Header().Set("Content-Type", "application/json") 1700 responseWriter.WriteHeader(http.StatusOK) 1701 1702 if requestState.formatResponseCompactly { 1703 _, _ = responseWriter.Write(leaseReportJSONPacked) 1704 } else { 1705 json.Indent(&leaseReportJSON, leaseReportJSONPacked, "", "\t") 1706 _, _ = responseWriter.Write(leaseReportJSON.Bytes()) 1707 _, _ = responseWriter.Write([]byte("\n")) 1708 } 1709 } 1710 1711 func doGetOfSnapShot(responseWriter http.ResponseWriter, request *http.Request, requestState *requestStateStruct) { 1712 var ( 1713 directionStringCanonicalized string 1714 directionStringSlice []string 1715 err error 1716 list []headhunter.SnapShotStruct 1717 listJSON bytes.Buffer 1718 listJSONPacked []byte 1719 orderByStringCanonicalized string 1720 orderByStringSlice []string 1721 queryValues url.Values 1722 reversed bool 1723 snapShot headhunter.SnapShotStruct 1724 ) 1725 1726 queryValues = request.URL.Query() 1727 1728 directionStringSlice = queryValues["direction"] 1729 1730 if 0 == len(directionStringSlice) { 1731 reversed = false 1732 } else { 1733 directionStringCanonicalized = strings.ToLower(directionStringSlice[0]) 1734 if "desc" == directionStringCanonicalized { 1735 reversed = true 1736 } else { 1737 reversed = false 1738 } 1739 } 1740 1741 orderByStringSlice = queryValues["orderby"] 1742 1743 if 0 == len(orderByStringSlice) { 1744 list = requestState.volume.headhunterVolumeHandle.SnapShotListByTime(reversed) 1745 } else { 1746 orderByStringCanonicalized = strings.ToLower(orderByStringSlice[0]) 1747 switch orderByStringCanonicalized { 1748 case "id": 1749 list = requestState.volume.headhunterVolumeHandle.SnapShotListByID(reversed) 1750 case "name": 1751 list = requestState.volume.headhunterVolumeHandle.SnapShotListByName(reversed) 1752 default: // assume "time" 1753 list = requestState.volume.headhunterVolumeHandle.SnapShotListByTime(reversed) 1754 } 1755 } 1756 1757 if requestState.formatResponseAsJSON { 1758 responseWriter.Header().Set("Content-Type", "application/json") 1759 responseWriter.WriteHeader(http.StatusOK) 1760 1761 listJSONPacked, err = json.Marshal(list) 1762 if nil != err { 1763 responseWriter.WriteHeader(http.StatusInternalServerError) 1764 return 1765 } 1766 1767 if requestState.formatResponseCompactly { 1768 _, _ = responseWriter.Write(listJSONPacked) 1769 } else { 1770 json.Indent(&listJSON, listJSONPacked, "", "\t") 1771 _, _ = responseWriter.Write(listJSON.Bytes()) 1772 _, _ = responseWriter.Write([]byte("\n")) 1773 } 1774 } else { 1775 responseWriter.Header().Set("Content-Type", "text/html") 1776 responseWriter.WriteHeader(http.StatusOK) 1777 1778 _, _ = responseWriter.Write([]byte(fmt.Sprintf(snapShotsTopTemplate, version.ProxyFSVersion, globals.ipAddrTCPPort, requestState.volume.name))) 1779 1780 for _, snapShot = range list { 1781 _, _ = responseWriter.Write([]byte(fmt.Sprintf(snapShotsPerSnapShotTemplate, snapShot.ID, snapShot.Time.Format(time.RFC3339), snapShot.Name))) 1782 } 1783 1784 _, _ = responseWriter.Write([]byte(fmt.Sprintf(snapShotsBottomTemplate, requestState.volume.name))) 1785 } 1786 } 1787 1788 func doPost(responseWriter http.ResponseWriter, request *http.Request) { 1789 switch { 1790 case strings.HasPrefix(request.URL.Path, "/deletions"): 1791 doPostOfDeletions(responseWriter, request) 1792 case strings.HasPrefix(request.URL.Path, "/trigger"): 1793 doPostOfTrigger(responseWriter, request) 1794 case strings.HasPrefix(request.URL.Path, "/volume"): 1795 doPostOfVolume(responseWriter, request) 1796 default: 1797 responseWriter.WriteHeader(http.StatusNotFound) 1798 } 1799 } 1800 1801 func doPostOfDeletions(responseWriter http.ResponseWriter, request *http.Request) { 1802 var ( 1803 numPathParts int 1804 pathSplit []string 1805 ) 1806 1807 pathSplit = strings.Split(request.URL.Path, "/") // leading "/" places "" in pathSplit[0] 1808 // pathSplit[1] should be "deletions" based on how we got here 1809 // trailing "/" places "" in pathSplit[len(pathSplit)-1] 1810 1811 numPathParts = len(pathSplit) - 1 1812 if "" == pathSplit[numPathParts] { 1813 numPathParts-- 1814 } 1815 1816 if "deletions" != pathSplit[1] { 1817 responseWriter.WriteHeader(http.StatusNotFound) 1818 return 1819 } 1820 1821 switch numPathParts { 1822 case 2: 1823 // Form: /deletions/{pause|resume} 1824 1825 switch pathSplit[2] { 1826 case "pause": 1827 headhunter.DisableObjectDeletions() 1828 responseWriter.WriteHeader(http.StatusNoContent) 1829 case "resume": 1830 headhunter.EnableObjectDeletions() 1831 responseWriter.WriteHeader(http.StatusNoContent) 1832 default: 1833 responseWriter.WriteHeader(http.StatusNotFound) 1834 } 1835 default: 1836 responseWriter.WriteHeader(http.StatusNotFound) 1837 } 1838 } 1839 1840 func doPostOfTrigger(responseWriter http.ResponseWriter, request *http.Request) { 1841 var ( 1842 err error 1843 haltTriggerCountAsString string 1844 haltTriggerCountAsU32 uint32 1845 haltTriggerCountAsU64 uint64 1846 haltTriggerString string 1847 numPathParts int 1848 pathSplit []string 1849 ) 1850 1851 pathSplit = strings.Split(request.URL.Path, "/") // leading "/" places "" in pathSplit[0] 1852 // pathSplit[1] should be "trigger" based on how we got here 1853 // trailing "/" places "" in pathSplit[len(pathSplit)-1] 1854 1855 numPathParts = len(pathSplit) - 1 1856 if "" == pathSplit[numPathParts] { 1857 numPathParts-- 1858 } 1859 1860 if "trigger" != pathSplit[1] { 1861 responseWriter.WriteHeader(http.StatusNotFound) 1862 return 1863 } 1864 1865 switch numPathParts { 1866 case 2: 1867 // Form: /trigger/<trigger-name> 1868 1869 haltTriggerString = pathSplit[2] 1870 1871 _, err = halter.Stat(haltTriggerString) 1872 if nil != err { 1873 responseWriter.WriteHeader(http.StatusNotFound) 1874 return 1875 } 1876 1877 haltTriggerCountAsString = request.FormValue("count") 1878 1879 haltTriggerCountAsU64, err = strconv.ParseUint(haltTriggerCountAsString, 10, 32) 1880 if nil != err { 1881 responseWriter.WriteHeader(http.StatusBadRequest) 1882 return 1883 } 1884 haltTriggerCountAsU32 = uint32(haltTriggerCountAsU64) 1885 1886 if 0 == haltTriggerCountAsU32 { 1887 halter.Disarm(haltTriggerString) 1888 } else { 1889 halter.Arm(haltTriggerString, haltTriggerCountAsU32) 1890 } 1891 1892 responseWriter.WriteHeader(http.StatusNoContent) 1893 default: 1894 responseWriter.WriteHeader(http.StatusNotFound) 1895 } 1896 } 1897 1898 func doPostOfVolume(responseWriter http.ResponseWriter, request *http.Request) { 1899 var ( 1900 acceptHeader string 1901 err error 1902 job *jobStruct 1903 jobAsValue sortedmap.Value 1904 jobID uint64 1905 jobType jobTypeType 1906 jobsCount int 1907 numPathParts int 1908 ok bool 1909 pathSplit []string 1910 volume *volumeStruct 1911 volumeAsValue sortedmap.Value 1912 volumeName string 1913 ) 1914 1915 pathSplit = strings.Split(request.URL.Path, "/") // leading "/" places "" in pathSplit[0] 1916 // pathSplit[1] should be "volume" based on how we got here 1917 // trailing "/" places "" in pathSplit[len(pathSplit)-1] 1918 1919 numPathParts = len(pathSplit) - 1 1920 if "" == pathSplit[numPathParts] { 1921 numPathParts-- 1922 } 1923 1924 if "volume" != pathSplit[1] { 1925 responseWriter.WriteHeader(http.StatusNotFound) 1926 return 1927 } 1928 1929 switch numPathParts { 1930 case 3: 1931 // Form: /volume/<volume-name>/fsck-job 1932 // Form: /volume/<volume-name>/scrub-job 1933 // Form: /volume/<volume-name>/snapshot 1934 case 4: 1935 // Form: /volume/<volume-name>/fsck-job/<job-id> 1936 // Form: /volume/<volume-name>/scrub-job/<job-id> 1937 default: 1938 responseWriter.WriteHeader(http.StatusNotFound) 1939 return 1940 } 1941 1942 volumeName = pathSplit[2] 1943 1944 volumeAsValue, ok, err = globals.volumeLLRB.GetByKey(volumeName) 1945 if nil != err { 1946 logger.Fatalf("HTTP Server Logic Error: %v", err) 1947 } 1948 if !ok { 1949 responseWriter.WriteHeader(http.StatusNotFound) 1950 return 1951 } 1952 volume = volumeAsValue.(*volumeStruct) 1953 1954 switch pathSplit[3] { 1955 case "fsck-job": 1956 jobType = fsckJobType 1957 case "scrub-job": 1958 jobType = scrubJobType 1959 case "snapshot": 1960 if 3 != numPathParts { 1961 responseWriter.WriteHeader(http.StatusNotFound) 1962 return 1963 } 1964 doPostOfSnapShot(responseWriter, request, volume) 1965 return 1966 default: 1967 responseWriter.WriteHeader(http.StatusNotFound) 1968 return 1969 } 1970 1971 volume.Lock() 1972 1973 if 3 == numPathParts { 1974 markJobsCompletedIfNoLongerActiveWhileLocked(volume) 1975 1976 if (nil != volume.fsckActiveJob) || (nil != volume.scrubActiveJob) { 1977 // Cannot start an FSCK or SCRUB job while either is active 1978 1979 volume.Unlock() 1980 responseWriter.WriteHeader(http.StatusPreconditionFailed) 1981 return 1982 } 1983 1984 for { 1985 switch jobType { 1986 case fsckJobType: 1987 jobsCount, err = volume.fsckJobs.Len() 1988 case scrubJobType: 1989 jobsCount, err = volume.scrubJobs.Len() 1990 } 1991 if nil != err { 1992 logger.Fatalf("HTTP Server Logic Error: %v", err) 1993 } 1994 1995 if jobsCount < int(globals.jobHistoryMaxSize) { 1996 break 1997 } 1998 1999 switch jobType { 2000 case fsckJobType: 2001 ok, err = volume.fsckJobs.DeleteByIndex(0) 2002 case scrubJobType: 2003 ok, err = volume.scrubJobs.DeleteByIndex(0) 2004 } 2005 if nil != err { 2006 logger.Fatalf("HTTP Server Logic Error: %v", err) 2007 } 2008 if !ok { 2009 switch jobType { 2010 case fsckJobType: 2011 err = fmt.Errorf("httpserver.doPostOfVolume() delete of oldest element of volume.fsckJobs failed") 2012 case scrubJobType: 2013 err = fmt.Errorf("httpserver.doPostOfVolume() delete of oldest element of volume.scrubJobs failed") 2014 } 2015 logger.Fatalf("HTTP Server Logic Error: %v", err) 2016 } 2017 } 2018 2019 job = &jobStruct{ 2020 volume: volume, 2021 state: jobRunning, 2022 startTime: time.Now(), 2023 } 2024 2025 job.id = volume.headhunterVolumeHandle.FetchNonce() 2026 2027 switch jobType { 2028 case fsckJobType: 2029 ok, err = volume.fsckJobs.Put(job.id, job) 2030 case scrubJobType: 2031 ok, err = volume.scrubJobs.Put(job.id, job) 2032 } 2033 if nil != err { 2034 logger.Fatalf("HTTP Server Logic Error: %v", err) 2035 } 2036 if !ok { 2037 switch jobType { 2038 case fsckJobType: 2039 err = fmt.Errorf("httpserver.doPostOfVolume() PUT to volume.fsckJobs failed") 2040 case scrubJobType: 2041 err = fmt.Errorf("httpserver.doPostOfVolume() PUT to volume.scrubJobs failed") 2042 } 2043 logger.Fatalf("HTTP Server Logic Error: %v", err) 2044 } 2045 2046 switch jobType { 2047 case fsckJobType: 2048 volume.fsckActiveJob = job 2049 2050 job.jobHandle = fs.ValidateVolume(volumeName) 2051 case scrubJobType: 2052 volume.scrubActiveJob = job 2053 2054 job.jobHandle = fs.ScrubVolume(volumeName) 2055 } 2056 2057 volume.Unlock() 2058 2059 switch jobType { 2060 case fsckJobType: 2061 responseWriter.Header().Set("Location", fmt.Sprintf("/volume/%v/fsck-job/%v", volumeName, job.id)) 2062 case scrubJobType: 2063 responseWriter.Header().Set("Location", fmt.Sprintf("/volume/%v/scrub-job/%v", volumeName, job.id)) 2064 } 2065 2066 acceptHeader = request.Header.Get("Accept") 2067 2068 if strings.Contains(acceptHeader, "text/html") { 2069 responseWriter.WriteHeader(http.StatusSeeOther) 2070 } else { 2071 responseWriter.WriteHeader(http.StatusCreated) 2072 } 2073 2074 return 2075 } 2076 2077 // If we reach here, numPathParts is 4 2078 2079 jobID, err = strconv.ParseUint(pathSplit[4], 10, 64) 2080 if nil != err { 2081 volume.Unlock() 2082 responseWriter.WriteHeader(http.StatusNotFound) 2083 return 2084 } 2085 2086 switch jobType { 2087 case fsckJobType: 2088 jobAsValue, ok, err = volume.fsckJobs.GetByKey(jobID) 2089 case scrubJobType: 2090 jobAsValue, ok, err = volume.scrubJobs.GetByKey(jobID) 2091 } 2092 if nil != err { 2093 logger.Fatalf("HTTP Server Logic Error: %v", err) 2094 } 2095 if !ok { 2096 volume.Unlock() 2097 responseWriter.WriteHeader(http.StatusNotFound) 2098 return 2099 } 2100 job = jobAsValue.(*jobStruct) 2101 2102 switch jobType { 2103 case fsckJobType: 2104 if volume.fsckActiveJob != job { 2105 volume.Unlock() 2106 responseWriter.WriteHeader(http.StatusPreconditionFailed) 2107 return 2108 } 2109 case scrubJobType: 2110 if volume.scrubActiveJob != job { 2111 volume.Unlock() 2112 responseWriter.WriteHeader(http.StatusPreconditionFailed) 2113 return 2114 } 2115 } 2116 2117 job.jobHandle.Cancel() 2118 2119 job.state = jobHalted 2120 job.endTime = time.Now() 2121 2122 switch jobType { 2123 case fsckJobType: 2124 volume.fsckActiveJob = nil 2125 case scrubJobType: 2126 volume.scrubActiveJob = nil 2127 } 2128 2129 volume.Unlock() 2130 2131 responseWriter.WriteHeader(http.StatusNoContent) 2132 } 2133 2134 func doPostOfSnapShot(responseWriter http.ResponseWriter, request *http.Request, volume *volumeStruct) { 2135 var ( 2136 err error 2137 snapShotID uint64 2138 ) 2139 2140 snapShotID, err = volume.inodeVolumeHandle.SnapShotCreate(request.FormValue("name")) 2141 if nil == err { 2142 responseWriter.Header().Set("Location", fmt.Sprintf("/volume/%v/snapshot/%v", volume.name, snapShotID)) 2143 2144 responseWriter.WriteHeader(http.StatusCreated) 2145 } else { 2146 responseWriter.WriteHeader(http.StatusConflict) 2147 } 2148 } 2149 2150 func sortedTwoColumnResponseWriter(llrb sortedmap.LLRBTree, responseWriter http.ResponseWriter) { 2151 var ( 2152 err error 2153 format string 2154 i int 2155 keyAsKey sortedmap.Key 2156 keyAsString string 2157 lenLLRB int 2158 line string 2159 longestKeyAsString int 2160 longestValueAsString int 2161 ok bool 2162 valueAsString string 2163 valueAsValue sortedmap.Value 2164 ) 2165 2166 lenLLRB, err = llrb.Len() 2167 if nil != err { 2168 err = fmt.Errorf("llrb.Len() failed: %v", err) 2169 logger.Fatalf("HTTP Server Logic Error: %v", err) 2170 } 2171 2172 responseWriter.Header().Set("Content-Type", "text/plain") 2173 responseWriter.WriteHeader(http.StatusOK) 2174 2175 longestKeyAsString = 0 2176 longestValueAsString = 0 2177 2178 for i = 0; i < lenLLRB; i++ { 2179 keyAsKey, valueAsValue, ok, err = llrb.GetByIndex(i) 2180 if nil != err { 2181 err = fmt.Errorf("llrb.GetByIndex(%v) failed: %v", i, err) 2182 logger.Fatalf("HTTP Server Logic Error: %v", err) 2183 } 2184 if !ok { 2185 err = fmt.Errorf("llrb.GetByIndex(%v) returned ok == false", i) 2186 logger.Fatalf("HTTP Server Logic Error: %v", err) 2187 } 2188 keyAsString = keyAsKey.(string) 2189 valueAsString = valueAsValue.(string) 2190 if len(keyAsString) > longestKeyAsString { 2191 longestKeyAsString = len(keyAsString) 2192 } 2193 if len(valueAsString) > longestValueAsString { 2194 longestValueAsString = len(valueAsString) 2195 } 2196 } 2197 2198 format = fmt.Sprintf("%%-%vs %%%vs\n", longestKeyAsString, longestValueAsString) 2199 2200 for i = 0; i < lenLLRB; i++ { 2201 keyAsKey, valueAsValue, ok, err = llrb.GetByIndex(i) 2202 if nil != err { 2203 err = fmt.Errorf("llrb.GetByIndex(%v) failed: %v", i, err) 2204 logger.Fatalf("HTTP Server Logic Error: %v", err) 2205 } 2206 if !ok { 2207 err = fmt.Errorf("llrb.GetByIndex(%v) returned ok == false", i) 2208 logger.Fatalf("HTTP Server Logic Error: %v", err) 2209 } 2210 keyAsString = keyAsKey.(string) 2211 valueAsString = valueAsValue.(string) 2212 line = fmt.Sprintf(format, keyAsString, valueAsString) 2213 _, _ = responseWriter.Write([]byte(line)) 2214 } 2215 } 2216 2217 func markJobsCompletedIfNoLongerActiveWhileLocked(volume *volumeStruct) { 2218 // First, mark as finished now any FSCK/SCRUB job 2219 2220 if (nil != volume.fsckActiveJob) && !volume.fsckActiveJob.jobHandle.Active() { 2221 // FSCK job finished at some point... make it look like it just finished now 2222 2223 volume.fsckActiveJob.state = jobCompleted 2224 volume.fsckActiveJob.endTime = time.Now() 2225 volume.fsckActiveJob = nil 2226 } 2227 2228 if (nil != volume.scrubActiveJob) && !volume.scrubActiveJob.jobHandle.Active() { 2229 // SCRUB job finished at some point... make it look like it just finished now 2230 2231 volume.scrubActiveJob.state = jobCompleted 2232 volume.scrubActiveJob.endTime = time.Now() 2233 volume.scrubActiveJob = nil 2234 } 2235 } 2236 2237 func doPut(responseWriter http.ResponseWriter, request *http.Request) { 2238 switch { 2239 case strings.HasPrefix(request.URL.Path, "/volume"): 2240 doPutOfVolume(responseWriter, request) 2241 default: 2242 responseWriter.WriteHeader(http.StatusNotFound) 2243 } 2244 } 2245 2246 func doPutOfVolume(responseWriter http.ResponseWriter, request *http.Request) { 2247 var ( 2248 err error 2249 numPathParts int 2250 ok bool 2251 pathSplit []string 2252 requestState *requestStateStruct 2253 volumeAsValue sortedmap.Value 2254 volumeName string 2255 ) 2256 2257 pathSplit = strings.Split(request.URL.Path, "/") // leading "/" places "" in pathSplit[0] 2258 // pathSplit[1] should be "volume" based on how we got here 2259 // trailing "/" places "" in pathSplit[len(pathSplit)-1] 2260 2261 numPathParts = len(pathSplit) - 1 2262 if "" == pathSplit[numPathParts] { 2263 numPathParts-- 2264 } 2265 2266 if "volume" != pathSplit[1] { 2267 responseWriter.WriteHeader(http.StatusNotFound) 2268 return 2269 } 2270 2271 switch numPathParts { 2272 case 3: 2273 // Form: /volume/<volume-name>/replace-dir-entries 2274 default: 2275 responseWriter.WriteHeader(http.StatusNotFound) 2276 return 2277 } 2278 2279 volumeName = pathSplit[2] 2280 2281 volumeAsValue, ok, err = globals.volumeLLRB.GetByKey(volumeName) 2282 if nil != err { 2283 logger.Fatalf("HTTP Server Logic Error: %v", err) 2284 } 2285 if !ok { 2286 responseWriter.WriteHeader(http.StatusNotFound) 2287 return 2288 } 2289 2290 requestState = &requestStateStruct{ 2291 volume: volumeAsValue.(*volumeStruct), 2292 } 2293 2294 switch pathSplit[3] { 2295 case "replace-dir-entries": 2296 doReplaceDirEntries(responseWriter, request, requestState) 2297 default: 2298 responseWriter.WriteHeader(http.StatusNotFound) 2299 return 2300 } 2301 } 2302 2303 // doReplaceDirEntries expects the instructions for how to replace a DirInode's directory entries 2304 // to be passed entirely in request.Body in the following form: 2305 // 2306 // (<parentDirInodeNumber>) <parentDirEntryBasename> => <dirInodeNumber> 2307 // <dirEntryInodeNumber A> 2308 // <dirEntryInodeNumber B> 2309 // ... 2310 // <dirEntryInodeNumber Z> 2311 // 2312 // where each InodeNumber is a 16 Hex Digit string. The list should not include "." nor "..". If 2313 // successful, the DirInode's directory entries will be replaced by: 2314 // 2315 // "." => <dirInodeNumber> 2316 // ".." => <parentDirInodeNumber> 2317 // <dirEntryInodeNumber A as 16 Hex Digits string> => <dirEntryInodeNumber A> 2318 // <dirEntryInodeNumber B as 16 Hex Digits string> => <dirEntryInodeNumber B> 2319 // ... 2320 // <dirEntryInodeNumber Z as 16 Hex Digits string> => <dirEntryInodeNumber Z> 2321 // 2322 // In addition, the LinkCount for the DirInode will be properly set to 2 + the number of 2323 // dirEntryInodeNumber's that refer to subdirectories. 2324 // 2325 func doReplaceDirEntries(responseWriter http.ResponseWriter, request *http.Request, requestState *requestStateStruct) { 2326 var ( 2327 dirEntryInodeNumber inode.InodeNumber 2328 dirEntryInodeNumbers []inode.InodeNumber 2329 dirInodeNumber inode.InodeNumber 2330 err error 2331 inodeNumberAsUint64 uint64 2332 maxNumberOfDirEntryInodeNumbers int 2333 parentDirEntryBasename string 2334 parentDirInodeNumber inode.InodeNumber 2335 requestBody []byte 2336 ) 2337 2338 requestBody, err = ioutil.ReadAll(request.Body) 2339 if nil != err { 2340 _ = request.Body.Close() 2341 responseWriter.WriteHeader(http.StatusBadRequest) 2342 return 2343 } 2344 2345 err = request.Body.Close() 2346 if nil != err { 2347 responseWriter.WriteHeader(http.StatusBadRequest) 2348 return 2349 } 2350 2351 // Parse parentDirInodeNumber first since it is in a fixed location 2352 2353 if (24 > len(requestBody)) || ('(' != requestBody[0]) || (')' != requestBody[17]) || (' ' != requestBody[18]) { 2354 responseWriter.WriteHeader(http.StatusBadRequest) 2355 return 2356 } 2357 2358 inodeNumberAsUint64, err = strconv.ParseUint(string(requestBody[1:17]), 16, 64) 2359 if nil != err { 2360 responseWriter.WriteHeader(http.StatusBadRequest) 2361 return 2362 } 2363 2364 parentDirInodeNumber = inode.InodeNumber(inodeNumberAsUint64) 2365 2366 requestBody = requestBody[19:] 2367 2368 // Parse backwards reading dirEntryInodeNumbers until hitting one preceeded by " => " 2369 2370 maxNumberOfDirEntryInodeNumbers = (len(requestBody) + 15) / 16 2371 dirEntryInodeNumbers = make([]inode.InodeNumber, 0, maxNumberOfDirEntryInodeNumbers) 2372 2373 for { 2374 if 21 > len(requestBody) { 2375 responseWriter.WriteHeader(http.StatusBadRequest) 2376 return 2377 } 2378 2379 if " => " == string(requestBody[len(requestBody)-20:len(requestBody)-16]) { 2380 break 2381 } 2382 2383 inodeNumberAsUint64, err = strconv.ParseUint(string(requestBody[len(requestBody)-16:]), 16, 64) 2384 if nil != err { 2385 responseWriter.WriteHeader(http.StatusBadRequest) 2386 return 2387 } 2388 2389 dirEntryInodeNumber = inode.InodeNumber(inodeNumberAsUint64) 2390 dirEntryInodeNumbers = append(dirEntryInodeNumbers, dirEntryInodeNumber) 2391 2392 requestBody = requestBody[:len(requestBody)-16] 2393 } 2394 2395 // Parse dirInodeNumber 2396 2397 inodeNumberAsUint64, err = strconv.ParseUint(string(requestBody[len(requestBody)-16:]), 16, 64) 2398 if nil != err { 2399 responseWriter.WriteHeader(http.StatusBadRequest) 2400 return 2401 } 2402 2403 dirInodeNumber = inode.InodeNumber(inodeNumberAsUint64) 2404 2405 requestBody = requestBody[:len(requestBody)-20] 2406 2407 // What remains is parentDirEntryBasename 2408 2409 parentDirEntryBasename = string(requestBody[:]) 2410 2411 // Now we can finally proceed 2412 2413 err = requestState.volume.inodeVolumeHandle.ReplaceDirEntries(parentDirInodeNumber, parentDirEntryBasename, dirInodeNumber, dirEntryInodeNumbers) 2414 if nil == err { 2415 responseWriter.WriteHeader(http.StatusCreated) 2416 } else { 2417 responseWriter.WriteHeader(http.StatusBadRequest) 2418 } 2419 }