This commit is contained in:
Anthony Yeh 2015-11-12 18:41:42 -08:00
Родитель de025e56bf
Коммит 993663574f
42 изменённых файлов: 102 добавлений и 3182 удалений

Просмотреть файл

@ -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 = "&nbsp;"
}
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>&nbsp;</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()