2013-06-05 17:18:27 +00:00
|
|
|
/*
|
|
|
|
Copyright 2013 The Camlistore Authors.
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package server
|
|
|
|
|
|
|
|
import (
|
2014-03-17 03:13:47 +00:00
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"html"
|
|
|
|
"log"
|
2013-06-05 17:18:27 +00:00
|
|
|
"net/http"
|
2014-08-29 23:03:44 +00:00
|
|
|
"os"
|
2014-08-29 21:11:58 +00:00
|
|
|
"reflect"
|
2014-03-18 04:17:46 +00:00
|
|
|
"regexp"
|
2014-03-17 03:13:47 +00:00
|
|
|
"strings"
|
2014-08-29 21:11:58 +00:00
|
|
|
"time"
|
2013-06-05 17:18:27 +00:00
|
|
|
|
|
|
|
"camlistore.org/pkg/blobserver"
|
|
|
|
"camlistore.org/pkg/buildinfo"
|
|
|
|
"camlistore.org/pkg/httputil"
|
2014-03-17 03:13:47 +00:00
|
|
|
"camlistore.org/pkg/index"
|
2013-06-05 17:18:27 +00:00
|
|
|
"camlistore.org/pkg/jsonconfig"
|
2014-08-29 21:11:58 +00:00
|
|
|
"camlistore.org/pkg/search"
|
2014-08-29 23:03:44 +00:00
|
|
|
"camlistore.org/pkg/types/camtypes"
|
2013-06-05 17:18:27 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// StatusHandler publishes server status information.
|
|
|
|
type StatusHandler struct {
|
2014-03-17 03:13:47 +00:00
|
|
|
prefix string
|
|
|
|
handlerFinder blobserver.FindHandlerByTyper
|
2013-06-05 17:18:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
blobserver.RegisterHandlerConstructor("status", newStatusFromConfig)
|
|
|
|
}
|
|
|
|
|
2014-08-29 21:11:58 +00:00
|
|
|
var _ blobserver.HandlerIniter = (*StatusHandler)(nil)
|
|
|
|
|
2013-06-05 17:18:27 +00:00
|
|
|
func newStatusFromConfig(ld blobserver.Loader, conf jsonconfig.Obj) (h http.Handler, err error) {
|
2013-09-22 23:03:47 +00:00
|
|
|
if err := conf.Validate(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-03-17 03:13:47 +00:00
|
|
|
return &StatusHandler{
|
|
|
|
prefix: ld.MyPrefix(),
|
|
|
|
handlerFinder: ld,
|
|
|
|
}, nil
|
2013-06-05 17:18:27 +00:00
|
|
|
}
|
|
|
|
|
2014-08-29 21:11:58 +00:00
|
|
|
func (sh *StatusHandler) InitHandler(hl blobserver.FindHandlerByTyper) error {
|
|
|
|
_, h, err := hl.FindHandlerByType("search")
|
|
|
|
if err == blobserver.ErrHandlerTypeNotFound {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
go func() {
|
|
|
|
var lastSend *status
|
|
|
|
for {
|
|
|
|
cur := sh.currentStatus()
|
|
|
|
if reflect.DeepEqual(cur, lastSend) {
|
|
|
|
// TODO: something better. get notified on interesting events.
|
|
|
|
time.Sleep(10 * time.Second)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
lastSend = cur
|
|
|
|
js, _ := json.MarshalIndent(cur, "", " ")
|
|
|
|
h.(*search.Handler).SendStatusUpdate(js)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2013-06-05 17:18:27 +00:00
|
|
|
func (sh *StatusHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
2013-06-12 09:17:30 +00:00
|
|
|
suffix := httputil.PathSuffix(req)
|
2014-03-17 03:13:47 +00:00
|
|
|
if !httputil.IsGet(req) {
|
|
|
|
http.Error(rw, "Illegal status method.", http.StatusMethodNotAllowed)
|
2013-06-09 17:25:00 +00:00
|
|
|
return
|
2013-06-05 17:18:27 +00:00
|
|
|
}
|
2014-03-17 03:13:47 +00:00
|
|
|
switch suffix {
|
|
|
|
case "status.json":
|
|
|
|
sh.serveStatusJSON(rw, req)
|
|
|
|
case "":
|
|
|
|
sh.serveStatusHTML(rw, req)
|
|
|
|
default:
|
|
|
|
http.Error(rw, "Illegal status path.", 404)
|
2013-06-05 17:18:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-03-17 03:13:47 +00:00
|
|
|
type status struct {
|
|
|
|
Version string `json:"version"`
|
2014-08-29 23:03:44 +00:00
|
|
|
Errors []camtypes.StatusError `json:"errors,omitempty"`
|
2014-03-18 04:17:46 +00:00
|
|
|
Sync map[string]syncStatus `json:"sync"`
|
2014-03-17 03:13:47 +00:00
|
|
|
Storage map[string]storageStatus `json:"storage"`
|
|
|
|
rootPrefix string
|
2014-08-16 18:58:16 +00:00
|
|
|
|
|
|
|
ImporterRoot string `json:"importerRoot"`
|
|
|
|
ImporterAccounts interface{} `json:"importerAccounts"`
|
2014-03-17 03:13:47 +00:00
|
|
|
}
|
|
|
|
|
2014-08-29 23:03:44 +00:00
|
|
|
func (st *status) addError(msg, url string) {
|
|
|
|
st.Errors = append(st.Errors, camtypes.StatusError{
|
|
|
|
Error: msg,
|
|
|
|
URL: url,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2014-03-18 04:17:46 +00:00
|
|
|
func (st *status) isHandler(pfx string) bool {
|
2014-08-16 18:58:16 +00:00
|
|
|
if pfx == st.ImporterRoot {
|
|
|
|
return true
|
|
|
|
}
|
2014-03-18 04:17:46 +00:00
|
|
|
if _, ok := st.Sync[pfx]; ok {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
if _, ok := st.Storage[pfx]; ok {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2014-03-17 03:13:47 +00:00
|
|
|
type storageStatus struct {
|
|
|
|
Primary bool `json:"primary,omitempty"`
|
|
|
|
IsIndex bool `json:"isIndex,omitempty"`
|
|
|
|
Type string `json:"type"`
|
2014-08-29 21:11:58 +00:00
|
|
|
ApproxBlobs int `json:"approxBlobs,omitempty"`
|
|
|
|
ApproxBytes int `json:"approxBytes,omitempty"`
|
2014-03-17 03:13:47 +00:00
|
|
|
ImplStatus interface{} `json:"implStatus,omitempty"`
|
2013-06-05 17:18:27 +00:00
|
|
|
}
|
|
|
|
|
2014-03-17 03:13:47 +00:00
|
|
|
func (sh *StatusHandler) currentStatus() *status {
|
|
|
|
res := &status{
|
2013-06-09 17:25:00 +00:00
|
|
|
Version: buildinfo.Version(),
|
2014-03-17 03:13:47 +00:00
|
|
|
Storage: make(map[string]storageStatus),
|
2014-03-18 04:17:46 +00:00
|
|
|
Sync: make(map[string]syncStatus),
|
2014-03-17 03:13:47 +00:00
|
|
|
}
|
2014-08-29 23:03:44 +00:00
|
|
|
if v := os.Getenv("CAMLI_FAKE_STATUS_ERROR"); v != "" {
|
|
|
|
res.addError(v, "/status/#fakeerror")
|
|
|
|
}
|
2014-03-17 03:13:47 +00:00
|
|
|
_, hi, err := sh.handlerFinder.FindHandlerByType("root")
|
|
|
|
if err != nil {
|
2014-08-29 23:03:44 +00:00
|
|
|
res.addError(fmt.Sprintf("Error finding root handler: %v", err), "")
|
2014-03-17 03:13:47 +00:00
|
|
|
return res
|
|
|
|
}
|
|
|
|
rh := hi.(*RootHandler)
|
|
|
|
res.rootPrefix = rh.Prefix
|
|
|
|
|
2014-08-16 18:58:16 +00:00
|
|
|
if pfx, h, err := sh.handlerFinder.FindHandlerByType("importer"); err == nil {
|
|
|
|
res.ImporterRoot = pfx
|
|
|
|
as := h.(interface {
|
2014-08-29 23:03:44 +00:00
|
|
|
AccountsStatus() (interface{}, []camtypes.StatusError)
|
2014-08-16 18:58:16 +00:00
|
|
|
})
|
2014-08-29 23:03:44 +00:00
|
|
|
var errs []camtypes.StatusError
|
|
|
|
res.ImporterAccounts, errs = as.AccountsStatus()
|
|
|
|
res.Errors = append(res.Errors, errs...)
|
2014-08-16 18:58:16 +00:00
|
|
|
}
|
|
|
|
|
2014-03-17 03:13:47 +00:00
|
|
|
types, handlers := sh.handlerFinder.AllHandlers()
|
|
|
|
|
2014-03-18 04:17:46 +00:00
|
|
|
// Sync
|
|
|
|
for pfx, h := range handlers {
|
|
|
|
sh, ok := h.(*SyncHandler)
|
|
|
|
if !ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
res.Sync[pfx] = sh.currentStatus()
|
|
|
|
}
|
|
|
|
|
2014-03-17 03:13:47 +00:00
|
|
|
// Storage
|
|
|
|
for pfx, typ := range types {
|
|
|
|
if !strings.HasPrefix(typ, "storage-") {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
h := handlers[pfx]
|
|
|
|
_, isIndex := h.(*index.Index)
|
|
|
|
res.Storage[pfx] = storageStatus{
|
|
|
|
Type: strings.TrimPrefix(typ, "storage-"),
|
|
|
|
Primary: pfx == rh.BlobRoot,
|
|
|
|
IsIndex: isIndex,
|
|
|
|
}
|
2013-06-05 17:18:27 +00:00
|
|
|
}
|
|
|
|
|
2014-03-17 03:13:47 +00:00
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
|
|
|
func (sh *StatusHandler) serveStatusJSON(rw http.ResponseWriter, req *http.Request) {
|
|
|
|
httputil.ReturnJSON(rw, sh.currentStatus())
|
|
|
|
}
|
|
|
|
|
2014-03-18 04:17:46 +00:00
|
|
|
var quotedPrefix = regexp.MustCompile(`[;"]/(\S+?/)[&"]`)
|
|
|
|
|
2014-03-17 03:13:47 +00:00
|
|
|
func (sh *StatusHandler) serveStatusHTML(rw http.ResponseWriter, req *http.Request) {
|
|
|
|
st := sh.currentStatus()
|
|
|
|
f := func(p string, a ...interface{}) {
|
|
|
|
fmt.Fprintf(rw, p, a...)
|
|
|
|
}
|
|
|
|
f("<html><head><title>Status</title></head>")
|
|
|
|
f("<body><h2>Status</h2>")
|
|
|
|
f("<p>As JSON: <a href='status.json'>status.json</a>; and the <a href='%s?camli.mode=config'>discovery JSON</a>.</p>", st.rootPrefix)
|
|
|
|
f("<p>Not yet pretty HTML UI:</p>")
|
|
|
|
js, err := json.MarshalIndent(st, "", " ")
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("JSON marshal error: %v", err)
|
|
|
|
}
|
2014-03-18 04:17:46 +00:00
|
|
|
jsh := html.EscapeString(string(js))
|
|
|
|
jsh = quotedPrefix.ReplaceAllStringFunc(jsh, func(in string) string {
|
|
|
|
pfx := in[1 : len(in)-1]
|
|
|
|
if st.isHandler(pfx) {
|
|
|
|
return fmt.Sprintf("%s<a href='%s'>%s</a>%s", in[:1], pfx, pfx, in[len(in)-1:])
|
|
|
|
}
|
|
|
|
return in
|
|
|
|
})
|
|
|
|
f("<pre>%s</pre>", jsh)
|
2013-06-05 17:18:27 +00:00
|
|
|
}
|