зеркало из https://github.com/github/vitess-gh.git
vtctld: Clean out old UI.
This commit is contained in:
Родитель
de025e56bf
Коммит
993663574f
|
@ -19,7 +19,6 @@ cp -R base/vt/dist lite/vt/
|
|||
|
||||
mkdir -p $lite/$vttop/go/cmd/vtctld
|
||||
mkdir -p $lite/$vttop/web
|
||||
cp -R base/$vttop/go/cmd/vtctld/templates $lite/$vttop/go/cmd/vtctld/
|
||||
cp -R base/$vttop/web/vtctld $lite/$vttop/web/
|
||||
|
||||
mkdir -p $lite/$vttop/config
|
||||
|
|
|
@ -41,7 +41,6 @@ spec:
|
|||
chown -R vitess /vt &&
|
||||
su -p -c "/vt/bin/vtctld
|
||||
-debug
|
||||
-templates $VTTOP/go/cmd/vtctld/templates
|
||||
-web_dir $VTTOP/web/vtctld
|
||||
-log_dir $VTDATAROOT/tmp
|
||||
-alsologtostderr
|
||||
|
|
|
@ -11,7 +11,7 @@ script_root=`dirname "${BASH_SOURCE}"`
|
|||
source $script_root/env.sh
|
||||
|
||||
echo "Starting vtctld..."
|
||||
$VTROOT/bin/vtctld -debug -templates $VTTOP/go/cmd/vtctld/templates \
|
||||
$VTROOT/bin/vtctld -debug \
|
||||
-web_dir $VTTOP/web/vtctld \
|
||||
-tablet_protocol grpc \
|
||||
-tablet_manager_protocol grpc \
|
||||
|
|
|
@ -8,11 +8,10 @@ import (
|
|||
"strings"
|
||||
"sync"
|
||||
|
||||
log "github.com/golang/glog"
|
||||
"github.com/youtube/vitess/go/cmd/vtctld/proto"
|
||||
"github.com/youtube/vitess/go/vt/topo/topoproto"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
topodatapb "github.com/youtube/vitess/go/vt/proto/topodata"
|
||||
"github.com/youtube/vitess/go/vt/topo"
|
||||
"github.com/youtube/vitess/go/vt/topo/topoproto"
|
||||
)
|
||||
|
||||
// Explorer allows exploring a topology server.
|
||||
|
@ -20,38 +19,7 @@ type Explorer interface {
|
|||
// HandlePath returns a result (suitable to be passed to a
|
||||
// template) appropriate for url, using actionRepo to populate
|
||||
// the actions in result.
|
||||
HandlePath(actionRepo proto.ActionRepository, url string, r *http.Request) interface{}
|
||||
|
||||
// GetKeyspacePath returns an explorer path that will contain
|
||||
// information about the named keyspace.
|
||||
GetKeyspacePath(keyspace string) string
|
||||
|
||||
// GetShardPath returns an explorer path that will contain
|
||||
// information about the named shard in the named keyspace.
|
||||
GetShardPath(keyspace, shard string) string
|
||||
|
||||
// GetSrvKeyspacePath returns an explorer path that will
|
||||
// contain information about the named keyspace in the serving
|
||||
// graph for cell.
|
||||
GetSrvKeyspacePath(cell, keyspace string) string
|
||||
|
||||
// GetShardPath returns an explorer path that will contain
|
||||
// information about the named shard in the named keyspace in
|
||||
// the serving graph for cell.
|
||||
GetSrvShardPath(cell, keyspace, shard string) string
|
||||
|
||||
// GetShardTypePath returns an explorer path that will contain
|
||||
// information about the named tablet type in the named shard
|
||||
// in the named keyspace in the serving graph for cell.
|
||||
GetSrvTypePath(cell, keyspace, shard string, tabletType topodatapb.TabletType) string
|
||||
|
||||
// GetTabletPath returns an explorer path that will contain
|
||||
// information about the tablet named by alias.
|
||||
GetTabletPath(alias *topodatapb.TabletAlias) string
|
||||
|
||||
// GetReplicationSlaves returns an explorer path that contains
|
||||
// replication slaves for the named cell, keyspace, and shard.
|
||||
GetReplicationSlaves(cell, keyspace, shard string) string
|
||||
HandlePath(url string, r *http.Request) interface{}
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -75,35 +43,22 @@ func HandleExplorer(name, url, templateName string, exp Explorer) {
|
|||
|
||||
// Topo explorer API for client-side vtctld app.
|
||||
handleCollection("topodata", func(r *http.Request) (interface{}, error) {
|
||||
return exp.HandlePath(actionRepo, path.Clean(url+getItemPath(r.URL.Path)), r), nil
|
||||
return exp.HandlePath(path.Clean(url+getItemPath(r.URL.Path)), r), nil
|
||||
})
|
||||
|
||||
// Old server-side explorer.
|
||||
explorer = exp
|
||||
explorerName = name
|
||||
indexContent.ToplevelLinks[name+" Explorer"] = url
|
||||
http.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
httpErrorf(w, r, "cannot parse form: %s", err)
|
||||
return
|
||||
}
|
||||
topoPath := r.URL.Path[strings.Index(r.URL.Path, url):]
|
||||
if cleanPath := path.Clean(topoPath); topoPath != cleanPath && topoPath != cleanPath+"/" {
|
||||
log.Infof("redirecting to %v", cleanPath)
|
||||
http.Redirect(w, r, cleanPath, http.StatusTemporaryRedirect)
|
||||
return
|
||||
}
|
||||
|
||||
if strings.HasSuffix(topoPath, "/") {
|
||||
topoPath = topoPath[:len(topoPath)-1]
|
||||
}
|
||||
result := explorer.HandlePath(actionRepo, topoPath, r)
|
||||
templateLoader.ServeTemplate(templateName, result, w, r)
|
||||
// Get the part after the prefix url.
|
||||
topoPath := r.URL.Path[strings.Index(r.URL.Path, url)+len(url):]
|
||||
// Redirect to the new client-side topo browser.
|
||||
http.Redirect(w, r, appPrefix+"#"+path.Join("/topo", topoPath), http.StatusFound)
|
||||
})
|
||||
}
|
||||
|
||||
// handleExplorerRedirect returns the redirect target URL.
|
||||
func handleExplorerRedirect(r *http.Request) (string, error) {
|
||||
func handleExplorerRedirect(ctx context.Context, ts topo.Server, r *http.Request) (string, error) {
|
||||
keyspace := r.FormValue("keyspace")
|
||||
shard := r.FormValue("shard")
|
||||
cell := r.FormValue("cell")
|
||||
|
@ -113,32 +68,28 @@ func handleExplorerRedirect(r *http.Request) (string, error) {
|
|||
if keyspace == "" {
|
||||
return "", errors.New("keyspace is required for this redirect")
|
||||
}
|
||||
return explorer.GetKeyspacePath(keyspace), nil
|
||||
return appPrefix + "#/keyspaces/", nil
|
||||
case "shard":
|
||||
if keyspace == "" || shard == "" {
|
||||
return "", errors.New("keyspace and shard are required for this redirect")
|
||||
}
|
||||
return explorer.GetShardPath(keyspace, shard), nil
|
||||
return appPrefix + fmt.Sprintf("#/shard/%s/%s", keyspace, shard), nil
|
||||
case "srv_keyspace":
|
||||
if keyspace == "" || cell == "" {
|
||||
return "", errors.New("keyspace and cell are required for this redirect")
|
||||
}
|
||||
return explorer.GetSrvKeyspacePath(cell, keyspace), nil
|
||||
return appPrefix + "#/keyspaces/", nil
|
||||
case "srv_shard":
|
||||
if keyspace == "" || shard == "" || cell == "" {
|
||||
return "", errors.New("keyspace, shard, and cell are required for this redirect")
|
||||
}
|
||||
return explorer.GetSrvShardPath(cell, keyspace, shard), nil
|
||||
return appPrefix + fmt.Sprintf("#/shard/%s/%s", keyspace, shard), nil
|
||||
case "srv_type":
|
||||
tabletType := r.FormValue("tablet_type")
|
||||
if keyspace == "" || shard == "" || cell == "" || tabletType == "" {
|
||||
return "", errors.New("keyspace, shard, cell, and tablet_type are required for this redirect")
|
||||
}
|
||||
tt, err := topoproto.ParseTabletType(tabletType)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("cannot parse tablet type %v: %v", tabletType, err)
|
||||
}
|
||||
return explorer.GetSrvTypePath(cell, keyspace, shard, tt), nil
|
||||
return appPrefix + fmt.Sprintf("#/shard/%s/%s", keyspace, shard), nil
|
||||
case "tablet":
|
||||
alias := r.FormValue("alias")
|
||||
if alias == "" {
|
||||
|
@ -148,12 +99,16 @@ func handleExplorerRedirect(r *http.Request) (string, error) {
|
|||
if err != nil {
|
||||
return "", fmt.Errorf("bad tablet alias %q: %v", alias, err)
|
||||
}
|
||||
return explorer.GetTabletPath(tabletAlias), nil
|
||||
ti, err := ts.GetTablet(ctx, tabletAlias)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("can't get tablet %q: %v", alias, err)
|
||||
}
|
||||
return appPrefix + fmt.Sprintf("#/shard/%s/%s", ti.Keyspace, ti.Shard), nil
|
||||
case "replication":
|
||||
if keyspace == "" || shard == "" || cell == "" {
|
||||
return "", errors.New("keyspace, shard, and cell are required for this redirect")
|
||||
}
|
||||
return explorer.GetReplicationSlaves(cell, keyspace, shard), nil
|
||||
return appPrefix + fmt.Sprintf("#/shard/%s/%s", keyspace, shard), nil
|
||||
default:
|
||||
return "", errors.New("bad redirect type")
|
||||
}
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
// Copyright 2015, Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
topodatapb "github.com/youtube/vitess/go/vt/proto/topodata"
|
||||
"github.com/youtube/vitess/go/vt/zktopo"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func TestHandleExplorerRedirect(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
ts := zktopo.NewTestServer(t, []string{"cell1"})
|
||||
if err := ts.CreateTablet(ctx, &topodatapb.Tablet{
|
||||
Alias: &topodatapb.TabletAlias{
|
||||
Cell: "cell1",
|
||||
Uid: 123,
|
||||
},
|
||||
Keyspace: "test_keyspace",
|
||||
Shard: "123-456",
|
||||
}); err != nil {
|
||||
t.Fatalf("CreateTablet failed: %v", err)
|
||||
}
|
||||
|
||||
table := map[string]string{
|
||||
"/explorers/redirect?type=keyspace&keyspace=test_keyspace": "/app/#/keyspaces/",
|
||||
"/explorers/redirect?type=shard&keyspace=test_keyspace&shard=-80": "/app/#/shard/test_keyspace/-80",
|
||||
"/explorers/redirect?type=srv_keyspace&keyspace=test_keyspace&cell=cell1": "/app/#/keyspaces/",
|
||||
"/explorers/redirect?type=srv_shard&keyspace=test_keyspace&shard=-80&cell=cell1": "/app/#/shard/test_keyspace/-80",
|
||||
"/explorers/redirect?type=srv_type&keyspace=test_keyspace&shard=-80&cell=cell1&tablet_type=replica": "/app/#/shard/test_keyspace/-80",
|
||||
"/explorers/redirect?type=tablet&alias=cell1-123": "/app/#/shard/test_keyspace/123-456",
|
||||
"/explorers/redirect?type=replication&keyspace=test_keyspace&shard=-80&cell=cell1": "/app/#/shard/test_keyspace/-80",
|
||||
}
|
||||
|
||||
for input, want := range table {
|
||||
request, err := http.NewRequest("GET", input, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("NewRequest error: %v", err)
|
||||
}
|
||||
if err := request.ParseForm(); err != nil {
|
||||
t.Fatalf("ParseForm error: %v", err)
|
||||
}
|
||||
got, err := handleExplorerRedirect(ctx, ts, request)
|
||||
if err != nil {
|
||||
t.Fatalf("handleExplorerRedirect error: %v", err)
|
||||
}
|
||||
if got != want {
|
||||
t.Errorf("handlExplorerRedirect(%#v) = %#v, want %#v", input, got, want)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,23 +7,13 @@ package main
|
|||
// Imports and register the Zookeeper TopologyServer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/youtube/vitess/go/cmd/vtctld/proto"
|
||||
"github.com/youtube/vitess/go/netutil"
|
||||
"github.com/youtube/vitess/go/vt/servenv"
|
||||
"github.com/youtube/vitess/go/vt/topo"
|
||||
"github.com/youtube/vitess/go/vt/topo/topoproto"
|
||||
"github.com/youtube/vitess/go/vt/zktopo"
|
||||
"github.com/youtube/vitess/go/zk"
|
||||
|
||||
topodatapb "github.com/youtube/vitess/go/vt/proto/topodata"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -45,43 +35,8 @@ func NewZkExplorer(zconn zk.Conn) *ZkExplorer {
|
|||
return &ZkExplorer{zconn}
|
||||
}
|
||||
|
||||
// GetKeyspacePath is part of the Explorer interface
|
||||
func (ex ZkExplorer) GetKeyspacePath(keyspace string) string {
|
||||
return path.Join("/zk/global/vt/keyspaces", keyspace)
|
||||
}
|
||||
|
||||
// GetShardPath is part of the Explorer interface
|
||||
func (ex ZkExplorer) GetShardPath(keyspace, shard string) string {
|
||||
return path.Join("/zk/global/vt/keyspaces", keyspace, "shards", shard)
|
||||
}
|
||||
|
||||
// GetSrvKeyspacePath is part of the Explorer interface
|
||||
func (ex ZkExplorer) GetSrvKeyspacePath(cell, keyspace string) string {
|
||||
return path.Join("/zk", cell, "vt/ns", keyspace)
|
||||
}
|
||||
|
||||
// GetSrvShardPath is part of the Explorer interface
|
||||
func (ex ZkExplorer) GetSrvShardPath(cell, keyspace, shard string) string {
|
||||
return path.Join("/zk", cell, "/vt/ns", keyspace, shard)
|
||||
}
|
||||
|
||||
// GetSrvTypePath is part of the Explorer interface
|
||||
func (ex ZkExplorer) GetSrvTypePath(cell, keyspace, shard string, tabletType topodatapb.TabletType) string {
|
||||
return path.Join("/zk", cell, "/vt/ns", keyspace, shard, strings.ToLower(tabletType.String()))
|
||||
}
|
||||
|
||||
// GetTabletPath is part of the Explorer interface
|
||||
func (ex ZkExplorer) GetTabletPath(alias *topodatapb.TabletAlias) string {
|
||||
return path.Join("/zk", alias.Cell, "vt/tablets", topoproto.TabletAliasUIDStr(alias))
|
||||
}
|
||||
|
||||
// GetReplicationSlaves is part of the Explorer interface
|
||||
func (ex ZkExplorer) GetReplicationSlaves(cell, keyspace, shard string) string {
|
||||
return path.Join("/zk", cell, "vt/replication", keyspace, shard)
|
||||
}
|
||||
|
||||
// HandlePath is part of the Explorer interface
|
||||
func (ex ZkExplorer) HandlePath(actionRepo proto.ActionRepository, zkPath string, r *http.Request) interface{} {
|
||||
func (ex ZkExplorer) HandlePath(zkPath string, r *http.Request) interface{} {
|
||||
result := NewZkResult(zkPath)
|
||||
|
||||
if zkPath == "/zk" {
|
||||
|
@ -103,20 +58,6 @@ func (ex ZkExplorer) HandlePath(actionRepo proto.ActionRepository, zkPath string
|
|||
result.Error = err.Error()
|
||||
return result
|
||||
}
|
||||
if m, _ := path.Match("/zk/global/vt/keyspaces/*", zkPath); m {
|
||||
keyspace := path.Base(zkPath)
|
||||
actionRepo.PopulateKeyspaceActions(result.Actions, keyspace)
|
||||
} else if m, _ := path.Match("/zk/global/vt/keyspaces/*/shards/*", zkPath); m {
|
||||
zkPathParts := strings.Split(zkPath, "/")
|
||||
keyspace := zkPathParts[5]
|
||||
shard := zkPathParts[7]
|
||||
actionRepo.PopulateShardActions(result.Actions, keyspace, shard)
|
||||
} else if m, _ := path.Match("/zk/*/vt/tablets/*", result.Path); m {
|
||||
zkPathParts := strings.Split(result.Path, "/")
|
||||
alias := zkPathParts[2] + "-" + zkPathParts[5]
|
||||
actionRepo.PopulateTabletActions(result.Actions, alias, r)
|
||||
ex.addTabletLinks(data, result)
|
||||
}
|
||||
result.Data = data
|
||||
children, _, err := ex.zconn.Children(zkPath)
|
||||
if err != nil {
|
||||
|
@ -128,33 +69,17 @@ func (ex ZkExplorer) HandlePath(actionRepo proto.ActionRepository, zkPath string
|
|||
return result
|
||||
}
|
||||
|
||||
func (ex ZkExplorer) addTabletLinks(data string, result *ZkResult) {
|
||||
t := &topodatapb.Tablet{}
|
||||
err := json.Unmarshal([]byte(data), t)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if port, ok := t.PortMap["vt"]; ok {
|
||||
result.Links["status"] = template.URL(fmt.Sprintf("http://%v/debug/status", netutil.JoinHostPort(t.Hostname, port)))
|
||||
}
|
||||
}
|
||||
|
||||
// ZkResult is the node for a zk path
|
||||
type ZkResult struct {
|
||||
Path string
|
||||
Data string
|
||||
Links map[string]template.URL
|
||||
Children []string
|
||||
Actions map[string]template.URL
|
||||
Error string
|
||||
}
|
||||
|
||||
// NewZkResult creates a new ZkResult for the path with no links nor actions.
|
||||
// NewZkResult creates a new ZkResult for the path.
|
||||
func NewZkResult(zkPath string) *ZkResult {
|
||||
return &ZkResult{
|
||||
Links: make(map[string]template.URL),
|
||||
Actions: make(map[string]template.URL),
|
||||
Path: zkPath,
|
||||
Path: zkPath,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,259 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"net/http"
|
||||
"path"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/youtube/vitess/go/vt/topo"
|
||||
"github.com/youtube/vitess/go/vt/topotools"
|
||||
|
||||
topodatapb "github.com/youtube/vitess/go/vt/proto/topodata"
|
||||
)
|
||||
|
||||
// FHtmlize writes data to w as debug HTML (using definition lists).
|
||||
func FHtmlize(w io.Writer, data interface{}) {
|
||||
v := reflect.Indirect(reflect.ValueOf(data))
|
||||
typ := v.Type()
|
||||
switch typ.Kind() {
|
||||
case reflect.Struct:
|
||||
fmt.Fprintf(w, "<dl class=\"%s\">", typ.Name())
|
||||
for i := 0; i < typ.NumField(); i++ {
|
||||
field := typ.Field(i)
|
||||
if field.PkgPath != "" {
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(w, "<dt>%v</dt>", field.Name)
|
||||
fmt.Fprint(w, "<dd>")
|
||||
FHtmlize(w, v.Field(i).Interface())
|
||||
fmt.Fprint(w, "</dd>")
|
||||
}
|
||||
fmt.Fprintf(w, "</dl>")
|
||||
case reflect.Slice:
|
||||
fmt.Fprint(w, "<ul>")
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
fmt.Fprint(w, "<li>")
|
||||
FHtmlize(w, v.Index(i).Interface())
|
||||
fmt.Fprint(w, "</li>")
|
||||
}
|
||||
fmt.Fprint(w, "</ul>")
|
||||
case reflect.Map:
|
||||
fmt.Fprintf(w, "<dl class=\"map\">")
|
||||
for _, k := range v.MapKeys() {
|
||||
fmt.Fprint(w, "<dt>")
|
||||
FHtmlize(w, k.Interface())
|
||||
fmt.Fprint(w, "</dt>")
|
||||
fmt.Fprint(w, "<dd>")
|
||||
FHtmlize(w, v.MapIndex(k).Interface())
|
||||
fmt.Fprint(w, "</dd>")
|
||||
}
|
||||
fmt.Fprintf(w, "</dl>")
|
||||
default:
|
||||
printed := fmt.Sprintf("%v", v.Interface())
|
||||
if printed == "" {
|
||||
printed = " "
|
||||
}
|
||||
fmt.Fprint(w, printed)
|
||||
}
|
||||
}
|
||||
|
||||
// Htmlize returns a debug HTML representation of data.
|
||||
func Htmlize(data interface{}) string {
|
||||
b := new(bytes.Buffer)
|
||||
FHtmlize(b, data)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func link(text, href string) string {
|
||||
return fmt.Sprintf("<a href=%q>%v</a>", href, text)
|
||||
}
|
||||
|
||||
func breadCrumbs(fullPath string) template.HTML {
|
||||
parts := strings.Split(fullPath, "/")
|
||||
paths := make([]string, len(parts))
|
||||
for i, part := range parts {
|
||||
if i == 0 {
|
||||
paths[i] = "/"
|
||||
continue
|
||||
}
|
||||
paths[i] = path.Join(paths[i-1], part)
|
||||
}
|
||||
b := new(bytes.Buffer)
|
||||
for i, part := range parts[1 : len(parts)-1] {
|
||||
fmt.Fprint(b, "/"+link(part, paths[i+1]))
|
||||
}
|
||||
fmt.Fprintf(b, "/"+parts[len(parts)-1])
|
||||
return template.HTML(b.String())
|
||||
}
|
||||
|
||||
// FuncMap defines functions accessible in templates. It can be modified in
|
||||
// init() method by plugins to provide extra formatting.
|
||||
var FuncMap = template.FuncMap{
|
||||
"htmlize": func(o interface{}) template.HTML {
|
||||
return template.HTML(Htmlize(o))
|
||||
},
|
||||
"hasprefix": strings.HasPrefix,
|
||||
"intequal": func(left, right int) bool {
|
||||
return left == right
|
||||
},
|
||||
"breadcrumbs": breadCrumbs,
|
||||
"keyspace": func(keyspace string) template.HTML {
|
||||
if explorer == nil {
|
||||
return template.HTML(keyspace)
|
||||
}
|
||||
return template.HTML(link(keyspace, explorer.GetKeyspacePath(keyspace)))
|
||||
},
|
||||
"srv_keyspace": func(cell, keyspace string) template.HTML {
|
||||
if explorer == nil {
|
||||
return template.HTML(keyspace)
|
||||
}
|
||||
return template.HTML(link(keyspace, explorer.GetSrvKeyspacePath(cell, keyspace)))
|
||||
},
|
||||
"shard": func(keyspace string, shard *topotools.ShardNodes) template.HTML {
|
||||
if explorer == nil {
|
||||
return template.HTML(shard.Name)
|
||||
}
|
||||
return template.HTML(link(shard.Name, explorer.GetShardPath(keyspace, shard.Name)))
|
||||
},
|
||||
"srv_shard": func(cell, keyspace string, shard *topotools.ShardNodes) template.HTML {
|
||||
if explorer == nil {
|
||||
return template.HTML(shard.Name)
|
||||
}
|
||||
return template.HTML(link(shard.Name, explorer.GetSrvShardPath(cell, keyspace, shard.Name)))
|
||||
},
|
||||
"tablet": func(alias *topodatapb.TabletAlias, shortname string) template.HTML {
|
||||
if explorer == nil {
|
||||
return template.HTML(shortname)
|
||||
}
|
||||
return template.HTML(link(shortname, explorer.GetTabletPath(alias)))
|
||||
},
|
||||
}
|
||||
|
||||
// TemplateLoader is a helper class to load html templates.
|
||||
type TemplateLoader struct {
|
||||
Directory string
|
||||
usesDummy bool
|
||||
template *template.Template
|
||||
}
|
||||
|
||||
func (loader *TemplateLoader) compile() (*template.Template, error) {
|
||||
return template.New("main").Funcs(FuncMap).ParseGlob(path.Join(loader.Directory, "[a-z]*.html"))
|
||||
}
|
||||
|
||||
func (loader *TemplateLoader) makeErrorTemplate(errorMessage string) *template.Template {
|
||||
return template.Must(template.New("error").Parse(fmt.Sprintf("Error in template: %s", errorMessage)))
|
||||
}
|
||||
|
||||
// NewTemplateLoader returns a template loader with templates from
|
||||
// directory. If directory is "", fallbackTemplate will be used
|
||||
// (regardless of the wanted template name). If debug is true,
|
||||
// templates will be recompiled each time.
|
||||
func NewTemplateLoader(directory string, debug bool) *TemplateLoader {
|
||||
loader := &TemplateLoader{Directory: directory}
|
||||
if directory == "" {
|
||||
loader.usesDummy = true
|
||||
loader.template = template.Must(template.New("dummy").Funcs(FuncMap).Parse(`
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
html {
|
||||
font-family: monospace;
|
||||
}
|
||||
dd {
|
||||
margin-left: 2em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
{{ htmlize . }}
|
||||
</body>
|
||||
</html>
|
||||
`))
|
||||
return loader
|
||||
}
|
||||
if !debug {
|
||||
tmpl, err := loader.compile()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
loader.template = tmpl
|
||||
}
|
||||
return loader
|
||||
}
|
||||
|
||||
// Lookup will find a template by name and return it
|
||||
func (loader *TemplateLoader) Lookup(name string) (*template.Template, error) {
|
||||
if loader.usesDummy {
|
||||
return loader.template, nil
|
||||
}
|
||||
var err error
|
||||
source := loader.template
|
||||
if source == nil {
|
||||
source, err = loader.compile()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
tmpl := source.Lookup(name)
|
||||
if tmpl == nil {
|
||||
err := fmt.Errorf("template %v not available", name)
|
||||
return nil, err
|
||||
}
|
||||
return tmpl, nil
|
||||
}
|
||||
|
||||
// ServeTemplate executes the named template passing data into it. If
|
||||
// the format GET parameter is equal to "json", serves data as JSON
|
||||
// instead.
|
||||
func (loader *TemplateLoader) ServeTemplate(templateName string, data interface{}, w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Query().Get("format") {
|
||||
case "json":
|
||||
j, err := json.MarshalIndent(data, "", " ")
|
||||
if err != nil {
|
||||
httpErrorf(w, r, "JSON error%s", err)
|
||||
return
|
||||
}
|
||||
w.Write(j)
|
||||
default:
|
||||
tmpl, err := loader.Lookup(templateName)
|
||||
if err != nil {
|
||||
httpErrorf(w, r, "error in template loader: %v", err)
|
||||
return
|
||||
}
|
||||
if err := tmpl.Execute(w, data); err != nil {
|
||||
httpErrorf(w, r, "error executing template: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
modifyDbTopology func(context.Context, topo.Server, *topotools.Topology) error
|
||||
modifyDbServingGraph func(context.Context, topo.Server, *topotools.ServingGraph)
|
||||
)
|
||||
|
||||
// SetDbTopologyPostprocessor installs a hook that can modify
|
||||
// topotools.Topology struct before it's displayed.
|
||||
func SetDbTopologyPostprocessor(f func(context.Context, topo.Server, *topotools.Topology) error) {
|
||||
if modifyDbTopology != nil {
|
||||
panic("Cannot set multiple DbTopology postprocessors")
|
||||
}
|
||||
modifyDbTopology = f
|
||||
}
|
||||
|
||||
// SetDbServingGraphPostprocessor installs a hook that can modify
|
||||
// topotools.ServingGraph struct before it's displayed.
|
||||
func SetDbServingGraphPostprocessor(f func(context.Context, topo.Server, *topotools.ServingGraph)) {
|
||||
if modifyDbServingGraph != nil {
|
||||
panic("Cannot set multiple DbServingGraph postprocessors")
|
||||
}
|
||||
modifyDbServingGraph = f
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHtmlizeStruct(t *testing.T) {
|
||||
type simpleStruct struct {
|
||||
Start, End int
|
||||
}
|
||||
|
||||
type funkyStruct struct {
|
||||
SomeString string
|
||||
SomeInt int
|
||||
Embedded simpleStruct
|
||||
EmptyString string
|
||||
}
|
||||
input := funkyStruct{SomeString: "a string", SomeInt: 42, Embedded: simpleStruct{1, 42}}
|
||||
want := `<dl class="funkyStruct"><dt>SomeString</dt><dd>a string</dd><dt>SomeInt</dt><dd>42</dd><dt>Embedded</dt><dd><dl class="simpleStruct"><dt>Start</dt><dd>1</dd><dt>End</dt><dd>42</dd></dl></dd><dt>EmptyString</dt><dd> </dd></dl>`
|
||||
if got := Htmlize(input); got != want {
|
||||
t.Errorf("Htmlize: got %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHtmlizeMap(t *testing.T) {
|
||||
// We can't test multiple entries, since Htmlize supports maps whose keys can't be sorted.
|
||||
input := map[string]string{"dog": "apple"}
|
||||
want := `<dl class="map"><dt>dog</dt><dd>apple</dd></dl>`
|
||||
if got := Htmlize(input); got != want {
|
||||
t.Errorf("Htmlize: got %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHtmlizeSlice(t *testing.T) {
|
||||
input := []string{"aaa", "bbb", "ccc"}
|
||||
want := `<ul><li>aaa</li><li>bbb</li><li>ccc</li></ul>`
|
||||
if got := Htmlize(input); got != want {
|
||||
t.Errorf("Htmlize: got %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBreadCrumbs(t *testing.T) {
|
||||
input := "/grandparent/parent/node"
|
||||
want := template.HTML(`/<a href="/grandparent">grandparent</a>/<a href="/grandparent/parent">parent</a>/node`)
|
||||
if got := breadCrumbs(input); got != want {
|
||||
t.Errorf("breadCrumbs(%q) = %q, want %q", input, got, want)
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Action Results</title>
|
||||
<style>
|
||||
html {font-family: sans-serif;}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<h1>{{.Name}}</h1>
|
||||
{{if .Parameters}}
|
||||
<p><b>Parameters:</b> {{.Parameters}}</p>
|
||||
{{end}}
|
||||
{{if .Error}}
|
||||
<h2>Error</h2>
|
||||
{{else}}
|
||||
<h2>Success</h2>
|
||||
{{end}}
|
||||
<pre>{{.Output}}</pre>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -1,44 +0,0 @@
|
|||
/**
|
||||
* Copyright 2014, Google Inc. All rights reserved.
|
||||
* Use of this source code is governed by a BSD-style
|
||||
* license that can be found in the LICENSE file.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
angular.module('app', ['ngRoute'])
|
||||
.factory('vindexInfo', vindexInfo)
|
||||
.factory('curSchema', curSchema)
|
||||
.controller('KeyspaceController', KeyspaceController)
|
||||
.controller('SidebarController', SidebarController)
|
||||
.controller('ClassController', ClassController)
|
||||
.controller('LoadController', LoadController)
|
||||
.controller('SubmitController', SubmitController)
|
||||
.controller('SchemaManagerController', SchemaManagerController)
|
||||
.config(['$routeProvider', function($routeProvider) {
|
||||
$routeProvider
|
||||
.when('/editor',{
|
||||
templateUrl: "/content/editor/keyspace.html",
|
||||
controller: "KeyspaceController"
|
||||
})
|
||||
.when('/editor/:keyspaceName',{
|
||||
templateUrl: "/content/editor/keyspace.html",
|
||||
controller: "KeyspaceController"
|
||||
})
|
||||
.when('/editor/:keyspaceName/class/:className',{
|
||||
templateUrl: "/content/editor/class/class.html",
|
||||
controller: "ClassController"
|
||||
})
|
||||
.when('/load',{
|
||||
templateUrl: "/content/load/load.html",
|
||||
controller: "LoadController"
|
||||
})
|
||||
.when('/submit',{
|
||||
templateUrl: "/content/submit/submit.html",
|
||||
controller: "SubmitController"
|
||||
})
|
||||
.when('/schema-manager',{
|
||||
templateUrl: "/content/schema_manager/index.html",
|
||||
controller: "SchemaManagerController"
|
||||
})
|
||||
.otherwise({redirectTo: '/'});
|
||||
}]);
|
|
@ -1,216 +0,0 @@
|
|||
/**
|
||||
* Copyright 2014, Google Inc. All rights reserved. Use of this source code is
|
||||
* governed by a BSD-style license that can be found in the LICENSE file.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
function curSchema(vindexInfo) {
|
||||
var data = {};
|
||||
|
||||
data.reset = function() {
|
||||
data.keyspaces = copyKeyspaces(data.original, vindexInfo);
|
||||
data.tables = computeTables(data.keyspaces);
|
||||
};
|
||||
|
||||
data.deleteKeyspace = function(keyspaceName) {
|
||||
delete data.keyspaces[keyspaceName];
|
||||
data.tables = computeTables(data.keyspaces);
|
||||
};
|
||||
|
||||
data.addTable = function(keyspaceName, tableName, className) {
|
||||
data.keyspaces[keyspaceName].Tables[tableName] = className;
|
||||
data.tables = computeTables(data.keyspaces);
|
||||
};
|
||||
|
||||
data.deleteTable = function(keyspaceName, tableName) {
|
||||
delete data.keyspaces[keyspaceName].Tables[tableName];
|
||||
data.tables = computeTables(data.keyspaces);
|
||||
};
|
||||
|
||||
data.validClasses = function(keyspace, tableName) {
|
||||
var valid = [];
|
||||
if (!keyspace) {
|
||||
return [];
|
||||
}
|
||||
for ( var className in keyspace.Classes) {
|
||||
if (data.classHasError(keyspace, tableName, className)) {
|
||||
continue;
|
||||
}
|
||||
valid.push(className);
|
||||
}
|
||||
return valid;
|
||||
};
|
||||
|
||||
data.classHasError = function(keyspace, tableName, className) {
|
||||
if (!(className in keyspace.Classes)) {
|
||||
return "class not found";
|
||||
}
|
||||
var klass = keyspace.Classes[className];
|
||||
for (var i = 0; i < klass.ColVindexes.length; i++) {
|
||||
var classError = data.vindexHasError(keyspace, className, i);
|
||||
if (classError) {
|
||||
return "invalid class";
|
||||
}
|
||||
var vindex = keyspace.Vindexes[klass.ColVindexes[i].Name];
|
||||
if (vindex.Owner != tableName) {
|
||||
continue;
|
||||
}
|
||||
if (i == 0) {
|
||||
if (vindexInfo.Types[vindex.Type].Type != "functional") {
|
||||
return "owned primary vindex must be functional";
|
||||
}
|
||||
} else {
|
||||
if (vindexInfo.Types[vindex.Type].Type != "lookup") {
|
||||
return "owned non-primary vindex must be lookup";
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
data.validVindexes = function(keyspace, className, index) {
|
||||
var valid = [];
|
||||
for ( var vindexName in keyspace.Vindexes) {
|
||||
// Duplicated from vindexHasError.
|
||||
if (index == 0) {
|
||||
var vindexTypeName = keyspace.Vindexes[vindexName].Type;
|
||||
if (!vindexInfo.Types[vindexTypeName].Unique) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
valid.push(vindexName);
|
||||
}
|
||||
return valid;
|
||||
};
|
||||
|
||||
data.vindexHasError = function(keyspace, className, index) {
|
||||
var vindexName = keyspace.Classes[className].ColVindexes[index].Name;
|
||||
if (!(vindexName in keyspace.Vindexes)) {
|
||||
return "vindex not found";
|
||||
}
|
||||
if (index == 0) {
|
||||
var vindexTypeName = keyspace.Vindexes[vindexName].Type;
|
||||
if (!vindexInfo.Types[vindexTypeName].Unique) {
|
||||
return "primary vindex must be unique";
|
||||
}
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
data.init = function(original) {
|
||||
data.original = original;
|
||||
data.reset();
|
||||
};
|
||||
|
||||
data.init({});
|
||||
return data;
|
||||
}
|
||||
|
||||
function SetSharded(keyspace, sharded) {
|
||||
if (sharded) {
|
||||
keyspace.Sharded = true;
|
||||
if (!keyspace["Classes"]) {
|
||||
keyspace.Classes = {};
|
||||
}
|
||||
if (!keyspace["Vindexes"]) {
|
||||
keyspace.Vindexes = {};
|
||||
}
|
||||
} else {
|
||||
keyspace.Sharded = false;
|
||||
for ( var tableName in keyspace.Tables) {
|
||||
keyspace.Tables[tableName] = "";
|
||||
}
|
||||
delete keyspace["Classes"];
|
||||
delete keyspace["Vindexes"];
|
||||
}
|
||||
};
|
||||
|
||||
function AddKeyspace(keyspaces, keyspaceName, sharded) {
|
||||
var keyspace = {};
|
||||
keyspace.Tables = {};
|
||||
SetSharded(keyspace, sharded);
|
||||
keyspaces[keyspaceName] = keyspace;
|
||||
};
|
||||
|
||||
function CopyParams(original, type, vindexInfo) {
|
||||
var params = {};
|
||||
var vparams = vindexInfo.Types[type].Params;
|
||||
for (var i = 0; i < vparams.length; i++) {
|
||||
params[vparams[i]] = original[vparams[i]];
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
function copyKeyspaces(original, vindexInfo) {
|
||||
var copied = {};
|
||||
for ( var key in original) {
|
||||
copied[key] = {};
|
||||
var keyspace = copied[key];
|
||||
if (original[key].Sharded) {
|
||||
keyspace.Sharded = true;
|
||||
keyspace.Vindexes = copyVindexes(original[key].Vindexes, vindexInfo);
|
||||
keyspace.Classes = copyClasses(original[key].Classes);
|
||||
keyspace.Tables = copyTables(original[key].Tables);
|
||||
} else {
|
||||
keyspace.Sharded = false;
|
||||
keyspace.Tables = {};
|
||||
for (key in original[key].Tables) {
|
||||
keyspace.Tables[key] = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
return copied;
|
||||
}
|
||||
|
||||
function copyVindexes(original, vindexInfo) {
|
||||
var copied = {};
|
||||
for ( var key in original) {
|
||||
if (!vindexInfo.Types[original[key].Type]) {
|
||||
continue;
|
||||
}
|
||||
copied[key] = {};
|
||||
var vindex = copied[key];
|
||||
vindex.Type = original[key].Type;
|
||||
vindex.Owner = original[key].Owner;
|
||||
vindex.Params = CopyParams(original[key].Params, original[key].Type, vindexInfo);
|
||||
}
|
||||
return copied;
|
||||
}
|
||||
|
||||
function copyClasses(original) {
|
||||
var copied = {};
|
||||
for ( var key in original) {
|
||||
copied[key] = {"ColVindexes": []};
|
||||
for (var i = 0; i < original[key].ColVindexes.length; i++) {
|
||||
copied[key].ColVindexes.push({
|
||||
"Col": original[key].ColVindexes[i].Col,
|
||||
"Name": original[key].ColVindexes[i].Name
|
||||
});
|
||||
}
|
||||
}
|
||||
return copied;
|
||||
}
|
||||
|
||||
function copyTables(original) {
|
||||
var copied = {};
|
||||
for ( var key in original) {
|
||||
copied[key] = original[key];
|
||||
}
|
||||
return copied;
|
||||
}
|
||||
|
||||
function computeTables(keyspaces) {
|
||||
var tables = {};
|
||||
for ( var ks in keyspaces) {
|
||||
for ( var table in keyspaces[ks].Tables) {
|
||||
if (table in tables) {
|
||||
tables[table].push(ks);
|
||||
} else {
|
||||
tables[table] = [
|
||||
ks
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
return tables;
|
||||
}
|
|
@ -1,106 +0,0 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>DB topology</title>
|
||||
<style>
|
||||
.keyspace {
|
||||
font-family: monospace;
|
||||
}
|
||||
td {
|
||||
border: 1px solid black;
|
||||
vertical-align:text-top;
|
||||
}
|
||||
ul.tablet-list li {
|
||||
list-style-type: none;
|
||||
margin: 0px;
|
||||
position: relative;
|
||||
}
|
||||
ul.tablet-list {
|
||||
padding-left: 0px;
|
||||
}
|
||||
table.keyspace {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
table.keyspace td {
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
}
|
||||
.topo-link {
|
||||
vertical-align: super;
|
||||
font-size: 20%;
|
||||
}
|
||||
table.keyspace td.legend {
|
||||
text-align: center;
|
||||
padding-left: 1ex;
|
||||
padding-right: 1ex;
|
||||
}
|
||||
table.keyspace thead {
|
||||
text-align: center;
|
||||
border-color: #666;
|
||||
background-color: #dedede;
|
||||
}
|
||||
.not-replicating {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
{{with .Error}}
|
||||
<h1>{{.}}</h1>
|
||||
{{end}}
|
||||
{{with .Topology}}
|
||||
{{if .Partial}}
|
||||
<p><i><b>Warning:</b> This is a partial topology result, some cells are unavailable.</i></p>
|
||||
{{end}}
|
||||
<h2>ToC</h2>
|
||||
{{range $keyspace, $shards := .Assigned}}
|
||||
<li><a href="#keyspace_{{$keyspace}}">{{$keyspace}}</a></li>
|
||||
{{end}}
|
||||
|
||||
<h1>Assigned</h1>
|
||||
{{range $keyspace, $shards := .Assigned}}
|
||||
<h2><a name="keyspace_{{$keyspace}}"></a>{{keyspace $keyspace}}</h2>
|
||||
<table class="keyspace">
|
||||
<thead>
|
||||
{{ range $i, $shard := $shards.ShardNodes }}
|
||||
{{if intequal $i 0}}<td class="legend">type</td>{{end}}
|
||||
<td>{{shard $keyspace $shard}}</td>
|
||||
{{end}}
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range $processedType := $shards.TabletTypes}}
|
||||
<tr>
|
||||
{{range $i, $shard := $shards.ShardNodes}}
|
||||
{{if intequal $i 0}}<td class="legend">{{$processedType}}</td>{{end}}
|
||||
<td>
|
||||
<ul class="tablet-list">
|
||||
{{range index $shard.TabletNodes $processedType}}
|
||||
<li title="{{.Alias}}">{{tablet .Alias .ShortName}}</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
</td>
|
||||
{{end}}
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{end}}
|
||||
<h1>Idle</h1>
|
||||
<ul>
|
||||
{{range .Idle}}
|
||||
<li title="{{.Alias}}">{{tablet .Alias .ShortName}}</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
<h1>Scrap</h1>
|
||||
<ul>
|
||||
{{range .Scrap}}
|
||||
<li title="{{.Alias}}">
|
||||
{{tablet .Alias .ShortName}} <a href="/tablet_actions?action=DeleteTablet&alias={{.Alias}}">(delete)</a>
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
{{end}}
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -1,75 +0,0 @@
|
|||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div data-ng-include="'/content/editor/sidebar.html'"></div>
|
||||
<div data-ng-show="className">
|
||||
<div class="col-md-9">
|
||||
<h4 data-ng-show="keyspaceName">Class:
|
||||
{{keyspaceName}}.{{className}}</h4>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<table class="table">
|
||||
<tr data-ng-repeat="colVindex in klass.ColVindexes">
|
||||
<td data-ng-class="{'alert-danger': colVindex.Col==''}">{{colVindex.Col}}
|
||||
<div data-ng-show="colVindex.Col==''">(empty)</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="dropdown">
|
||||
<button
|
||||
data-ng-class="{'btn dropdown-toggle': true, 'alert-danger': vindexHasError(className, $index)}"
|
||||
title="{{vindexHasError(className, $index)}}" type="button"
|
||||
data-toggle="dropdown">
|
||||
{{colVindex.Name}} <span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li
|
||||
data-ng-repeat="vindex in validVindexes(className, $index)"
|
||||
data-ng-click="setName(colVindex, vindex)"><a
|
||||
href="#/editor/{{keyspaceName}}/class/{{className}}">{{vindex}}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
<td><a type="button" class="btn btn-xs"
|
||||
data-ng-click="deleteColVindex($index)"> <span
|
||||
class="glyphicon glyphicon-remove"></span>
|
||||
</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" placeholder="Column"
|
||||
data-ng-model="classEditor.newColumnName">
|
||||
<div class="input-group-btn">
|
||||
<button type="button" class="btn dropdown-toggle"
|
||||
data-toggle="dropdown">
|
||||
Add <span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li
|
||||
data-ng-repeat="vindex in validVindexes(className, klass.ColVindexes.length)"
|
||||
data-ng-click="addColVindex(classEditor.newColumnName, vindex)"><a
|
||||
href="#/editor/{{keyspaceName}}/class/{{className}}">{{vindex}}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr data-ng-show="classEditor.err">
|
||||
<td colspan="2">
|
||||
<div class="alert alert-danger" data-ng-show="classEditor.err">
|
||||
{{classEditor.err}}<a type="button"
|
||||
class="alert-danger pull-right"
|
||||
data-ng-click="clearClassError()"> <span
|
||||
class="glyphicon glyphicon-remove"></span>
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<button type="button" class="btn btn-default btn-lg"
|
||||
onclick="history.back()" title="Back">
|
||||
<span class="glyphicon glyphicon-arrow-left"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,66 +0,0 @@
|
|||
/**
|
||||
* Copyright 2014, Google Inc. All rights reserved. Use of this source code is
|
||||
* governed by a BSD-style license that can be found in the LICENSE file.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
function ClassController($scope, $routeParams, vindexInfo, curSchema) {
|
||||
init();
|
||||
|
||||
function init() {
|
||||
$scope.curSchema = curSchema;
|
||||
$scope.vindexInfo = vindexInfo;
|
||||
if (!$routeParams.keyspaceName || !curSchema.keyspaces[$routeParams.keyspaceName]) {
|
||||
return;
|
||||
}
|
||||
$scope.keyspaceName = $routeParams.keyspaceName;
|
||||
$scope.keyspace = curSchema.keyspaces[$routeParams.keyspaceName];
|
||||
if (!$routeParams.className || !$scope.keyspace.Classes[$routeParams.className]) {
|
||||
return;
|
||||
}
|
||||
$scope.className = $routeParams.className;
|
||||
$scope.klass = $scope.keyspace.Classes[$routeParams.className];
|
||||
$scope.classEditor = {};
|
||||
}
|
||||
|
||||
$scope.setName = function($colVindex, $vindex) {
|
||||
$colVindex.Name = $vindex;
|
||||
$scope.clearClassError();
|
||||
};
|
||||
|
||||
$scope.addColVindex = function($colName, $vindex) {
|
||||
if (!$colName) {
|
||||
$scope.classEditor.err = "empty column name";
|
||||
return;
|
||||
}
|
||||
for (var i = 0; i < $scope.klass.length; i++) {
|
||||
if ($colName == $scope.klass[i].Col) {
|
||||
$scope.classEditor.err = $colName + " already exists";
|
||||
return;
|
||||
}
|
||||
}
|
||||
;
|
||||
$scope.klass.ColVindexes.push({
|
||||
"Col": $colName,
|
||||
"Name": $vindex
|
||||
});
|
||||
$scope.clearClassError();
|
||||
};
|
||||
|
||||
$scope.deleteColVindex = function(index) {
|
||||
$scope.klass.ColVindexes.splice(index, 1);
|
||||
$scope.clearClassError();
|
||||
};
|
||||
|
||||
$scope.clearClassError = function() {
|
||||
$scope.classEditor.err = "";
|
||||
};
|
||||
|
||||
$scope.validVindexes = function($className, $index) {
|
||||
return curSchema.validVindexes($scope.keyspace, $className, $index);
|
||||
};
|
||||
|
||||
$scope.vindexHasError = function($className, $index) {
|
||||
return curSchema.vindexHasError($scope.keyspace, $className, $index);
|
||||
};
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
<div class="col-md-3">
|
||||
<table class="table table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Class</th>
|
||||
<th>Column</th>
|
||||
<th>Vindex</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody data-ng-repeat="(className, klass) in keyspace.Classes">
|
||||
<tr data-ng-show="klass.ColVindexes.length == 0">
|
||||
<td><a href="#/editor/{{keyspaceName}}/class/{{className}}">{{className}}</a></td>
|
||||
<td></td>
|
||||
<td><div class="alert-danger">(empty)</div></td>
|
||||
<td><a type="button" class="btn btn-xs"
|
||||
data-ng-click="deleteClass(className)"> <span
|
||||
class="glyphicon glyphicon-remove"></span>
|
||||
</a></td>
|
||||
</tr>
|
||||
<tr data-ng-repeat="colVindex in klass.ColVindexes">
|
||||
<td data-ng-show="$first" rowspan="{{klass.ColVindexes.length}}"><a
|
||||
href="#/editor/{{keyspaceName}}/class/{{className}}">{{className}}</a>
|
||||
</td>
|
||||
<td data-ng-class="{'alert-danger': colVindex.Col==''}">{{colVindex.Col}}
|
||||
<div data-ng-show="colVindex.Col==''">(empty)</div>
|
||||
</td>
|
||||
<td data-ng-class="{'alert-danger': vindexHasError(className, $index)}"
|
||||
title="{{vindexHasError(className, $index)}}">{{colVindex.Name}}
|
||||
</td>
|
||||
<td data-ng-show="$first" rowspan="{{klass.ColVindexes.length}}"><a
|
||||
type="button" class="btn btn-xs"
|
||||
data-ng-click="deleteClass(className)"> <span
|
||||
class="glyphicon glyphicon-remove"></span>
|
||||
</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" placeholder="Class"
|
||||
data-ng-model="classesEditor.newClassName"><span
|
||||
class="input-group-btn"><button class="btn" type="button"
|
||||
data-ng-click="addClass(classesEditor.newClassName)">
|
||||
<span class="glyphicon glyphicon-plus"></span>
|
||||
</button> </span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr data-ng-show="classesEditor.err">
|
||||
<td colspan="2">
|
||||
<div class="alert alert-danger">
|
||||
{{classesEditor.err}}<a type="button" class="alert-danger pull-right"
|
||||
data-ng-click="clearClassesError()"> <span
|
||||
class="glyphicon glyphicon-remove"></span>
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
|
@ -1,33 +0,0 @@
|
|||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div data-ng-include="'/content/editor/sidebar.html'"></div>
|
||||
<div class="col-md-4">
|
||||
<h4 data-ng-show="keyspaceName">
|
||||
Keyspace: {{keyspaceName}} <span class="btn-group ">
|
||||
<button type="button"
|
||||
data-ng-class="{'btn btn-info': true, 'active': keyspace.Sharded}"
|
||||
data-ng-click="setSharded(true)">Sharded</button>
|
||||
<button type="button"
|
||||
data-ng-class="{'btn btn-info': true, 'active': !keyspace.Sharded}"
|
||||
data-ng-click="setSharded(false)">Unsharded</button>
|
||||
</span> <span class="btn-group ">
|
||||
<a type="button" class="btn btn-primary" title="Delete keyspace"
|
||||
data-ng-click="deleteKeyspace(keyspaceName)"
|
||||
href="#">
|
||||
<span class="glyphicon glyphicon-trash"></span>
|
||||
</a>
|
||||
</span>
|
||||
</h4>
|
||||
</div>
|
||||
<div data-ng-switch="keyspace.Sharded">
|
||||
<div data-ng-switch-when="true">
|
||||
<div data-ng-include="'/content/editor/tables.html'"></div>
|
||||
<div data-ng-include="'/content/editor/classes.html'"></div>
|
||||
<div data-ng-include="'/content/editor/vindexes.html'"></div>
|
||||
</div>
|
||||
<div data-ng-switch-default>
|
||||
<div data-ng-include="'/content/editor/unsharded_tables.html'"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,158 +0,0 @@
|
|||
/**
|
||||
* Copyright 2014, Google Inc. All rights reserved. Use of this source code is
|
||||
* governed by a BSD-style license that can be found in the LICENSE file.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
function KeyspaceController($scope, $routeParams, vindexInfo, curSchema) {
|
||||
init();
|
||||
|
||||
function init() {
|
||||
$scope.curSchema = curSchema;
|
||||
$scope.vindexInfo = vindexInfo;
|
||||
if (!$routeParams.keyspaceName || !curSchema.keyspaces[$routeParams.keyspaceName]) {
|
||||
return;
|
||||
}
|
||||
$scope.keyspaceName = $routeParams.keyspaceName;
|
||||
$scope.keyspace = curSchema.keyspaces[$routeParams.keyspaceName];
|
||||
if ($scope.keyspace.Sharded) {
|
||||
$scope.vindexNames = Object.keys($scope.keyspace.Vindexes);
|
||||
}
|
||||
$scope.tablesEditor = {};
|
||||
$scope.classesEditor = {};
|
||||
$scope.vindexEditor = {};
|
||||
}
|
||||
|
||||
$scope.setSharded = function($sharded) {
|
||||
SetSharded($scope.keyspace, $sharded);
|
||||
};
|
||||
|
||||
$scope.deleteKeyspace = function($keyspaceName) {
|
||||
curSchema.deleteKeyspace($keyspaceName);
|
||||
};
|
||||
|
||||
$scope.tableHasError = function($tableName) {
|
||||
var table = curSchema.tables[$tableName];
|
||||
if (table && table.length > 1) {
|
||||
return $tableName + " duplicated in " + curSchema.tables[$tableName];
|
||||
}
|
||||
};
|
||||
|
||||
$scope.addTable = function($tableName, $className) {
|
||||
if (!$tableName) {
|
||||
$scope.tablesEditor.err = "empty table name";
|
||||
return;
|
||||
}
|
||||
if ($tableName in curSchema.tables) {
|
||||
$scope.tablesEditor.err = $tableName + " already exists in " + curSchema.tables[$tableName];
|
||||
return;
|
||||
|
||||
}
|
||||
curSchema.addTable($scope.keyspaceName, $tableName, $className);
|
||||
$scope.tablesEditor.newTableName = "";
|
||||
$scope.clearTableError();
|
||||
};
|
||||
|
||||
$scope.setTableClass = function($tableName, $className) {
|
||||
$scope.keyspace.Tables[$tableName] = $className;
|
||||
$scope.clearTableError();
|
||||
};
|
||||
|
||||
$scope.deleteTable = function($tableName) {
|
||||
curSchema.deleteTable($scope.keyspaceName, $tableName);
|
||||
$scope.clearTableError();
|
||||
};
|
||||
|
||||
$scope.validClasses = function($tableName) {
|
||||
return curSchema.validClasses($scope.keyspace, $tableName);
|
||||
};
|
||||
|
||||
$scope.classHasError = function($tableName, $className) {
|
||||
return curSchema.classHasError($scope.keyspace, $tableName, $className);
|
||||
};
|
||||
|
||||
$scope.clearTableError = function() {
|
||||
$scope.tablesEditor.err = "";
|
||||
};
|
||||
|
||||
$scope.addClass = function($className) {
|
||||
if (!$className) {
|
||||
$scope.classesEditor.err = "empty class name";
|
||||
return;
|
||||
}
|
||||
if ($className in $scope.keyspace.Classes) {
|
||||
$scope.classesEditor.err = $className + " already exists";
|
||||
return;
|
||||
}
|
||||
$scope.keyspace.Classes[$className] = {"ColVindexes": []};
|
||||
$scope.classesEditor.newClassName = "";
|
||||
$scope.clearClassesError();
|
||||
window.location.href = "#/editor/" + $scope.keyspaceName + "/class/" + $className;
|
||||
};
|
||||
|
||||
$scope.deleteClass = function($className) {
|
||||
delete $scope.keyspace.Classes[$className];
|
||||
$scope.clearClassesError();
|
||||
};
|
||||
|
||||
$scope.clearClassesError = function() {
|
||||
$scope.classesEditor.err = "";
|
||||
};
|
||||
|
||||
$scope.vindexHasError = function($className, $index) {
|
||||
return curSchema.vindexHasError($scope.keyspace, $className, $index);
|
||||
};
|
||||
|
||||
$scope.setVindexType = function($vindex, $vindexType) {
|
||||
$vindex.Type = $vindexType;
|
||||
$vindex.Params = CopyParams($vindex.Params, $vindexType, vindexInfo);
|
||||
$scope.clearVindexError();
|
||||
};
|
||||
|
||||
$scope.deleteVindex = function($vindexName) {
|
||||
delete $scope.keyspace.Vindexes[$vindexName];
|
||||
$scope.clearVindexError();
|
||||
};
|
||||
|
||||
$scope.ownerHasWarning = function($owner, $vindexName) {
|
||||
if (!$owner) {
|
||||
return "";
|
||||
}
|
||||
var className = $scope.keyspace.Tables[$owner];
|
||||
if (!className) {
|
||||
return "table not found";
|
||||
}
|
||||
var klass = $scope.keyspace.Classes[className];
|
||||
if (!klass) {
|
||||
return "table has invalid class";
|
||||
}
|
||||
for (var i = 0; i < klass.length; i++) {
|
||||
if (klass[i].Name == $vindexName) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
return "table does not contain this index";
|
||||
};
|
||||
|
||||
$scope.addVindex = function($vindexName, $vindexType) {
|
||||
if (!$vindexName) {
|
||||
$scope.vindexEditor.err = "empty vindex name";
|
||||
return;
|
||||
}
|
||||
if ($vindexName in $scope.keyspace.Vindexes) {
|
||||
$scope.vindexEditor.err = $vindexName + " already exists";
|
||||
return;
|
||||
}
|
||||
var newVindex = {
|
||||
"Params": {}
|
||||
};
|
||||
$scope.setVindexType(newVindex, $vindexType);
|
||||
$scope.keyspace.Vindexes[$vindexName] = newVindex;
|
||||
$scope.vindexEditor.vindexName = "";
|
||||
$scope.clearVindexError();
|
||||
};
|
||||
|
||||
$scope.clearVindexError = function() {
|
||||
$scope.vindexEditor.err = "";
|
||||
};
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
<div class="col-md-1 sidebar" data-ng-controller="SidebarController">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">Commands</div>
|
||||
<div class="panel-body">
|
||||
<ul class="nav nav-sidebar">
|
||||
<li><a href="#/load">Load</a></li>
|
||||
<li><a href="#/submit">Submit</a></li>
|
||||
<li><a href="#/editor" data-ng-click="reset()">Reset</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">Keyspaces</div>
|
||||
<div class="panel-body">
|
||||
<ul class="nav nav-sidebar">
|
||||
<li data-ng-repeat="(keyspace, keyspaceInfo) in curSchema.keyspaces"><a
|
||||
href="#/editor/{{keyspace}}"
|
||||
data-ng-class="{'btn-primary': keyspace==keyspaceName}">{{keyspace}}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group input-group-sm">
|
||||
<input type="text" class="form-control" placeholder="Keyspace"
|
||||
data-ng-model="newKeyspace">
|
||||
<div class="input-group-btn">
|
||||
<button type="button" class="btn dropdown-toggle" data-toggle="dropdown">
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a data-ng-click="addKeyspace(newKeyspace, true)">Sharded</a></li>
|
||||
<li><a data-ng-click="addKeyspace(newKeyspace, false)">Unsharded</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="alert alert-danger" data-ng-show="keyspaceEditor.err">
|
||||
{{keyspaceEditor.err}}<a type="button" class="alert-danger pull-right"
|
||||
data-ng-click="clearKeyspaceError()"> <span
|
||||
class="glyphicon glyphicon-remove"></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
|
@ -1,36 +0,0 @@
|
|||
/**
|
||||
* Copyright 2014, Google Inc. All rights reserved. Use of this source code is
|
||||
* governed by a BSD-style license that can be found in the LICENSE file.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
function SidebarController($scope, $routeParams, curSchema) {
|
||||
init();
|
||||
|
||||
function init() {
|
||||
$scope.curSchema = curSchema;
|
||||
$scope.keyspaceEditor = {};
|
||||
}
|
||||
$scope.addKeyspace = function($keyspaceName, $sharded) {
|
||||
if (!$keyspaceName) {
|
||||
$scope.keyspaceEditor.err = "empty keyspace name";
|
||||
return;
|
||||
}
|
||||
if ($keyspaceName in curSchema.keyspaces) {
|
||||
$scope.keyspaceEditor.err = $keyspaceName + " already exists";
|
||||
return;
|
||||
}
|
||||
AddKeyspace(curSchema.keyspaces, $keyspaceName, $sharded);
|
||||
$scope.clearKeyspaceError();
|
||||
window.location.href = "#/editor/" + $keyspaceName;
|
||||
};
|
||||
|
||||
$scope.reset = function() {
|
||||
curSchema.reset();
|
||||
$scope.clearKeyspaceError();
|
||||
};
|
||||
|
||||
$scope.clearKeyspaceError = function() {
|
||||
$scope.keyspaceEditor.err = "";
|
||||
};
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
<div class="col-md-3" data-ng-show="keyspaceName">
|
||||
<table class="table table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Table</th>
|
||||
<th>Class</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr data-ng-repeat="(tableName, tableClass) in keyspace.Tables">
|
||||
<td data-ng-class="{'alert-danger': tableHasError(tableName)}"
|
||||
title="{{tableHasError(tableName)}}">{{tableName}}
|
||||
</td>
|
||||
<td>
|
||||
<div class="dropdown">
|
||||
<button
|
||||
data-ng-class="{'btn dropdown-toggle form-control': true, 'alert-danger': classHasError(tableName, tableClass)}"
|
||||
title="{{classHasError(tableName, tableClass)}}" type="button"
|
||||
data-toggle="dropdown">
|
||||
{{tableClass}} <span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li data-ng-repeat="className in validClasses(tableName)"
|
||||
data-ng-click="setTableClass(tableName, className)"><a
|
||||
href="#/editor/{{keyspaceName}}">{{className}}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
<td><a type="button" class="btn btn-xs"
|
||||
data-ng-click="deleteTable(tableName)"> <span
|
||||
class="glyphicon glyphicon-remove"></span>
|
||||
</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" placeholder="Table"
|
||||
data-ng-model="tablesEditor.newTableName">
|
||||
<div class="input-group-btn">
|
||||
<button class="btn dropdown-toggle" type="button"
|
||||
data-toggle="dropdown">
|
||||
{{tableClass}} <span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li data-ng-repeat="(className, klass) in keyspace.Classes"
|
||||
data-ng-click="addTable(tablesEditor.newTableName, className)"><a
|
||||
href="#/editor/{{keyspaceName}}">{{className}}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr data-ng-show="tablesEditor.err">
|
||||
<td colspan="2">
|
||||
<div class="alert alert-danger">
|
||||
{{tablesEditor.err}}<a type="button" class="alert-danger pull-right"
|
||||
data-ng-click="clearTableError()"> <span
|
||||
class="glyphicon glyphicon-remove"></span>
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
|
@ -1,41 +0,0 @@
|
|||
<div class="col-md-2" data-ng-show="keyspaceName">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Table</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr data-ng-repeat="(tableName, tableClass) in keyspace.Tables">
|
||||
<td data-ng-class="{'alert-danger': tableHasError(tableName)}"
|
||||
title="{{tableHasError(tableName)}}">{{tableName}}</td>
|
||||
<td><a type="button" class="btn btn-xs"
|
||||
data-ng-click="deleteTable(tableName)"> <span
|
||||
class="glyphicon glyphicon-remove"></span>
|
||||
</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" placeholder="Table"
|
||||
data-ng-model="tablesEditor.newTableName">
|
||||
<div class="input-group-btn">
|
||||
<button class="btn btn-primary" type="button"
|
||||
data-ng-click="addTable(tablesEditor.newTableName, '')">
|
||||
<span class="glyphicon glyphicon-plus"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr data-ng-show="tablesEditor.err">
|
||||
<td>
|
||||
<div class="alert alert-danger">
|
||||
{{tablesEditor.err}}<a type="button" class="alert-danger pull-right"
|
||||
data-ng-click="clearTableError()"> <span
|
||||
class="glyphicon glyphicon-remove"></span>
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
|
@ -1,72 +0,0 @@
|
|||
|
||||
<div class="col-md-5">
|
||||
<table class="table table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Vindex</th>
|
||||
<th>Type</th>
|
||||
<th>Owner</th>
|
||||
<th>Params</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr>
|
||||
<tr data-ng-repeat="(vindexName, vindex) in keyspace.Vindexes">
|
||||
<td>{{vindexName}}</td>
|
||||
<td>
|
||||
<div class="dropdown">
|
||||
<button class="btn dropdown-toggle form-control" type="button"
|
||||
data-toggle="dropdown">
|
||||
{{vindex.Type}} <span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li data-ng-repeat="vindexType in vindexInfo.TypeNames"><a
|
||||
data-ng-click="setVindexType(vindex, vindexType)">{{vindexType}}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
<td><input type="text"
|
||||
data-ng-class="{'form-control': true, 'alert-warning': ownerHasWarning(vindex.Owner, vindexName)}"
|
||||
title="{{ownerHasWarning(vindex.Owner, vindexName)}}"
|
||||
placeholder="Owner" data-ng-model="vindex.Owner"></td>
|
||||
<td><table class="table-condensed table-bordered">
|
||||
<tr data-ng-repeat="(paramName, paramValue) in vindex.Params">
|
||||
<td>{{paramName}}</td>
|
||||
<td><input type="text" class="form-control" placeholder="Value"
|
||||
data-ng-model=vindex.Params[paramName]></td>
|
||||
</tr>
|
||||
</table></td>
|
||||
<td><a type="button" class="btn btn-xs"> <span
|
||||
class="glyphicon glyphicon-remove"
|
||||
data-ng-click="deleteVindex(vindexName)"></span>
|
||||
</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" placeholder="Vindex"
|
||||
data-ng-model="vindexEditor.vindexName">
|
||||
<div class="input-group-btn">
|
||||
<button class="btn dropdown-toggle" type="button"
|
||||
data-toggle="dropdown"><span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li data-ng-repeat="vindexType in vindexInfo.TypeNames"><a
|
||||
data-ng-click="addVindex(vindexEditor.vindexName, vindexType)">{{vindexType}}
|
||||
</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr data-ng-show="vindexEditor.err">
|
||||
<td colspan="2">
|
||||
<div class="alert alert-danger">
|
||||
{{vindexEditor.err}}<a type="button" class="alert-danger pull-right"
|
||||
data-ng-click="clearVindexError()"> <span
|
||||
class="glyphicon glyphicon-remove"></span>
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
|
@ -1,45 +0,0 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Etcd Explorer</title>
|
||||
<style>
|
||||
html {font-family: sans-serif;}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{{$path := .Path}}
|
||||
|
||||
{{if .Error}}
|
||||
<h1>Error</h1>
|
||||
{{.Error}}
|
||||
{{else}}
|
||||
<h1>{{breadcrumbs .Path}}</h1>
|
||||
{{range $key, $link := .Links}}
|
||||
<small>[<a href="{{$link}}">{{$key}}</a>]</small>
|
||||
{{end}}
|
||||
<h2>Children</h2>
|
||||
<ul>
|
||||
<li><a href="{{$path}}/..">..</a></li>
|
||||
{{range .Children}}
|
||||
<li><a href="{{$path}}/{{.}}">{{.}}</a></li>
|
||||
{{end}}
|
||||
|
||||
</ul>
|
||||
|
||||
{{with .Data}}
|
||||
<h2>Data</h2>
|
||||
<pre>{{.}}</pre>
|
||||
{{end}}
|
||||
|
||||
{{with .Actions}}
|
||||
<h2>Actions</h2>
|
||||
<ul>
|
||||
{{range $name, $href := .}}
|
||||
<li><a href="{{$href}}">{{$name}}</a></li>
|
||||
{{end}}
|
||||
</ul>
|
||||
{{end}}
|
||||
|
||||
{{end}}
|
||||
</body>
|
||||
</html>
|
|
@ -1,59 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<!--
|
||||
Copyright 2014, Google Inc. All rights reserved.
|
||||
Use of this source code is governed by a BSD-style
|
||||
license that can be found in the LICENSE file.
|
||||
-->
|
||||
|
||||
<html data-ng-app="app">
|
||||
<link rel="stylesheet"
|
||||
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css">
|
||||
<head>
|
||||
<base href="/" />
|
||||
<meta charset="US-ASCII">
|
||||
<title>Vitess Vtctld</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<nav class="navbar navbar-inverse">
|
||||
<div class="container-fluid">
|
||||
<div class="navbar-header">
|
||||
<a class="navbar-brand" href="/">Vitess</a>
|
||||
</div>
|
||||
<div id="navbar" class="navbar-collapse">
|
||||
<ul class="nav navbar-nav">
|
||||
<li><a href="/dbtopo">Topology</a></li>
|
||||
<li><a href="/serving_graph">Serving graph</a></li>
|
||||
<li><a href="#/editor">Schema editor</a></li>
|
||||
<li><a href="/vschema">Schema View</a></li>
|
||||
<li><a href="#/schema-manager">Schema Manager</a></li>
|
||||
{{with .ToplevelLinks}}
|
||||
{{range $name, $href := .}}
|
||||
<li><a href="{{$href}}">{{$name}}</a></li>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="container-fluid">
|
||||
<div data-ng-view></div>
|
||||
</div>
|
||||
<script src="https://code.jquery.com/jquery-2.1.3.js" type="text/javascript"></script>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/js/bootstrap.js"
|
||||
type="text/javascript"></script>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular.js"
|
||||
type="text/javascript"></script>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular-route.min.js"
|
||||
type="text/javascript"></script>
|
||||
<script src="content/vindex_info.js" type="text/javascript"></script>
|
||||
<script src="content/curschema.js" type="text/javascript"></script>
|
||||
<script src="content/editor/sidebar.js" type="text/javascript"></script>
|
||||
<script src="content/editor/keyspace.js" type="text/javascript"></script>
|
||||
<script src="content/editor/class/class.js" type="text/javascript"></script>
|
||||
<script src="content/load/load.js" type="text/javascript"></script>
|
||||
<script src="content/submit/submit.js" type="text/javascript"></script>
|
||||
<script src="content/schema_manager/index.js" type="text/javascript"></script>
|
||||
<script src="content/app.js" type="text/javascript"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,21 +0,0 @@
|
|||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div data-ng-include="'/content/editor/sidebar.html'"></div>
|
||||
<div class="col-md-6">
|
||||
<button type="button" class="btn btn-primary"
|
||||
data-ng-click="loadFromTopo()">Load from topo</button>
|
||||
<button type="button" class="btn btn-primary"
|
||||
data-ng-click="loadTestData()">Load test data</button>
|
||||
</div>
|
||||
<div class="alert alert-danger col-md-6" data-ng-show="loader.err">
|
||||
{{loader.err}}<a type="button" class="alert-danger pull-right"
|
||||
data-ng-click="clearLoaderError()"> <span
|
||||
class="glyphicon glyphicon-remove"></span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-6" data-ng-show="loadedJSON">
|
||||
<h4>Loaded data</h4>
|
||||
<pre>{{loadedJSON}}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,153 +0,0 @@
|
|||
/**
|
||||
* Copyright 2015, Google Inc. All rights reserved. Use of this source code is
|
||||
* governed by a BSD-style license that can be found in the LICENSE file.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
function LoadController($scope, $http, curSchema) {
|
||||
init();
|
||||
|
||||
function init() {
|
||||
$scope.loader = {};
|
||||
}
|
||||
|
||||
function setLoaded(loaded) {
|
||||
$scope.loadedJSON = angular.toJson(loaded, true);
|
||||
curSchema.init(loaded.Keyspaces);
|
||||
$scope.clearLoaderError();
|
||||
}
|
||||
|
||||
$scope.loadFromTopo = function() {
|
||||
$http.get("/vschema").success(function(data, status, headers, config) {
|
||||
try {
|
||||
var parser = new DOMParser();
|
||||
var xmlDoc = parser.parseFromString(data, "text/xml");
|
||||
var err = xmlDoc.getElementById("err");
|
||||
if (err) {
|
||||
$scope.loader.err = err.innerHTML;
|
||||
return;
|
||||
}
|
||||
var initialJSON = xmlDoc.getElementById("vschema").innerHTML;
|
||||
setLoaded(angular.fromJson(initialJSON));
|
||||
} catch (err) {
|
||||
$scope.loader.err = err.message;
|
||||
}
|
||||
}).error(function(data, status, headers, config) {
|
||||
$scope.loader.err = data;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.loadTestData = function() {
|
||||
var testData = {
|
||||
"user": {
|
||||
"Sharded": true,
|
||||
"Vindexes": {
|
||||
"user_index": {
|
||||
"Type": "hash_autoinc",
|
||||
"Owner": "user",
|
||||
"Params": {
|
||||
"Table": "user_lookup",
|
||||
"Column": "user_id"
|
||||
}
|
||||
},
|
||||
"music_user_map": {
|
||||
"Type": "lookup_hash_unique_autoinc",
|
||||
"Owner": "music_extra",
|
||||
"Params": {
|
||||
"Table": "music_user_map",
|
||||
"From": "music_id",
|
||||
"To": "user_id"
|
||||
}
|
||||
},
|
||||
"name_user_map": {
|
||||
"Type": "lookup_hash",
|
||||
"Owner": "user",
|
||||
"Params": {
|
||||
"Table": "name_user_map",
|
||||
"From": "name",
|
||||
"To": "user_id"
|
||||
}
|
||||
},
|
||||
"user_extra_index": {
|
||||
"Type": "hash",
|
||||
"Owner": "user_extra",
|
||||
"Params": {
|
||||
"Table": "user_extra_lookup",
|
||||
"Column": "user_extra_id"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Classes": {
|
||||
"user": {
|
||||
"ColVindexes": [
|
||||
{
|
||||
"Col": "id",
|
||||
"Name": "user_index"
|
||||
}, {
|
||||
"Col": "",
|
||||
"Name": "name_user_map"
|
||||
}, {
|
||||
"Col": "third",
|
||||
"Name": "name_user_map"
|
||||
}
|
||||
]
|
||||
},
|
||||
"user_extra": {
|
||||
"ColVindexes": [
|
||||
{
|
||||
"Col": "user_id",
|
||||
"Name": "user_index"
|
||||
}, {
|
||||
"Col": "id",
|
||||
"Name": "user_extra_index"
|
||||
}
|
||||
]
|
||||
},
|
||||
"music": {
|
||||
"ColVindexes": [
|
||||
{
|
||||
"Col": "user_id",
|
||||
"Name": "name_user_map"
|
||||
}, {
|
||||
"Col": "id",
|
||||
"Name": "user_index"
|
||||
}
|
||||
]
|
||||
},
|
||||
"music_extra": {
|
||||
"ColVindexes": [
|
||||
{
|
||||
"Col": "user_id",
|
||||
"Name": "music_user_map"
|
||||
}, {
|
||||
"Col": "music_id",
|
||||
"Name": "user_index1"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"Tables": {
|
||||
"user": "aa",
|
||||
"user_extra": "user_extra",
|
||||
"music": "music",
|
||||
"music_extra": "music_extra",
|
||||
"very_very_long_name": "music_extra"
|
||||
}
|
||||
},
|
||||
"main": {
|
||||
"Tables": {
|
||||
"main1": "aa",
|
||||
"main2": "",
|
||||
"music_extra": ""
|
||||
}
|
||||
}
|
||||
};
|
||||
setLoaded({
|
||||
"Keyspaces": testData
|
||||
});
|
||||
};
|
||||
|
||||
$scope.clearLoaderError = function() {
|
||||
$scope.loader.err = "";
|
||||
};
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
<div class="container">
|
||||
<div class="row-fluid">
|
||||
<div class="col-xs-2">
|
||||
<label>Keyspaces</label>
|
||||
<div class="list-group">
|
||||
<a href="" class="list-group-item"
|
||||
ng-class="{active:selected == keyspace}"
|
||||
ng-click="$parent.selected = keyspace; selectKeyspace(keyspace)"
|
||||
ng-repeat="keyspace in keyspaces">
|
||||
{{keyspace}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<textarea class="col-xs-8" rows="20" data-ng-model="schemaChanges"></textarea>
|
||||
</div>
|
||||
<div class="col-xs-2">
|
||||
<button type="button" class="btn btn-success" data-ng-click="submitSchema()" >Submit</button>
|
||||
<div ng-if="shouldShowSchemaChangeStatus">
|
||||
<label>Schema Change Status</label>
|
||||
<label>{{schemaStatus}}</label>
|
||||
<div class="progress">
|
||||
<div class="progress-bar progress-bar-success progress-bar-striped" role="progressbar" aria-valuenow="40" aria-valuemin="0" aria-valuemax="100" style="width: 40%">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,43 +0,0 @@
|
|||
/**
|
||||
* Copyright 2015, Google Inc. All rights reserved. Use of this source code is
|
||||
* governed by a BSD-style license that can be found in the LICENSE file.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
function SchemaManagerController($scope, $http) {
|
||||
init();
|
||||
|
||||
function init() {
|
||||
$scope.schemaChanges = "";
|
||||
$scope.selectedKeyspace = "";
|
||||
$scope.shouldShowSchemaChangeStatus = false;
|
||||
$scope.keyspaces = [];
|
||||
$http.get('/json/Keyspaces').
|
||||
success(function(data, status, headers, config) {
|
||||
$scope.keyspaces = data.Keyspaces;
|
||||
}).
|
||||
error(function(data, status, headers, config) {
|
||||
});
|
||||
}
|
||||
|
||||
$scope.selectKeyspace = function(selectedKeyspace) {
|
||||
$scope.selectedKeyspace = selectedKeyspace;
|
||||
}
|
||||
|
||||
$scope.submitSchema = function() {
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: '/json/schema-manager',
|
||||
data: {"keyspace": $scope.selectedKeyspace, "data": $scope.schemaChanges},
|
||||
dataType: 'json'
|
||||
}).success(function(data) {
|
||||
$scope.schemaStatus = data.responseText;
|
||||
$scope.shouldShowSchemaChangeStatus = true;
|
||||
$scope.$apply();
|
||||
}).error(function(data) {
|
||||
$scope.schemaStatus = data.responseText;
|
||||
$scope.shouldShowSchemaChangeStatus = true;
|
||||
$scope.$apply();
|
||||
});
|
||||
};
|
||||
}
|
|
@ -1,103 +0,0 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Serving Graph</title>
|
||||
<style>
|
||||
.keyspace {
|
||||
font-family: monospace;
|
||||
}
|
||||
td {
|
||||
border: 1px solid black;
|
||||
vertical-align:text-top;
|
||||
}
|
||||
ul.tablet-list li {
|
||||
list-style-type: none;
|
||||
margin: 0px;
|
||||
position: relative;
|
||||
}
|
||||
ul.tablet-list {
|
||||
padding-left: 0px;
|
||||
}
|
||||
table.keyspace {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
table.keyspace td {
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
}
|
||||
.topo-link {
|
||||
vertical-align: super;
|
||||
font-size: 20%;
|
||||
}
|
||||
table.keyspace td.legend {
|
||||
text-align: center;
|
||||
padding-left: 1ex;
|
||||
padding-right: 1ex;
|
||||
}
|
||||
table.keyspace thead {
|
||||
text-align: center;
|
||||
border-color: #666;
|
||||
background-color: #dedede;
|
||||
}
|
||||
.not-replicating {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
{{with .Errors}}
|
||||
<h1>Errors</h1>
|
||||
<ul>
|
||||
{{range $i, $error := . }}
|
||||
<li>{{$error}}</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
{{end}}
|
||||
<h1>Serving Graph for {{.Cell}}</h1>
|
||||
<ul>
|
||||
{{range $keyspace, $shards := .Keyspaces}}
|
||||
<li><a href="#keyspace_{{$keyspace}}">{{$keyspace}}</a></li>
|
||||
{{end}}
|
||||
</ul>
|
||||
{{ $cell := .Cell}}
|
||||
{{range $keyspace, $shards := .Keyspaces}}
|
||||
<h2><a name="keyspace_{{$keyspace}}"></a>{{srv_keyspace $cell $keyspace}}</h2>
|
||||
<ul>
|
||||
{{range $servedType, $servedFrom := $shards.ServedFrom}}
|
||||
<li><i>Serving {{$servedType}} from <a href="#keyspace_{{$servedFrom}}">{{$servedFrom}}</a></i></li>
|
||||
{{end}}
|
||||
</ul>
|
||||
<table class="keyspace">
|
||||
<thead>
|
||||
{{range $i, $shard := $shards.ShardNodes}}
|
||||
{{if intequal $i 0}}<td class="legend">type</td>{{end}}
|
||||
<td>
|
||||
{{srv_shard $.Cell $keyspace $shard}}<br/>
|
||||
{{range $j, $servedType := $shard.ServedTypes}}
|
||||
<i>{{$servedType}}</i><br/>
|
||||
{{end}}
|
||||
</td>
|
||||
{{end}}
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range $processedType := $shards.TabletTypes}}
|
||||
<tr>
|
||||
{{range $i, $shard := $shards.ShardNodes}}
|
||||
{{if intequal $i 0}}<td class="legend">{{$processedType}}</td>{{end}}
|
||||
<td>
|
||||
<ul class="tablet-list">
|
||||
{{range index $shard.TabletNodes $processedType}}
|
||||
<li title="{{.Alias}}">{{tablet .Alias .ShortName}}</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
</td>
|
||||
{{end}}
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{end}}
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -1,57 +0,0 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Serving Graph</title>
|
||||
<style>
|
||||
.keyspace {
|
||||
font-family: monospace;
|
||||
}
|
||||
td {
|
||||
border: 1px solid black;
|
||||
vertical-align:text-top;
|
||||
}
|
||||
ul.tablet-list li {
|
||||
list-style-type: none;
|
||||
margin: 0px;
|
||||
position: relative;
|
||||
}
|
||||
ul.tablet-list {
|
||||
padding-left: 0px;
|
||||
}
|
||||
table.keyspace {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
table.keyspace td {
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
}
|
||||
.topo-link {
|
||||
vertical-align: super;
|
||||
font-size: 20%;
|
||||
}
|
||||
table.keyspace td.legend {
|
||||
text-align: center;
|
||||
padding-left: 1ex;
|
||||
padding-right: 1ex;
|
||||
}
|
||||
table.keyspace thead {
|
||||
text-align: center;
|
||||
border-color: #666;
|
||||
background-color: #dedede;
|
||||
}
|
||||
.not-replicating {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Serving Graph</h1>
|
||||
<h2>Available cells</h2>
|
||||
<ul>
|
||||
{{range .}}
|
||||
<li><a href="/serving_graph/{{.}}">{{.}}</a>
|
||||
{{end}}
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
|
@ -1,29 +0,0 @@
|
|||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div data-ng-include="'/content/editor/sidebar.html'"></div>
|
||||
<div class="col-md-8">
|
||||
<button type="button" class="btn btn-primary pull-right"
|
||||
data-ng-click="submitToTopo()">Submit to topo</button>
|
||||
</div>
|
||||
<div class="alert alert-danger col-md-8" data-ng-show="submitter.err">
|
||||
{{submitter.err}}<a type="button" class="alert-danger pull-right"
|
||||
data-ng-click="clearSubmitter()"> <span
|
||||
class="glyphicon glyphicon-remove"></span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="alert alert-success col-md-8" data-ng-show="submitter.ok">
|
||||
{{submitter.ok}}<a type="button" class="alert-success pull-right"
|
||||
data-ng-click="clearSubmitter()"> <span
|
||||
class="glyphicon glyphicon-remove"></span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<h4>Original</h4>
|
||||
<pre>{{originalJSON}}</pre>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<h4>Edited</h4>
|
||||
<pre>{{keyspacesJSON}}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,48 +0,0 @@
|
|||
/**
|
||||
* Copyright 2014, Google Inc. All rights reserved. Use of this source code is
|
||||
* governed by a BSD-style license that can be found in the LICENSE file.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
function SubmitController($scope, $http, curSchema) {
|
||||
init();
|
||||
|
||||
function init() {
|
||||
$scope.keyspacesJSON = angular.toJson({
|
||||
"Keyspaces": curSchema.keyspaces
|
||||
}, true);
|
||||
$scope.originalJSON = angular.toJson({
|
||||
"Keyspaces": curSchema.original
|
||||
}, true);
|
||||
$scope.submitter = {};
|
||||
}
|
||||
|
||||
$scope.submitToTopo = function() {
|
||||
try {
|
||||
$http({
|
||||
method: 'POST',
|
||||
url: '/vschema',
|
||||
data: "vschema=" + $scope.keyspacesJSON,
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
}
|
||||
}).success(function(data, status, headers, config) {
|
||||
var parser = new DOMParser();
|
||||
var xmlDoc = parser.parseFromString(data, "text/xml");
|
||||
var err = xmlDoc.getElementById("err");
|
||||
if (err) {
|
||||
$scope.submitter.err = err.innerHTML;
|
||||
return;
|
||||
}
|
||||
$scope.submitter.ok = "Submitted!";
|
||||
});
|
||||
} catch (err) {
|
||||
$scope.submitter.err = err.message;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.clearSubmitter = function() {
|
||||
$scope.submitter.err = "";
|
||||
$scope.submitter.ok = "";
|
||||
};
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
/**
|
||||
* Copyright 2014, Google Inc. All rights reserved. Use of this source code is
|
||||
* governed by a BSD-style license that can be found in the LICENSE file.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
function vindexInfo() {
|
||||
var info = {};
|
||||
info.Types = {
|
||||
"numeric": {
|
||||
"Type": "functional",
|
||||
"Unique": true,
|
||||
"Params": []
|
||||
},
|
||||
"hash": {
|
||||
"Type": "functional",
|
||||
"Unique": true,
|
||||
"Params": [
|
||||
"Table", "Column"
|
||||
]
|
||||
},
|
||||
"hash_autoinc": {
|
||||
"Type": "functional",
|
||||
"Unique": true,
|
||||
"Params": [
|
||||
"Table", "Column"
|
||||
]
|
||||
},
|
||||
"lookup_hash": {
|
||||
"Type": "lookup",
|
||||
"Unique": false,
|
||||
"Params": [
|
||||
"Table", "From", "To"
|
||||
]
|
||||
},
|
||||
"lookup_hash_unique": {
|
||||
"Type": "lookup",
|
||||
"Unique": true,
|
||||
"Params": [
|
||||
"Table", "From", "To"
|
||||
]
|
||||
},
|
||||
"lookup_hash_autoinc": {
|
||||
"Type": "lookup",
|
||||
"Unique": false,
|
||||
"Params": [
|
||||
"Table", "From", "To"
|
||||
]
|
||||
},
|
||||
"lookup_hash_unique_autoinc": {
|
||||
"Type": "lookup",
|
||||
"Unique": true,
|
||||
"Params": [
|
||||
"Table", "From", "To"
|
||||
]
|
||||
}
|
||||
};
|
||||
info.TypeNames = Object.keys(info.Types);
|
||||
return info;
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>VSchema view</title>
|
||||
<style>
|
||||
html {font-family: sans-serif;}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
{{if .Error}}
|
||||
<h2>Error</h2>
|
||||
<div id="err">{{.Error}}</div>
|
||||
{{else}}
|
||||
<h2>Success</h2>
|
||||
{{end}}
|
||||
<pre id="vschema">{{.Output}}</pre>
|
||||
|
||||
<form method="post">
|
||||
Upload VSchema<br><textarea name="vschema" cols=80 rows=30>{{.Input}}</textarea><br>
|
||||
<input type="submit" value="Submit">
|
||||
</form>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -1,45 +0,0 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Zookeeper Explorer</title>
|
||||
<style>
|
||||
html {font-family: sans-serif;}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{{$path := .Path}}
|
||||
|
||||
{{if .Error}}
|
||||
<h1>Error</h1>
|
||||
{{.Error}}
|
||||
{{else}}
|
||||
<h1>{{breadcrumbs .Path}}</h1>
|
||||
{{range $key, $link := .Links}}
|
||||
<small>[<a href="{{$link}}">{{$key}}</a>]</small>
|
||||
{{end}}
|
||||
<h2>Children</h2>
|
||||
<ul>
|
||||
<li><a href="{{$path}}/..">..</a></li>
|
||||
{{range .Children}}
|
||||
<li><a href="{{$path}}/{{.}}">{{.}}</a></li>
|
||||
{{end}}
|
||||
|
||||
</ul>
|
||||
|
||||
{{with .Data}}
|
||||
<h2>Data</h2>
|
||||
<pre>{{.}}</pre>
|
||||
{{end}}
|
||||
|
||||
{{with .Actions}}
|
||||
<h2>Actions</h2>
|
||||
<ul>
|
||||
{{range $name, $href := .}}
|
||||
<li><a href="{{$href}}">{{$name}}</a></li>
|
||||
{{end}}
|
||||
</ul>
|
||||
{{end}}
|
||||
|
||||
{{end}}
|
||||
</body>
|
||||
</html>
|
|
@ -17,8 +17,6 @@ import (
|
|||
"github.com/youtube/vitess/go/vt/servenv"
|
||||
"github.com/youtube/vitess/go/vt/tabletmanager/tmclient"
|
||||
"github.com/youtube/vitess/go/vt/topo"
|
||||
"github.com/youtube/vitess/go/vt/topo/topoproto"
|
||||
"github.com/youtube/vitess/go/vt/topotools"
|
||||
"github.com/youtube/vitess/go/vt/wrangler"
|
||||
|
||||
topodatapb "github.com/youtube/vitess/go/vt/proto/topodata"
|
||||
|
@ -26,12 +24,15 @@ import (
|
|||
|
||||
var (
|
||||
webDir = flag.String("web_dir", "", "directory from which to serve vtctld web interface resources")
|
||||
templateDir = flag.String("templates", "", "directory containing templates")
|
||||
debug = flag.Bool("debug", false, "recompile templates for every request")
|
||||
schemaChangeDir = flag.String("schema_change_dir", "", "directory contains schema changes for all keyspaces. Each keyspace has its own directory and schema changes are expected to live in '$KEYSPACE/input' dir. e.g. test_keyspace/input/*sql, each sql file represents a schema change")
|
||||
schemaChangeController = flag.String("schema_change_controller", "", "schema change controller is responsible for finding schema changes and responsing schema change events")
|
||||
schemaChangeCheckInterval = flag.Int("schema_change_check_interval", 60, "this value decides how often we check schema change dir, in seconds")
|
||||
schemaChangeUser = flag.String("schema_change_user", "", "The user who submits this schema change.")
|
||||
|
||||
_ = flag.String("templates", "", "<deprecated>")
|
||||
|
||||
appPrefix = "/app/"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -44,32 +45,16 @@ func httpErrorf(w http.ResponseWriter, r *http.Request, format string, args ...i
|
|||
http.Error(w, errMsg, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
// DbTopologyResult encapsulates a topotools.Topology and the possible error
|
||||
type DbTopologyResult struct {
|
||||
Topology *topotools.Topology
|
||||
Error string
|
||||
}
|
||||
|
||||
// IndexContent has the list of toplevel links
|
||||
type IndexContent struct {
|
||||
// maps a name to a linked URL
|
||||
ToplevelLinks map[string]string
|
||||
}
|
||||
|
||||
// used at runtime by plug-ins
|
||||
var templateLoader *TemplateLoader
|
||||
var actionRepo *ActionRepository
|
||||
var indexContent = IndexContent{
|
||||
ToplevelLinks: map[string]string{},
|
||||
}
|
||||
|
||||
var ts topo.Server
|
||||
var (
|
||||
actionRepo *ActionRepository
|
||||
ts topo.Server
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
servenv.Init()
|
||||
defer servenv.Close()
|
||||
templateLoader = NewTemplateLoader(*templateDir, *debug)
|
||||
|
||||
ts = topo.GetServer()
|
||||
defer topo.CloseServers()
|
||||
|
@ -147,138 +132,13 @@ func main() {
|
|||
return "", wr.ReloadSchema(ctx, tabletAlias)
|
||||
})
|
||||
|
||||
// keyspace actions
|
||||
http.HandleFunc("/keyspace_actions", func(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
httpErrorf(w, r, "cannot parse form: %s", err)
|
||||
return
|
||||
}
|
||||
action := r.FormValue("action")
|
||||
if action == "" {
|
||||
http.Error(w, "no action provided", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
keyspace := r.FormValue("keyspace")
|
||||
if keyspace == "" {
|
||||
http.Error(w, "no keyspace provided", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
ctx := context.Background()
|
||||
result := actionRepo.ApplyKeyspaceAction(ctx, action, keyspace, r)
|
||||
|
||||
templateLoader.ServeTemplate("action.html", result, w, r)
|
||||
})
|
||||
|
||||
// shard actions
|
||||
http.HandleFunc("/shard_actions", func(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
httpErrorf(w, r, "cannot parse form: %s", err)
|
||||
return
|
||||
}
|
||||
action := r.FormValue("action")
|
||||
if action == "" {
|
||||
http.Error(w, "no action provided", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
keyspace := r.FormValue("keyspace")
|
||||
if keyspace == "" {
|
||||
http.Error(w, "no keyspace provided", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
shard := r.FormValue("shard")
|
||||
if shard == "" {
|
||||
http.Error(w, "no shard provided", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
ctx := context.Background()
|
||||
result := actionRepo.ApplyShardAction(ctx, action, keyspace, shard, r)
|
||||
|
||||
templateLoader.ServeTemplate("action.html", result, w, r)
|
||||
})
|
||||
|
||||
// tablet actions
|
||||
http.HandleFunc("/tablet_actions", func(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
httpErrorf(w, r, "cannot parse form: %s", err)
|
||||
return
|
||||
}
|
||||
action := r.FormValue("action")
|
||||
if action == "" {
|
||||
http.Error(w, "no action provided", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
alias := r.FormValue("alias")
|
||||
if alias == "" {
|
||||
http.Error(w, "no alias provided", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
tabletAlias, err := topoproto.ParseTabletAlias(alias)
|
||||
if err != nil {
|
||||
http.Error(w, "bad alias provided", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
ctx := context.Background()
|
||||
result := actionRepo.ApplyTabletAction(ctx, action, tabletAlias, r)
|
||||
|
||||
templateLoader.ServeTemplate("action.html", result, w, r)
|
||||
})
|
||||
|
||||
// topology server
|
||||
http.HandleFunc("/dbtopo", func(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
httpErrorf(w, r, "cannot parse form: %s", err)
|
||||
return
|
||||
}
|
||||
result := DbTopologyResult{}
|
||||
ctx := context.TODO()
|
||||
topology, err := topotools.DbTopology(ctx, ts)
|
||||
if err == nil && modifyDbTopology != nil {
|
||||
err = modifyDbTopology(ctx, ts, topology)
|
||||
}
|
||||
if err != nil {
|
||||
result.Error = err.Error()
|
||||
} else {
|
||||
result.Topology = topology
|
||||
}
|
||||
templateLoader.ServeTemplate("dbtopo.html", result, w, r)
|
||||
})
|
||||
|
||||
// serving graph
|
||||
http.HandleFunc("/serving_graph/", func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := context.Background()
|
||||
parts := strings.Split(r.URL.Path, "/")
|
||||
|
||||
cell := parts[len(parts)-1]
|
||||
if cell == "" {
|
||||
cells, err := ts.GetKnownCells(ctx)
|
||||
if err != nil {
|
||||
httpErrorf(w, r, "cannot get known cells: %v", err)
|
||||
return
|
||||
}
|
||||
templateLoader.ServeTemplate("serving_graph_cells.html", cells, w, r)
|
||||
return
|
||||
}
|
||||
|
||||
servingGraph := topotools.DbServingGraph(ctx, ts, cell)
|
||||
if modifyDbServingGraph != nil {
|
||||
modifyDbServingGraph(ctx, ts, servingGraph)
|
||||
}
|
||||
templateLoader.ServeTemplate("serving_graph.html", servingGraph, w, r)
|
||||
})
|
||||
|
||||
// Anything unrecognized gets redirected to the main app page.
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, "/app/", http.StatusFound)
|
||||
})
|
||||
|
||||
http.HandleFunc("/content/", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.ServeFile(w, r, *templateDir+r.URL.Path[8:])
|
||||
http.Redirect(w, r, appPrefix, http.StatusFound)
|
||||
})
|
||||
|
||||
// Serve the static files for the vtctld web app.
|
||||
http.HandleFunc("/app/", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.HandleFunc(appPrefix, func(w http.ResponseWriter, r *http.Request) {
|
||||
// Strip the prefix.
|
||||
parts := strings.SplitN(r.URL.Path, "/", 3)
|
||||
if len(parts) != 3 {
|
||||
|
@ -295,32 +155,6 @@ func main() {
|
|||
// Serve the REST API for the vtctld web app.
|
||||
initAPI(context.Background(), ts, actionRepo)
|
||||
|
||||
// vschema viewer
|
||||
http.HandleFunc("/vschema", func(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
httpErrorf(w, r, "cannot parse form: %s", err)
|
||||
return
|
||||
}
|
||||
var data struct {
|
||||
Error error
|
||||
Input, Output string
|
||||
}
|
||||
ctx := context.Background()
|
||||
switch r.Method {
|
||||
case "POST":
|
||||
data.Input = r.FormValue("vschema")
|
||||
data.Error = ts.SaveVSchema(ctx, data.Input)
|
||||
}
|
||||
vschema, err := ts.GetVSchema(ctx)
|
||||
if err != nil {
|
||||
if data.Error == nil {
|
||||
data.Error = fmt.Errorf("Error fetching schema: %s", err)
|
||||
}
|
||||
}
|
||||
data.Output = vschema
|
||||
templateLoader.ServeTemplate("vschema.html", data, w, r)
|
||||
})
|
||||
|
||||
// redirects for explorers
|
||||
http.HandleFunc("/explorers/redirect", func(w http.ResponseWriter, r *http.Request) {
|
||||
if explorer == nil {
|
||||
|
@ -332,7 +166,7 @@ func main() {
|
|||
return
|
||||
}
|
||||
|
||||
target, err := handleExplorerRedirect(r)
|
||||
target, err := handleExplorerRedirect(context.Background(), ts, r)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
|
@ -341,152 +175,7 @@ func main() {
|
|||
http.Redirect(w, r, target, http.StatusFound)
|
||||
})
|
||||
|
||||
// serve some data
|
||||
knownCellsCache := newKnownCellsCache(ts)
|
||||
http.HandleFunc("/json/KnownCells", func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := context.Background()
|
||||
result, err := knownCellsCache.Get(ctx)
|
||||
if err != nil {
|
||||
httpErrorf(w, r, "error getting known cells: %v", err)
|
||||
return
|
||||
}
|
||||
w.Write(result)
|
||||
})
|
||||
|
||||
keyspacesCache := newKeyspacesCache(ts)
|
||||
http.HandleFunc("/json/Keyspaces", func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := context.Background()
|
||||
result, err := keyspacesCache.Get(ctx)
|
||||
if err != nil {
|
||||
httpErrorf(w, r, "error getting keyspaces: %v", err)
|
||||
return
|
||||
}
|
||||
w.Write(result)
|
||||
})
|
||||
|
||||
keyspaceCache := newKeyspaceCache(ts)
|
||||
http.HandleFunc("/json/Keyspace", func(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
httpErrorf(w, r, "cannot parse form: %s", err)
|
||||
return
|
||||
}
|
||||
keyspace := r.FormValue("keyspace")
|
||||
if keyspace == "" {
|
||||
http.Error(w, "no keyspace provided", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
ctx := context.Background()
|
||||
result, err := keyspaceCache.Get(ctx, keyspace)
|
||||
if err != nil {
|
||||
httpErrorf(w, r, "error getting keyspace: %v", err)
|
||||
return
|
||||
}
|
||||
w.Write(result)
|
||||
})
|
||||
|
||||
shardNamesCache := newShardNamesCache(ts)
|
||||
http.HandleFunc("/json/ShardNames", func(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
httpErrorf(w, r, "cannot parse form: %s", err)
|
||||
return
|
||||
}
|
||||
keyspace := r.FormValue("keyspace")
|
||||
if keyspace == "" {
|
||||
http.Error(w, "no keyspace provided", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
ctx := context.Background()
|
||||
result, err := shardNamesCache.Get(ctx, keyspace)
|
||||
if err != nil {
|
||||
httpErrorf(w, r, "error getting shardNames: %v", err)
|
||||
return
|
||||
}
|
||||
w.Write(result)
|
||||
})
|
||||
|
||||
shardCache := newShardCache(ts)
|
||||
http.HandleFunc("/json/Shard", func(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
httpErrorf(w, r, "cannot parse form: %s", err)
|
||||
return
|
||||
}
|
||||
keyspace := r.FormValue("keyspace")
|
||||
if keyspace == "" {
|
||||
http.Error(w, "no keyspace provided", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
shard := r.FormValue("shard")
|
||||
if shard == "" {
|
||||
http.Error(w, "no shard provided", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
ctx := context.Background()
|
||||
result, err := shardCache.Get(ctx, keyspace+"/"+shard)
|
||||
if err != nil {
|
||||
httpErrorf(w, r, "error getting shard: %v", err)
|
||||
return
|
||||
}
|
||||
w.Write(result)
|
||||
})
|
||||
|
||||
cellShardTabletsCache := newCellShardTabletsCache(ts)
|
||||
http.HandleFunc("/json/CellShardTablets", func(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
httpErrorf(w, r, "cannot parse form: %s", err)
|
||||
return
|
||||
}
|
||||
cell := r.FormValue("cell")
|
||||
if cell == "" {
|
||||
http.Error(w, "no cell provided", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
keyspace := r.FormValue("keyspace")
|
||||
if keyspace == "" {
|
||||
http.Error(w, "no keyspace provided", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
shard := r.FormValue("shard")
|
||||
if shard == "" {
|
||||
http.Error(w, "no shard provided", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
ctx := context.Background()
|
||||
result, err := cellShardTabletsCache.Get(ctx, cell+"/"+keyspace+"/"+shard)
|
||||
if err != nil {
|
||||
httpErrorf(w, r, "error getting shard: %v", err)
|
||||
return
|
||||
}
|
||||
w.Write(result)
|
||||
})
|
||||
|
||||
// flush all data and will force a full client reload
|
||||
http.HandleFunc("/json/flush", func(w http.ResponseWriter, r *http.Request) {
|
||||
knownCellsCache.Flush()
|
||||
keyspacesCache.Flush()
|
||||
keyspaceCache.Flush()
|
||||
shardNamesCache.Flush()
|
||||
shardCache.Flush()
|
||||
cellShardTabletsCache.Flush()
|
||||
})
|
||||
|
||||
http.HandleFunc("/json/schema-manager", func(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
httpErrorf(w, r, "cannot parse form: %s", err)
|
||||
return
|
||||
}
|
||||
sqlStr := r.FormValue("data")
|
||||
keyspace := r.FormValue("keyspace")
|
||||
executor := schemamanager.NewTabletExecutor(
|
||||
tmclient.NewTabletManagerClient(),
|
||||
ts)
|
||||
|
||||
ctx := context.Background()
|
||||
schemamanager.Run(
|
||||
ctx,
|
||||
schemamanager.NewUIController(sqlStr, keyspace, w),
|
||||
executor,
|
||||
)
|
||||
})
|
||||
// Start schema manager service.
|
||||
if *schemaChangeDir != "" {
|
||||
interval := 60
|
||||
if *schemaChangeCheckInterval > 0 {
|
||||
|
@ -521,5 +210,6 @@ func main() {
|
|||
})
|
||||
servenv.OnClose(func() { timer.Stop() })
|
||||
}
|
||||
|
||||
servenv.RunDefault()
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
package etcdtopo
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
|
@ -13,10 +12,6 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/coreos/go-etcd/etcd"
|
||||
ctlproto "github.com/youtube/vitess/go/cmd/vtctld/proto"
|
||||
"github.com/youtube/vitess/go/netutil"
|
||||
|
||||
topodatapb "github.com/youtube/vitess/go/vt/proto/topodata"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -34,43 +29,8 @@ func NewExplorer(ts *Server) *Explorer {
|
|||
return &Explorer{ts: ts}
|
||||
}
|
||||
|
||||
// GetKeyspacePath implements vtctld Explorer.
|
||||
func (ex Explorer) GetKeyspacePath(keyspace string) string {
|
||||
return path.Join(explorerRoot, globalCell, keyspaceDirPath(keyspace))
|
||||
}
|
||||
|
||||
// GetShardPath implements vtctld Explorer.
|
||||
func (ex Explorer) GetShardPath(keyspace, shard string) string {
|
||||
return path.Join(explorerRoot, globalCell, shardDirPath(keyspace, shard))
|
||||
}
|
||||
|
||||
// GetSrvKeyspacePath implements vtctld Explorer.
|
||||
func (ex Explorer) GetSrvKeyspacePath(cell, keyspace string) string {
|
||||
return path.Join(explorerRoot, cell, srvKeyspaceDirPath(keyspace))
|
||||
}
|
||||
|
||||
// GetSrvShardPath implements vtctld Explorer.
|
||||
func (ex Explorer) GetSrvShardPath(cell, keyspace, shard string) string {
|
||||
return path.Join(explorerRoot, cell, srvShardDirPath(keyspace, shard))
|
||||
}
|
||||
|
||||
// GetSrvTypePath implements vtctld Explorer.
|
||||
func (ex Explorer) GetSrvTypePath(cell, keyspace, shard string, tabletType topodatapb.TabletType) string {
|
||||
return path.Join(explorerRoot, cell, endPointsDirPath(keyspace, shard, tabletType))
|
||||
}
|
||||
|
||||
// GetTabletPath implements vtctld Explorer.
|
||||
func (ex Explorer) GetTabletPath(alias *topodatapb.TabletAlias) string {
|
||||
return path.Join(explorerRoot, alias.Cell, tabletDirPath(alias))
|
||||
}
|
||||
|
||||
// GetReplicationSlaves implements vtctld Explorer.
|
||||
func (ex Explorer) GetReplicationSlaves(cell, keyspace, shard string) string {
|
||||
return path.Join(explorerRoot, cell, shardReplicationDirPath(keyspace, shard))
|
||||
}
|
||||
|
||||
// HandlePath implements vtctld Explorer.
|
||||
func (ex Explorer) HandlePath(actionRepo ctlproto.ActionRepository, rPath string, r *http.Request) interface{} {
|
||||
func (ex Explorer) HandlePath(rPath string, r *http.Request) interface{} {
|
||||
result := newExplorerResult(rPath)
|
||||
|
||||
// Cut off explorerRoot prefix.
|
||||
|
@ -125,17 +85,6 @@ func (ex Explorer) HandlePath(actionRepo ctlproto.ActionRepository, rPath string
|
|||
result.Children = append(result.Children, path.Base(node.Key))
|
||||
}
|
||||
|
||||
// Populate actions.
|
||||
if m, _ := path.Match(keyspaceDirPath("*"), rPath); m {
|
||||
actionRepo.PopulateKeyspaceActions(result.Actions, path.Base(rPath))
|
||||
} else if m, _ := path.Match(shardDirPath("*", "*"), rPath); m {
|
||||
if keyspace, shard, err := splitShardDirPath(rPath); err == nil {
|
||||
actionRepo.PopulateShardActions(result.Actions, keyspace, shard)
|
||||
}
|
||||
} else if m, _ := path.Match(path.Join(tabletsDirPath, "*"), rPath); m {
|
||||
actionRepo.PopulateTabletActions(result.Actions, path.Base(rPath), r)
|
||||
addTabletLinks(result, result.Data)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
|
@ -193,15 +142,3 @@ func splitShardDirPath(p string) (keyspace, shard string, err error) {
|
|||
}
|
||||
return parts[3], parts[4], nil
|
||||
}
|
||||
|
||||
func addTabletLinks(result *explorerResult, data string) {
|
||||
t := &topodatapb.Tablet{}
|
||||
err := json.Unmarshal([]byte(data), t)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if port, ok := t.PortMap["vt"]; ok {
|
||||
result.Links["status"] = template.URL(fmt.Sprintf("http://%v/debug/status", netutil.JoinHostPort(t.Hostname, port)))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,8 +6,6 @@ package etcdtopo
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"path"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
@ -60,7 +58,7 @@ func TestSplitShardDirPath(t *testing.T) {
|
|||
func TestHandlePathInvalid(t *testing.T) {
|
||||
// Don't panic!
|
||||
ex := NewExplorer(nil)
|
||||
result := ex.HandlePath(nil, "xxx", nil)
|
||||
result := ex.HandlePath("xxx", nil)
|
||||
exResult := result.(*explorerResult)
|
||||
if want := "invalid"; !strings.Contains(exResult.Error, want) {
|
||||
t.Errorf("HandlePath returned wrong error: got %q, want %q", exResult.Error, want)
|
||||
|
@ -74,7 +72,7 @@ func TestHandlePathRoot(t *testing.T) {
|
|||
|
||||
ts := newTestServer(t, cells)
|
||||
ex := NewExplorer(ts)
|
||||
result := ex.HandlePath(nil, input, nil)
|
||||
result := ex.HandlePath(input, nil)
|
||||
exResult := result.(*explorerResult)
|
||||
if got := exResult.Children; !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("HandlePath(%q) = %v, want %v", input, got, want)
|
||||
|
@ -100,9 +98,8 @@ func TestHandlePathKeyspace(t *testing.T) {
|
|||
t.Fatalf("CreateShard error: %v", err)
|
||||
}
|
||||
|
||||
m := &mockActionRepo{}
|
||||
ex := NewExplorer(ts)
|
||||
result := ex.HandlePath(m, input, nil)
|
||||
result := ex.HandlePath(input, nil)
|
||||
exResult := result.(*explorerResult)
|
||||
if got := exResult.Data; got != want {
|
||||
t.Errorf("HandlePath(%q) = %q, want %q", input, got, want)
|
||||
|
@ -110,12 +107,6 @@ func TestHandlePathKeyspace(t *testing.T) {
|
|||
if got, want := exResult.Children, []string{"10-20", "20-30"}; !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("Children = %v, want %v", got, want)
|
||||
}
|
||||
if m.keyspaceActions == nil {
|
||||
t.Errorf("ActionRepository.PopulateKeyspaceActions not called")
|
||||
}
|
||||
if m.keyspace != "test_keyspace" {
|
||||
t.Errorf("ActionRepository called with keyspace %q, want %q", m.keyspace, "test_keyspace")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlePathShard(t *testing.T) {
|
||||
|
@ -134,22 +125,12 @@ func TestHandlePathShard(t *testing.T) {
|
|||
t.Fatalf("CreateShard error: %v", err)
|
||||
}
|
||||
|
||||
m := &mockActionRepo{}
|
||||
ex := NewExplorer(ts)
|
||||
result := ex.HandlePath(m, input, nil)
|
||||
result := ex.HandlePath(input, nil)
|
||||
exResult := result.(*explorerResult)
|
||||
if got := exResult.Data; got != want {
|
||||
t.Errorf("HandlePath(%q) = %q, want %q", input, got, want)
|
||||
}
|
||||
if m.shardActions == nil {
|
||||
t.Errorf("ActionRepository.PopulateShardActions not called")
|
||||
}
|
||||
if m.keyspace != "test_keyspace" {
|
||||
t.Errorf("ActionRepository called with keyspace %q, want %q", m.keyspace, "test_keyspace")
|
||||
}
|
||||
if m.shard != "-80" {
|
||||
t.Errorf("ActionRepository called with shard %q, want %q", m.shard, "-80")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlePathTablet(t *testing.T) {
|
||||
|
@ -168,44 +149,10 @@ func TestHandlePathTablet(t *testing.T) {
|
|||
t.Fatalf("CreateTablet error: %v", err)
|
||||
}
|
||||
|
||||
m := &mockActionRepo{}
|
||||
ex := NewExplorer(ts)
|
||||
result := ex.HandlePath(m, input, nil)
|
||||
result := ex.HandlePath(input, nil)
|
||||
exResult := result.(*explorerResult)
|
||||
if got := exResult.Data; got != want {
|
||||
t.Errorf("HandlePath(%q) = %q, want %q", input, got, want)
|
||||
}
|
||||
wantLinks := map[string]template.URL{
|
||||
"status": template.URL("http://example.com:4321/debug/status"),
|
||||
}
|
||||
for k, want := range wantLinks {
|
||||
if got := exResult.Links[k]; got != want {
|
||||
t.Errorf("Links[%q] = %v, want %v", k, got, want)
|
||||
}
|
||||
}
|
||||
if m.tabletActions == nil {
|
||||
t.Errorf("ActionRepository.PopulateTabletActions not called")
|
||||
}
|
||||
if m.tablet != "cell1-0000000123" {
|
||||
t.Errorf("ActionRepository called with tablet %q, want %q", m.tablet, "cell1-0000000123")
|
||||
}
|
||||
}
|
||||
|
||||
type mockActionRepo struct {
|
||||
keyspace, shard, tablet string
|
||||
keyspaceActions, shardActions, tabletActions map[string]template.URL
|
||||
}
|
||||
|
||||
func (m *mockActionRepo) PopulateKeyspaceActions(actions map[string]template.URL, keyspace string) {
|
||||
m.keyspace = keyspace
|
||||
m.keyspaceActions = actions
|
||||
}
|
||||
func (m *mockActionRepo) PopulateShardActions(actions map[string]template.URL, keyspace, shard string) {
|
||||
m.keyspace = keyspace
|
||||
m.shard = shard
|
||||
m.shardActions = actions
|
||||
}
|
||||
func (m *mockActionRepo) PopulateTabletActions(actions map[string]template.URL, tablet string, r *http.Request) {
|
||||
m.tablet = tablet
|
||||
m.tabletActions = actions
|
||||
}
|
||||
|
|
|
@ -1,331 +0,0 @@
|
|||
package topotools
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
log "github.com/golang/glog"
|
||||
"github.com/youtube/vitess/go/netutil"
|
||||
"github.com/youtube/vitess/go/vt/concurrency"
|
||||
"github.com/youtube/vitess/go/vt/topo"
|
||||
"github.com/youtube/vitess/go/vt/topo/topoproto"
|
||||
|
||||
topodatapb "github.com/youtube/vitess/go/vt/proto/topodata"
|
||||
)
|
||||
|
||||
// TabletNode is the representation of a tablet in the db topology.
|
||||
// It can be constructed from a Tablet object, or from an EndPoint.
|
||||
type TabletNode struct {
|
||||
Host string
|
||||
Alias *topodatapb.TabletAlias
|
||||
Port int32
|
||||
}
|
||||
|
||||
// ShortName returns a displayable representation of the host name.
|
||||
// If the host is an IP address instead of a name, it is not shortened.
|
||||
func (tn *TabletNode) ShortName() string {
|
||||
if net.ParseIP(tn.Host) != nil {
|
||||
return netutil.JoinHostPort(tn.Host, tn.Port)
|
||||
}
|
||||
|
||||
hostPart := strings.SplitN(tn.Host, ".", 2)[0]
|
||||
if tn.Port == 0 {
|
||||
return hostPart
|
||||
}
|
||||
return netutil.JoinHostPort(hostPart, tn.Port)
|
||||
}
|
||||
|
||||
func newTabletNodeFromTabletInfo(ti *topo.TabletInfo) *TabletNode {
|
||||
if err := topo.TabletValidatePortMap(ti.Tablet); err != nil {
|
||||
log.Errorf("ValidatePortmap(%v): %v", ti.Alias, err)
|
||||
}
|
||||
return &TabletNode{
|
||||
Host: ti.Hostname,
|
||||
Port: ti.PortMap["vt"],
|
||||
Alias: ti.Alias,
|
||||
}
|
||||
}
|
||||
|
||||
func newTabletNodeFromEndPoint(ep *topodatapb.EndPoint, cell string) *TabletNode {
|
||||
return &TabletNode{
|
||||
Host: ep.Host,
|
||||
Alias: &topodatapb.TabletAlias{
|
||||
Uid: ep.Uid,
|
||||
Cell: cell,
|
||||
},
|
||||
Port: ep.PortMap[topo.DefaultPortName],
|
||||
}
|
||||
}
|
||||
|
||||
// TabletNodesByType maps tablet types to slices of tablet nodes.
|
||||
type TabletNodesByType struct {
|
||||
TabletType topodatapb.TabletType
|
||||
Nodes []*TabletNode
|
||||
}
|
||||
|
||||
// ShardNodes represents all tablet nodes for a shard, indexed by tablet type.
|
||||
type ShardNodes struct {
|
||||
Name string
|
||||
TabletNodes []*TabletNodesByType
|
||||
ServedTypes []topodatapb.TabletType
|
||||
Tag interface{} // Tag is an arbitrary value manageable by a plugin.
|
||||
}
|
||||
|
||||
type numericShardNodesList []*ShardNodes
|
||||
|
||||
// Len is part of sort.Interface
|
||||
func (nsnl numericShardNodesList) Len() int {
|
||||
return len(nsnl)
|
||||
}
|
||||
|
||||
// Less is part of sort.Interface
|
||||
func (nsnl numericShardNodesList) Less(i, j int) bool {
|
||||
// This panics, so it shouldn't be called unless all shard
|
||||
// names can be converted to integers.
|
||||
ii, err := strconv.Atoi(nsnl[i].Name)
|
||||
if err != nil {
|
||||
panic("bad numeric shard: " + nsnl[i].Name)
|
||||
}
|
||||
|
||||
jj, err := strconv.Atoi(nsnl[j].Name)
|
||||
if err != nil {
|
||||
panic("bad numeric shard" + nsnl[j].Name)
|
||||
}
|
||||
return ii < jj
|
||||
|
||||
}
|
||||
|
||||
// Swap is part of sort.Interface
|
||||
func (nsnl numericShardNodesList) Swap(i, j int) {
|
||||
nsnl[i], nsnl[j] = nsnl[j], nsnl[i]
|
||||
}
|
||||
|
||||
type rangeShardNodesList []*ShardNodes
|
||||
|
||||
// Len is part of sort.Interface
|
||||
func (rsnl rangeShardNodesList) Len() int {
|
||||
return len(rsnl)
|
||||
}
|
||||
|
||||
// Less is part of sort.Interface
|
||||
func (rsnl rangeShardNodesList) Less(i, j int) bool {
|
||||
return rsnl[i].Name < rsnl[j].Name
|
||||
}
|
||||
|
||||
// Swap is part of sort.Interface
|
||||
func (rsnl rangeShardNodesList) Swap(i, j int) {
|
||||
rsnl[i], rsnl[j] = rsnl[j], rsnl[i]
|
||||
}
|
||||
|
||||
// KeyspaceNodes represents all tablet nodes in a keyspace.
|
||||
type KeyspaceNodes struct {
|
||||
ShardNodes []*ShardNodes // sorted by shard name
|
||||
ServedFrom map[string]string
|
||||
}
|
||||
|
||||
func newKeyspaceNodes() *KeyspaceNodes {
|
||||
return &KeyspaceNodes{
|
||||
ServedFrom: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
func (ks *KeyspaceNodes) hasOnlyNumericShardNames() bool {
|
||||
for _, shardNodes := range ks.ShardNodes {
|
||||
if _, err := strconv.Atoi(shardNodes.Name); err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// TabletTypes returns a slice of tablet type names this ks
|
||||
// contains.
|
||||
func (ks KeyspaceNodes) TabletTypes() []topodatapb.TabletType {
|
||||
var contained []topodatapb.TabletType
|
||||
for _, t := range topoproto.AllTabletTypes {
|
||||
if ks.HasType(t) {
|
||||
contained = append(contained, t)
|
||||
}
|
||||
}
|
||||
return contained
|
||||
}
|
||||
|
||||
// HasType returns true if ks has any tablets with the named type.
|
||||
func (ks KeyspaceNodes) HasType(tabletType topodatapb.TabletType) bool {
|
||||
for _, shardNodes := range ks.ShardNodes {
|
||||
for _, tabletNodes := range shardNodes.TabletNodes {
|
||||
if tabletNodes.TabletType == tabletType {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Topology is the entire set of tablets in the topology.
|
||||
type Topology struct {
|
||||
Assigned map[string]*KeyspaceNodes // indexed by keyspace name
|
||||
Partial bool
|
||||
}
|
||||
|
||||
// DbTopology returns the Topology for the topo server.
|
||||
func DbTopology(ctx context.Context, ts topo.Server) (*Topology, error) {
|
||||
topology := &Topology{
|
||||
Assigned: make(map[string]*KeyspaceNodes),
|
||||
Partial: false,
|
||||
}
|
||||
|
||||
tabletInfos, err := GetAllTabletsAcrossCells(ctx, ts)
|
||||
switch err {
|
||||
case nil:
|
||||
// we're good, no error
|
||||
case topo.ErrPartialResult:
|
||||
// we got a partial result
|
||||
topology.Partial = true
|
||||
default:
|
||||
// we got no result at all
|
||||
return nil, err
|
||||
}
|
||||
|
||||
assigned := make(map[string]map[string][]*TabletNodesByType)
|
||||
for _, ti := range tabletInfos {
|
||||
tablet := newTabletNodeFromTabletInfo(ti)
|
||||
if _, ok := assigned[ti.Keyspace]; !ok {
|
||||
assigned[ti.Keyspace] = make(map[string][]*TabletNodesByType)
|
||||
}
|
||||
var tabletNode *TabletNodesByType
|
||||
for _, tabletNodes := range assigned[ti.Keyspace][ti.Shard] {
|
||||
if tabletNodes.TabletType == ti.Type {
|
||||
tabletNode = tabletNodes
|
||||
break
|
||||
}
|
||||
}
|
||||
if tabletNode == nil {
|
||||
tabletNode = &TabletNodesByType{
|
||||
TabletType: ti.Type,
|
||||
}
|
||||
assigned[ti.Keyspace][ti.Shard] = append(assigned[ti.Keyspace][ti.Shard], tabletNode)
|
||||
}
|
||||
tabletNode.Nodes = append(tabletNode.Nodes, tablet)
|
||||
}
|
||||
|
||||
for keyspace, shardMap := range assigned {
|
||||
kn := newKeyspaceNodes()
|
||||
for shard, nodes := range shardMap {
|
||||
kn.ShardNodes = append(kn.ShardNodes, &ShardNodes{
|
||||
Name: shard,
|
||||
TabletNodes: nodes,
|
||||
})
|
||||
}
|
||||
if kn.hasOnlyNumericShardNames() {
|
||||
sort.Sort(numericShardNodesList(kn.ShardNodes))
|
||||
} else {
|
||||
sort.Sort(rangeShardNodesList(kn.ShardNodes))
|
||||
}
|
||||
topology.Assigned[keyspace] = kn
|
||||
}
|
||||
return topology, nil
|
||||
}
|
||||
|
||||
// ServingGraph contains the representation of the serving graph
|
||||
// for a given cell.
|
||||
type ServingGraph struct {
|
||||
Cell string
|
||||
Keyspaces map[string]*KeyspaceNodes // indexed by keyspace name
|
||||
Errors []string // collected during creation
|
||||
}
|
||||
|
||||
// DbServingGraph returns the ServingGraph for the given cell.
|
||||
func DbServingGraph(ctx context.Context, ts topo.Server, cell string) (servingGraph *ServingGraph) {
|
||||
servingGraph = &ServingGraph{
|
||||
Cell: cell,
|
||||
Keyspaces: make(map[string]*KeyspaceNodes),
|
||||
}
|
||||
rec := concurrency.AllErrorRecorder{}
|
||||
|
||||
keyspaces, err := ts.GetSrvKeyspaceNames(ctx, cell)
|
||||
if err != nil {
|
||||
servingGraph.Errors = append(servingGraph.Errors, fmt.Sprintf("GetSrvKeyspaceNames failed: %v", err))
|
||||
return
|
||||
}
|
||||
wg := sync.WaitGroup{}
|
||||
servingTypes := []topodatapb.TabletType{topodatapb.TabletType_MASTER, topodatapb.TabletType_REPLICA, topodatapb.TabletType_RDONLY}
|
||||
for _, keyspace := range keyspaces {
|
||||
kn := newKeyspaceNodes()
|
||||
servingGraph.Keyspaces[keyspace] = kn
|
||||
wg.Add(1)
|
||||
go func(keyspace string, kn *KeyspaceNodes) {
|
||||
defer wg.Done()
|
||||
|
||||
ks, err := ts.GetSrvKeyspace(ctx, cell, keyspace)
|
||||
if err != nil {
|
||||
rec.RecordError(fmt.Errorf("GetSrvKeyspace(%v, %v) failed: %v", cell, keyspace, err))
|
||||
return
|
||||
}
|
||||
for _, sf := range ks.ServedFrom {
|
||||
kn.ServedFrom[strings.ToLower(sf.TabletType.String())] = sf.Keyspace
|
||||
}
|
||||
|
||||
displayedShards := make(map[string]bool)
|
||||
for _, partitionTabletType := range servingTypes {
|
||||
kp := topoproto.SrvKeyspaceGetPartition(ks, partitionTabletType)
|
||||
if kp == nil {
|
||||
continue
|
||||
}
|
||||
for _, srvShard := range kp.ShardReferences {
|
||||
shard := srvShard.Name
|
||||
if displayedShards[shard] {
|
||||
continue
|
||||
}
|
||||
displayedShards[shard] = true
|
||||
|
||||
sn := &ShardNodes{
|
||||
Name: shard,
|
||||
}
|
||||
kn.ShardNodes = append(kn.ShardNodes, sn)
|
||||
wg.Add(1)
|
||||
go func(shard string, sn *ShardNodes) {
|
||||
defer wg.Done()
|
||||
tabletTypes, err := ts.GetSrvTabletTypesPerShard(ctx, cell, keyspace, shard)
|
||||
if err != nil {
|
||||
rec.RecordError(fmt.Errorf("GetSrvTabletTypesPerShard(%v, %v, %v) failed: %v", cell, keyspace, shard, err))
|
||||
return
|
||||
}
|
||||
for _, tabletType := range tabletTypes {
|
||||
endPoints, _, err := ts.GetEndPoints(ctx, cell, keyspace, shard, tabletType)
|
||||
if err != nil {
|
||||
rec.RecordError(fmt.Errorf("GetEndPoints(%v, %v, %v, %v) failed: %v", cell, keyspace, shard, tabletType, err))
|
||||
continue
|
||||
}
|
||||
for _, endPoint := range endPoints.Entries {
|
||||
var tabletNode *TabletNodesByType
|
||||
for _, t := range sn.TabletNodes {
|
||||
if t.TabletType == tabletType {
|
||||
tabletNode = t
|
||||
break
|
||||
}
|
||||
}
|
||||
if tabletNode == nil {
|
||||
tabletNode = &TabletNodesByType{
|
||||
TabletType: tabletType,
|
||||
}
|
||||
sn.TabletNodes = append(sn.TabletNodes, tabletNode)
|
||||
}
|
||||
tabletNode.Nodes = append(tabletNode.Nodes, newTabletNodeFromEndPoint(endPoint, cell))
|
||||
}
|
||||
}
|
||||
}(shard, sn)
|
||||
}
|
||||
}
|
||||
}(keyspace, kn)
|
||||
}
|
||||
wg.Wait()
|
||||
servingGraph.Errors = rec.ErrorStrings()
|
||||
return
|
||||
}
|
|
@ -1,104 +0,0 @@
|
|||
// Copyright 2014, Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package topotools
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
topodatapb "github.com/youtube/vitess/go/vt/proto/topodata"
|
||||
"github.com/youtube/vitess/go/vt/topo/topoproto"
|
||||
)
|
||||
|
||||
func TestTabletNodeShortName(t *testing.T) {
|
||||
table := map[TabletNode]string{
|
||||
TabletNode{Host: "hostname", Port: 0}: "hostname",
|
||||
TabletNode{Host: "hostname", Port: 123}: "hostname:123",
|
||||
TabletNode{Host: "hostname.domain", Port: 456}: "hostname:456",
|
||||
TabletNode{Host: "12.34.56.78", Port: 555}: "12.34.56.78:555",
|
||||
TabletNode{Host: "::1", Port: 789}: "[::1]:789",
|
||||
}
|
||||
for input, want := range table {
|
||||
if got := input.ShortName(); got != want {
|
||||
t.Errorf("ShortName(%v:%v) = %q, want %q", input.Host, input.Port, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNumericShardNodesList(t *testing.T) {
|
||||
input := numericShardNodesList{
|
||||
&ShardNodes{Name: "3"},
|
||||
&ShardNodes{Name: "5"},
|
||||
&ShardNodes{Name: "4"},
|
||||
&ShardNodes{Name: "7"},
|
||||
&ShardNodes{Name: "10"},
|
||||
&ShardNodes{Name: "1"},
|
||||
&ShardNodes{Name: "0"},
|
||||
}
|
||||
want := numericShardNodesList{
|
||||
&ShardNodes{Name: "0"},
|
||||
&ShardNodes{Name: "1"},
|
||||
&ShardNodes{Name: "3"},
|
||||
&ShardNodes{Name: "4"},
|
||||
&ShardNodes{Name: "5"},
|
||||
&ShardNodes{Name: "7"},
|
||||
&ShardNodes{Name: "10"},
|
||||
}
|
||||
sort.Sort(input)
|
||||
if !reflect.DeepEqual(input, want) {
|
||||
t.Errorf("Sort(numericShardNodesList) failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRangeShardNodesList(t *testing.T) {
|
||||
input := rangeShardNodesList{
|
||||
&ShardNodes{Name: "50-60"},
|
||||
&ShardNodes{Name: "80-"},
|
||||
&ShardNodes{Name: "70-80"},
|
||||
&ShardNodes{Name: "30-40"},
|
||||
&ShardNodes{Name: "-10"},
|
||||
}
|
||||
want := rangeShardNodesList{
|
||||
&ShardNodes{Name: "-10"},
|
||||
&ShardNodes{Name: "30-40"},
|
||||
&ShardNodes{Name: "50-60"},
|
||||
&ShardNodes{Name: "70-80"},
|
||||
&ShardNodes{Name: "80-"},
|
||||
}
|
||||
sort.Sort(input)
|
||||
if !reflect.DeepEqual(input, want) {
|
||||
t.Errorf("Sort(rangeShardNodesList) failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyspaceNodesTabletTypes(t *testing.T) {
|
||||
input := KeyspaceNodes{
|
||||
ShardNodes: []*ShardNodes{
|
||||
&ShardNodes{
|
||||
TabletNodes: []*TabletNodesByType{
|
||||
&TabletNodesByType{
|
||||
TabletType: topodatapb.TabletType_REPLICA,
|
||||
},
|
||||
},
|
||||
},
|
||||
&ShardNodes{
|
||||
TabletNodes: []*TabletNodesByType{
|
||||
&TabletNodesByType{
|
||||
TabletType: topodatapb.TabletType_MASTER,
|
||||
},
|
||||
&TabletNodesByType{
|
||||
TabletType: topodatapb.TabletType_REPLICA,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
want := topoproto.MakeStringTypeList([]topodatapb.TabletType{topodatapb.TabletType_REPLICA, topodatapb.TabletType_MASTER})
|
||||
got := topoproto.MakeStringTypeList(input.TabletTypes())
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("KeyspaceNodes.TabletTypes() = %v, want %v", got, want)
|
||||
}
|
||||
}
|
|
@ -1035,13 +1035,6 @@ class Vtctld(object):
|
|||
if protocols_flavor().vtctl_client_protocol() == 'grpc':
|
||||
self.grpc_port = environment.reserve_ports(1)
|
||||
|
||||
def dbtopo(self):
|
||||
data = json.load(
|
||||
urllib2.urlopen('http://localhost:%d/dbtopo?format=json' % self.port))
|
||||
if data['Error']:
|
||||
raise VtctldError(data)
|
||||
return data['Topology']
|
||||
|
||||
def serving_graph(self):
|
||||
data = json.load(
|
||||
urllib2.urlopen(
|
||||
|
@ -1055,7 +1048,6 @@ class Vtctld(object):
|
|||
args = environment.binary_args('vtctld') + [
|
||||
'-debug',
|
||||
'-web_dir', environment.vttop + '/web/vtctld',
|
||||
'--templates', environment.vttop + '/go/cmd/vtctld/templates',
|
||||
'--log_dir', environment.vtlogroot,
|
||||
'--port', str(self.port),
|
||||
'--schema_change_dir', self.schema_change_dir,
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
#!/usr/bin/env python
|
||||
import logging
|
||||
import socket
|
||||
import unittest
|
||||
import urllib2
|
||||
import re
|
||||
|
||||
from vtproto import topodata_pb2
|
||||
|
||||
from vtctl import vtctl_client
|
||||
|
||||
import environment
|
||||
|
@ -108,10 +104,6 @@ class TestVtctld(unittest.TestCase):
|
|||
# run checks now before we start the tablets
|
||||
utils.validate_topology()
|
||||
|
||||
def setUp(self):
|
||||
self.data = utils.vtctld.dbtopo()
|
||||
self.serving_data = utils.vtctld.serving_graph()
|
||||
|
||||
def _check_all_tablets(self, result):
|
||||
lines = result.splitlines()
|
||||
self.assertEqual(len(lines), len(tablets))
|
||||
|
@ -142,85 +134,6 @@ class TestVtctld(unittest.TestCase):
|
|||
mode=utils.VTCTL_RPC)
|
||||
self._check_all_tablets(out)
|
||||
|
||||
def test_assigned(self):
|
||||
logging.debug('test_assigned: %s', str(self.data))
|
||||
self.assertItemsEqual(self.data['Assigned'].keys(), ['test_keyspace'])
|
||||
s0 = self.data['Assigned']['test_keyspace']['ShardNodes'][0]
|
||||
self.assertItemsEqual(s0['Name'], '-80')
|
||||
s1 = self.data['Assigned']['test_keyspace']['ShardNodes'][1]
|
||||
self.assertItemsEqual(s1['Name'], '80-')
|
||||
|
||||
def test_partial(self):
|
||||
utils.pause(
|
||||
'You can now run a browser and connect to http://%s:%d to '
|
||||
'manually check topology' % (socket.getfqdn(), utils.vtctld.port))
|
||||
self.assertEqual(self.data['Partial'], True)
|
||||
|
||||
def test_explorer_redirects(self):
|
||||
if environment.topo_server().flavor() != 'zookeeper':
|
||||
logging.info('Skipping zookeeper tests in topology %s',
|
||||
environment.topo_server().flavor())
|
||||
return
|
||||
|
||||
base = 'http://localhost:%d' % utils.vtctld.port
|
||||
self.assertEqual(
|
||||
urllib2.urlopen(
|
||||
base + '/explorers/redirect?'
|
||||
'type=keyspace&explorer=zk&keyspace=test_keyspace').geturl(),
|
||||
base + '/zk/global/vt/keyspaces/test_keyspace')
|
||||
self.assertEqual(
|
||||
urllib2.urlopen(
|
||||
base + '/explorers/redirect?type=shard&explorer=zk&'
|
||||
'keyspace=test_keyspace&shard=-80').geturl(),
|
||||
base + '/zk/global/vt/keyspaces/test_keyspace/shards/-80')
|
||||
self.assertEqual(
|
||||
urllib2.urlopen(
|
||||
base + '/explorers/redirect?type=tablet&explorer=zk&alias=%s' %
|
||||
shard_0_replica.tablet_alias).geturl(),
|
||||
base + shard_0_replica.zk_tablet_path)
|
||||
|
||||
self.assertEqual(
|
||||
urllib2.urlopen(
|
||||
base + '/explorers/redirect?type=srv_keyspace&explorer=zk&'
|
||||
'keyspace=test_keyspace&cell=test_nj').geturl(),
|
||||
base + '/zk/test_nj/vt/ns/test_keyspace')
|
||||
self.assertEqual(
|
||||
urllib2.urlopen(
|
||||
base + '/explorers/redirect?type=srv_shard&explorer=zk&'
|
||||
'keyspace=test_keyspace&shard=-80&cell=test_nj').geturl(),
|
||||
base + '/zk/test_nj/vt/ns/test_keyspace/-80')
|
||||
self.assertEqual(
|
||||
urllib2.urlopen(
|
||||
base + '/explorers/redirect?type=srv_type&explorer=zk&'
|
||||
'keyspace=test_keyspace&shard=-80&tablet_type=replica&'
|
||||
'cell=test_nj').geturl(),
|
||||
base + '/zk/test_nj/vt/ns/test_keyspace/-80/replica')
|
||||
|
||||
self.assertEqual(
|
||||
urllib2.urlopen(
|
||||
base + '/explorers/redirect?type=replication&explorer=zk&'
|
||||
'keyspace=test_keyspace&shard=-80&cell=test_nj').geturl(),
|
||||
base + '/zk/test_nj/vt/replication/test_keyspace/-80')
|
||||
|
||||
def test_serving_graph(self):
|
||||
self.assertItemsEqual(sorted(self.serving_data.keys()),
|
||||
['redirected_keyspace', 'test_keyspace'])
|
||||
s0 = self.serving_data['test_keyspace']['ShardNodes'][0]
|
||||
self.assertItemsEqual(s0['Name'], '-80')
|
||||
s1 = self.serving_data['test_keyspace']['ShardNodes'][1]
|
||||
self.assertItemsEqual(s1['Name'], '80-')
|
||||
types = []
|
||||
for tn in s0['TabletNodes']:
|
||||
tt = tn['TabletType']
|
||||
types.append(tt)
|
||||
if tt == topodata_pb2.MASTER:
|
||||
self.assertEqual(len(tn['Nodes']), 1)
|
||||
self.assertItemsEqual(sorted(types),
|
||||
sorted([topodata_pb2.MASTER, topodata_pb2.REPLICA]))
|
||||
self.assertEqual(
|
||||
self.serving_data['redirected_keyspace']['ServedFrom']['master'],
|
||||
'test_keyspace')
|
||||
|
||||
def test_tablet_status(self):
|
||||
# the vttablet that has a health check has a bit more, so using it
|
||||
shard_0_replica_status = shard_0_replica.get_status()
|
||||
|
|
Загрузка…
Ссылка в новой задаче