Esempio n. 1
0
func main() {
	defer func() {
		if panicErr := recover(); panicErr != nil {
			log.Fatalf("panic: %v", tb.Errorf("%v", panicErr))
		}
	}()

	flag.Parse()
	args := flag.Args()
	if len(args) != 0 {
		flag.Usage()
		os.Exit(1)
	}

	if *fromTopo == "" || *toTopo == "" {
		log.Fatalf("Need both from and to topo")
	}

	fromTS := topo.GetServerByName(*fromTopo)
	toTS := topo.GetServerByName(*toTopo)

	if *doKeyspaces {
		helpers.CopyKeyspaces(fromTS, toTS)
	}
	if *doShards {
		helpers.CopyShards(fromTS, toTS, *deleteKeyspaceShards)
	}
	if *doShardReplications {
		helpers.CopyShardReplications(fromTS, toTS)
	}
	if *doTablets {
		helpers.CopyTablets(fromTS, toTS)
	}
}
Esempio n. 2
0
func main() {
	defer exit.RecoverAll()
	defer logutil.Flush()

	flag.Parse()
	args := flag.Args()
	if len(args) != 0 {
		flag.Usage()
		os.Exit(1)
	}

	if *fromTopo == "" || *toTopo == "" {
		log.Errorf("Need both from and to topo")
		exit.Return(1)
	}

	ctx := context.Background()
	fromTS := topo.GetServerByName(*fromTopo)
	toTS := topo.GetServerByName(*toTopo)

	if *doKeyspaces {
		helpers.CopyKeyspaces(ctx, fromTS.Impl, toTS.Impl)
	}
	if *doShards {
		helpers.CopyShards(ctx, fromTS.Impl, toTS.Impl, *deleteKeyspaceShards)
	}
	if *doShardReplications {
		helpers.CopyShardReplications(ctx, fromTS.Impl, toTS.Impl)
	}
	if *doTablets {
		helpers.CopyTablets(ctx, fromTS.Impl, toTS.Impl)
	}
}
Esempio n. 3
0
func main() {
	defer exit.RecoverAll()
	defer logutil.Flush()

	flag.Parse()
	args := flag.Args()
	if len(args) != 0 {
		flag.Usage()
		os.Exit(1)
	}

	if *fromTopo == "" || *toTopo == "" {
		log.Fatalf("Need both from and to topo")
	}

	fromTS := topo.GetServerByName(*fromTopo)
	toTS := topo.GetServerByName(*toTopo)

	if *doKeyspaces {
		helpers.CopyKeyspaces(fromTS, toTS)
	}
	if *doShards {
		helpers.CopyShards(fromTS, toTS, *deleteKeyspaceShards)
	}
	if *doShardReplications {
		helpers.CopyShardReplications(fromTS, toTS)
	}
	if *doTablets {
		helpers.CopyTablets(fromTS, toTS)
	}
}
Esempio n. 4
0
func init() {
	// handles /zk paths
	ts := topo.GetServerByName("zookeeper")
	if ts == nil {
		log.Error("zookeeper explorer disabled: no zktopo.Server")
		return
	}

	HandleExplorer("zk", "/zk/", "zk.html", NewZkExplorer(ts.(*zktopo.Server).GetZConn()))
}
Esempio n. 5
0
func main() {
	defer exit.Recover()

	// flag parsing
	flags := dbconfigs.AppConfig | dbconfigs.DbaConfig |
		dbconfigs.FilteredConfig | dbconfigs.ReplConfig
	dbconfigs.RegisterFlags(flags)
	mysqlctl.RegisterFlags()
	flag.Parse()
	if len(flag.Args()) > 0 {
		flag.Usage()
		log.Errorf("vtcombo doesn't take any positional arguments")
		exit.Return(1)
	}

	// register topo server
	topo.RegisterServer("fakezk", zktopo.NewServer(fakezk.NewConn()))
	ts := topo.GetServerByName("fakezk")

	servenv.Init()

	// database configs
	mycnf, err := mysqlctl.NewMycnfFromFlags(0)
	if err != nil {
		log.Errorf("mycnf read failed: %v", err)
		exit.Return(1)
	}
	dbcfgs, err := dbconfigs.Init(mycnf.SocketFile, flags)
	if err != nil {
		log.Warning(err)
	}
	mysqld := mysqlctl.NewMysqld("Dba", "App", mycnf, &dbcfgs.Dba, &dbcfgs.App.ConnParams, &dbcfgs.Repl)

	// tablets configuration and init
	binlog.RegisterUpdateStreamService(mycnf)
	initTabletMap(ts, *topology, mysqld, dbcfgs, mycnf)

	// vtgate configuration and init
	resilientSrvTopoServer := vtgate.NewResilientSrvTopoServer(ts, "ResilientSrvTopoServer")
	healthCheck := discovery.NewHealthCheck(30*time.Second /*connTimeoutTotal*/, 1*time.Millisecond /*retryDelay*/)
	vtgate.Init(healthCheck, ts, resilientSrvTopoServer, nil /*schema*/, cell, 1*time.Millisecond /*retryDelay*/, 2 /*retryCount*/, 30*time.Second /*connTimeoutTotal*/, 10*time.Second /*connTimeoutPerConn*/, 365*24*time.Hour /*connLife*/, 0 /*maxInFlight*/, "" /*testGateway*/)

	servenv.OnTerm(func() {
		// FIXME(alainjobart) stop vtgate, all tablets
		//		qsc.DisallowQueries()
		//		agent.Stop()
	})
	servenv.OnClose(func() {
		// We will still use the topo server during lameduck period
		// to update our state, so closing it in OnClose()
		topo.CloseServers()
	})
	servenv.RunDefault()
}
Esempio n. 6
0
func main() {
	defer func() {
		if panicErr := recover(); panicErr != nil {
			relog.Fatal("panic: %v", tb.Errorf("%v", panicErr))
		}
	}()

	flag.Parse()
	args := flag.Args()
	if len(args) != 0 {
		flag.Usage()
		os.Exit(1)
	}

	logLevel, err := relog.LogNameToLogLevel(*logLevel)
	if err != nil {
		relog.Fatal("%v", err)
	}
	relog.SetLevel(logLevel)

	if *fromTopo == "" || *toTopo == "" {
		relog.Fatal("Need both from and to topo")
	}

	fromTS := topo.GetServerByName(*fromTopo)
	toTS := topo.GetServerByName(*toTopo)

	if *doKeyspaces {
		topotools.CopyKeyspaces(fromTS, toTS)
	}
	if *doShards {
		topotools.CopyShards(fromTS, toTS, *deleteKeyspaceShards)
	}
	if *doTablets {
		topotools.CopyTablets(fromTS, toTS)
	}
}
Esempio n. 7
0
func init() {
	// handles /zk paths
	ts := topo.GetServerByName("zookeeper")
	if ts == nil {
		log.Error("zookeeper explorer disabled: no zktopo.Server")
		return
	}

	HandleExplorer("/zk/", "zk.html", NewZkExplorer(ts.(*zktopo.Server).GetZConn()))

	// adds links for keyspaces and shards
	funcMap["keyspace"] = func(keyspace string) template.HTML {
		return template.HTML("<a href=\"/zk/global/vt/keyspaces/" + keyspace + "\">" + keyspace + "</a>")
	}
	funcMap["shard"] = func(keyspace, shard string) template.HTML {
		return template.HTML("<a href=\"/zk/global/vt/keyspaces/" + keyspace + "/shards/" + shard + "\">" + shard + "</a>")
	}

	// add toplevel link for zookeeper
	indexContent.ToplevelLinks["Zookeeper Explorer"] = "/zk"
}
Esempio n. 8
0
func main() {
	defer exit.Recover()

	// flag parsing
	flags := dbconfigs.AppConfig | dbconfigs.DbaConfig |
		dbconfigs.FilteredConfig | dbconfigs.ReplConfig
	dbconfigs.RegisterFlags(flags)
	mysqlctl.RegisterFlags()
	flag.Parse()
	if len(flag.Args()) > 0 {
		flag.Usage()
		log.Errorf("vtcombo doesn't take any positional arguments")
		exit.Return(1)
	}

	// register topo server
	zkconn := fakezk.NewConn()
	topo.RegisterServer("fakezk", zktopo.NewServer(zkconn))
	ts = topo.GetServerByName("fakezk")

	servenv.Init()
	tabletserver.Init()

	// database configs
	mycnf, err := mysqlctl.NewMycnfFromFlags(0)
	if err != nil {
		log.Errorf("mycnf read failed: %v", err)
		exit.Return(1)
	}
	dbcfgs, err := dbconfigs.Init(mycnf.SocketFile, flags)
	if err != nil {
		log.Warning(err)
	}
	mysqld := mysqlctl.NewMysqld("Dba", "App", mycnf, &dbcfgs.Dba, &dbcfgs.App.ConnParams, &dbcfgs.Repl)
	servenv.OnClose(mysqld.Close)

	// tablets configuration and init
	initTabletMap(ts, *topology, mysqld, dbcfgs, mycnf)

	// vschema
	var schema *planbuilder.Schema
	if *vschema != "" {
		schema, err = planbuilder.LoadFile(*vschema)
		if err != nil {
			log.Error(err)
			exit.Return(1)
		}
		log.Infof("v3 is enabled: loaded schema from file")
	}

	// vtgate configuration and init
	resilientSrvTopoServer := vtgate.NewResilientSrvTopoServer(ts, "ResilientSrvTopoServer")
	healthCheck := discovery.NewHealthCheck(30*time.Second /*connTimeoutTotal*/, 1*time.Millisecond /*retryDelay*/, 1*time.Minute /*healthCheckTimeout*/)
	tabletTypesToWait := []topodatapb.TabletType{
		topodatapb.TabletType_MASTER,
		topodatapb.TabletType_REPLICA,
		topodatapb.TabletType_RDONLY,
	}
	vtgate.Init(healthCheck, ts, resilientSrvTopoServer, schema, cell, 1*time.Millisecond /*retryDelay*/, 2 /*retryCount*/, 30*time.Second /*connTimeoutTotal*/, 10*time.Second /*connTimeoutPerConn*/, 365*24*time.Hour /*connLife*/, tabletTypesToWait, 0 /*maxInFlight*/, "" /*testGateway*/)

	// vtctld configuration and init
	vtctld.InitVtctld(ts)
	vtctld.HandleExplorer("zk", zktopo.NewZkExplorer(zkconn))

	servenv.OnTerm(func() {
		// FIXME(alainjobart): stop vtgate
	})
	servenv.OnClose(func() {
		log.Infof("Total count of new connections to MySQL: %v", expvar.Get("mysql-new-connection-count"))
		// We will still use the topo server during lameduck period
		// to update our state, so closing it in OnClose()
		topo.CloseServers()
	})
	servenv.RunDefault()
}
Esempio n. 9
0
func init() {
	// handles /zk paths
	http.HandleFunc("/zk/", func(w http.ResponseWriter, r *http.Request) {
		zkTopoServ := topo.GetServerByName("zookeeper")
		if zkTopoServ == nil {
			http.Error(w, "can only look at zk with zktopo.Server", http.StatusInternalServerError)
			return
		}
		zconn := zkTopoServ.(*zktopo.Server).GetZConn()

		if err := r.ParseForm(); err != nil {
			httpError(w, "cannot parse form: %s", err)
			return
		}
		zkPath := r.URL.Path[strings.Index(r.URL.Path, "/zk"):]

		if cleanPath := path.Clean(zkPath); zkPath != cleanPath && zkPath != cleanPath+"/" {
			log.Infof("redirecting to %v", cleanPath)
			http.Redirect(w, r, cleanPath, http.StatusTemporaryRedirect)
			return
		}

		if strings.HasSuffix(zkPath, "/") {
			zkPath = zkPath[:len(zkPath)-1]
		}

		result := NewZkResult(zkPath)

		if zkPath == "/zk" {
			cells, err := zk.ResolveWildcards(zconn, []string{"/zk/*"})
			if err != nil {
				httpError(w, "zk error: %v", err)
				return
			}
			for i, cell := range cells {
				cells[i] = cell[4:] // cut off "/zk/"
			}
			result.Children = cells
			sort.Strings(result.Children)
		} else {

			if data, _, err := zconn.Get(zkPath); err != nil {
				result.Error = err.Error()
			} else {

				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)
				}
				result.Data = data
				if children, _, err := zconn.Children(zkPath); err != nil {
					result.Error = err.Error()
				} else {
					result.Children = children
					sort.Strings(result.Children)
				}
			}
		}
		templateLoader.ServeTemplate("zk.html", result, w, r)
	})

	// adds links for keyspaces and shards
	funcMap["keyspace"] = func(keyspace string) template.HTML {
		return template.HTML("<a href=\"/zk/global/vt/keyspaces/" + keyspace + "\">" + keyspace + "</a>")
	}
	funcMap["shard"] = func(keyspace, shard string) template.HTML {
		return template.HTML("<a href=\"/zk/global/vt/keyspaces/" + keyspace + "/shards/" + shard + "\">" + shard + "</a>")
	}

	// add toplevel link for zookeeper
	indexContent.ToplevelLinks["Zookeeper Explorer"] = "/zk"
}
Esempio n. 10
0
func main() {
	defer exit.Recover()

	// flag parsing
	flags := dbconfigs.AppConfig | dbconfigs.DbaConfig |
		dbconfigs.FilteredConfig | dbconfigs.ReplConfig
	dbconfigs.RegisterFlags(flags)
	mysqlctl.RegisterFlags()
	flag.Parse()
	if len(flag.Args()) > 0 {
		flag.Usage()
		log.Errorf("vtcombo doesn't take any positional arguments")
		exit.Return(1)
	}

	// set discoverygateway flag to default value
	flag.Set("cells_to_watch", cell)

	// register topo server
	zkconn := fakezk.NewConn()
	topo.RegisterServer("fakezk", zktopo.NewServer(zkconn))
	ts = topo.GetServerByName("fakezk")

	servenv.Init()
	tabletserver.Init()

	// database configs
	mycnf, err := mysqlctl.NewMycnfFromFlags(0)
	if err != nil {
		log.Errorf("mycnf read failed: %v", err)
		exit.Return(1)
	}
	dbcfgs, err := dbconfigs.Init(mycnf.SocketFile, flags)
	if err != nil {
		log.Warning(err)
	}
	mysqld := mysqlctl.NewMysqld("Dba", "App", mycnf, &dbcfgs.Dba, &dbcfgs.App.ConnParams, &dbcfgs.Repl)
	servenv.OnClose(mysqld.Close)

	// tablets configuration and init
	if err := initTabletMap(ts, *protoTopo, mysqld, dbcfgs, *schemaDir, mycnf); err != nil {
		log.Errorf("initTabletMapProto failed: %v", err)
		exit.Return(1)
	}

	// vtgate configuration and init
	resilientSrvTopoServer := vtgate.NewResilientSrvTopoServer(ts, "ResilientSrvTopoServer")
	healthCheck := discovery.NewHealthCheck(30*time.Second /*connTimeoutTotal*/, 1*time.Millisecond /*retryDelay*/, 1*time.Hour /*healthCheckTimeout*/)
	tabletTypesToWait := []topodatapb.TabletType{
		topodatapb.TabletType_MASTER,
		topodatapb.TabletType_REPLICA,
		topodatapb.TabletType_RDONLY,
	}
	vtgate.Init(context.Background(), healthCheck, ts, resilientSrvTopoServer, cell, 2 /*retryCount*/, tabletTypesToWait)

	// vtctld configuration and init
	vtctld.InitVtctld(ts)
	vtctld.HandleExplorer("zk", zktopo.NewZkExplorer(zkconn))

	servenv.OnTerm(func() {
		// FIXME(alainjobart): stop vtgate
	})
	servenv.OnClose(func() {
		// We will still use the topo server during lameduck period
		// to update our state, so closing it in OnClose()
		topo.CloseServers()
	})
	servenv.RunDefault()
}
Esempio n. 11
0
func main() {
	defer exit.Recover()

	// flag parsing
	flags := dbconfigs.AppConfig | dbconfigs.AllPrivsConfig | dbconfigs.DbaConfig |
		dbconfigs.FilteredConfig | dbconfigs.ReplConfig
	dbconfigs.RegisterFlags(flags)
	mysqlctl.RegisterFlags()
	flag.Parse()
	if len(flag.Args()) > 0 {
		flag.Usage()
		log.Errorf("vtcombo doesn't take any positional arguments")
		exit.Return(1)
	}

	// parse the input topology
	tpb := &vttestpb.VTTestTopology{}
	if err := proto.UnmarshalText(*protoTopo, tpb); err != nil {
		log.Errorf("cannot parse topology: %v", err)
		exit.Return(1)
	}

	// default cell to "test" if unspecified
	if len(tpb.Cells) == 0 {
		tpb.Cells = append(tpb.Cells, "test")
	}

	// set discoverygateway flag to default value
	flag.Set("cells_to_watch", strings.Join(tpb.Cells, ","))

	// vtctld UI requires the cell flag
	flag.Set("cell", tpb.Cells[0])
	flag.Set("enable_realtime_stats", "true")
	flag.Set("log_dir", "$VTDATAROOT/tmp")

	// create zk client config file
	config := path.Join(os.Getenv("VTDATAROOT"), "vt_0000000001/tmp/test-zk-client-conf.json")
	cellmap := make(map[string]string)
	for _, cell := range tpb.Cells {
		cellmap[cell] = "localhost"
	}
	b, err := json.Marshal(cellmap)
	if err != nil {
		log.Errorf("failed to marshal json: %v", err)
	}

	f, err := os.Create(config)
	if err != nil {
		log.Errorf("failed to create zk config file: %v", err)
	}
	defer f.Close()
	_, err = f.WriteString(string(b[:]))
	if err != nil {
		log.Errorf("failed to write to zk config file: %v", err)
	}
	os.Setenv("ZK_CLIENT_CONFIG", config)

	// register topo server
	zkconn := fakezk.NewConn()
	topo.RegisterServer("fakezk", zktopo.NewServer(zkconn))
	ts = topo.GetServerByName("fakezk")

	servenv.Init()
	tabletserver.Init()

	// database configs
	mycnf, err := mysqlctl.NewMycnfFromFlags(0)
	if err != nil {
		log.Errorf("mycnf read failed: %v", err)
		exit.Return(1)
	}
	dbcfgs, err := dbconfigs.Init(mycnf.SocketFile, flags)
	if err != nil {
		log.Warning(err)
	}
	mysqld := mysqlctl.NewMysqld(mycnf, &dbcfgs.Dba, &dbcfgs.AllPrivs, &dbcfgs.App, &dbcfgs.Repl, true /* enablePublishStats */)
	servenv.OnClose(mysqld.Close)

	// tablets configuration and init
	if err := initTabletMap(ts, tpb, mysqld, dbcfgs, *schemaDir, mycnf); err != nil {
		log.Errorf("initTabletMapProto failed: %v", err)
		exit.Return(1)
	}

	// vtgate configuration and init
	resilientSrvTopoServer := vtgate.NewResilientSrvTopoServer(ts, "ResilientSrvTopoServer")
	healthCheck := discovery.NewHealthCheck(30*time.Second /*connTimeoutTotal*/, 1*time.Millisecond /*retryDelay*/, 1*time.Hour /*healthCheckTimeout*/)
	tabletTypesToWait := []topodatapb.TabletType{
		topodatapb.TabletType_MASTER,
		topodatapb.TabletType_REPLICA,
		topodatapb.TabletType_RDONLY,
	}
	vtgate.Init(context.Background(), healthCheck, ts, resilientSrvTopoServer, tpb.Cells[0], 2 /*retryCount*/, tabletTypesToWait)

	// vtctld configuration and init
	vtctld.InitVtctld(ts)
	vtctld.HandleExplorer("zk", zktopo.NewZkExplorer(zkconn))

	servenv.OnTerm(func() {
		// FIXME(alainjobart): stop vtgate
	})
	servenv.OnClose(func() {
		// We will still use the topo server during lameduck period
		// to update our state, so closing it in OnClose()
		topo.CloseServers()
	})
	servenv.RunDefault()
}
Esempio n. 12
0
func main() {
	flag.Parse()

	templateLoader := NewTemplateLoader(*templateDir, dummyTemplate, *debug)

	ts := topo.GetServer()
	defer topo.CloseServers()

	wr := wrangler.New(ts, 30*time.Second, 30*time.Second)

	actionRepo := NewActionRepository(wr)

	const (
		keyspacePath = "/zk/global/vt/keyspaces/*"
		shardPath    = "/zk/global/vt/keyspaces/*/shards/*"
		tabletPath   = "/zk/*/vt/tablets/*"
	)

	actionRepo.Register(keyspacePath, "ValidateKeyspace",
		func(wr *wrangler.Wrangler, zkPath string, r *http.Request) (string, error) {
			return "", wr.ValidateKeyspace(path.Base(zkPath), false)
		})

	actionRepo.Register(keyspacePath, "ValidateSchemaKeyspace",
		func(wr *wrangler.Wrangler, zkPath string, r *http.Request) (string, error) {
			return "", wr.ValidateSchemaKeyspace(path.Base(zkPath), false)
		})

	actionRepo.Register(keyspacePath, "ValidateVersionKeyspace",
		func(wr *wrangler.Wrangler, zkPath string, r *http.Request) (string, error) {
			return "", wr.ValidateVersionKeyspace(path.Base(zkPath))
		})

	actionRepo.Register(keyspacePath, "ValidatePermissionsKeyspace",
		func(wr *wrangler.Wrangler, zkPath string, r *http.Request) (string, error) {
			return "", wr.ValidatePermissionsKeyspace(path.Base(zkPath))
		})

	actionRepo.Register(shardPath, "ValidateShard",
		func(wr *wrangler.Wrangler, zkPath string, r *http.Request) (string, error) {
			zkPathParts := strings.Split(zkPath, "/")
			return "", wr.ValidateShard(zkPathParts[5], zkPathParts[7], false)
		})

	actionRepo.Register(shardPath, "ValidateSchemaShard",
		func(wr *wrangler.Wrangler, zkPath string, r *http.Request) (string, error) {
			zkPathParts := strings.Split(zkPath, "/")
			return "", wr.ValidateSchemaShard(zkPathParts[5], zkPathParts[7], false)
		})

	actionRepo.Register(shardPath, "ValidateVersionShard",
		func(wr *wrangler.Wrangler, zkPath string, r *http.Request) (string, error) {
			zkPathParts := strings.Split(zkPath, "/")
			return "", wr.ValidateVersionShard(zkPathParts[5], zkPathParts[7])
		})

	actionRepo.Register(shardPath, "ValidatePermissionsShard",
		func(wr *wrangler.Wrangler, zkPath string, r *http.Request) (string, error) {
			zkPathParts := strings.Split(zkPath, "/")
			return "", wr.ValidatePermissionsShard(zkPathParts[5], zkPathParts[7])
		})

	actionRepo.Register(tabletPath, "RpcPing",
		func(wr *wrangler.Wrangler, zkPath string, r *http.Request) (string, error) {
			zkPathParts := strings.Split(zkPath, "/")
			alias, err := topo.ParseTabletAliasString(zkPathParts[2] + "-" + zkPathParts[5])
			if err != nil {
				return "", err
			}
			return "", wr.ActionInitiator().RpcPing(alias, 10*time.Second)
		})

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		tmpl, err := templateLoader.Lookup("index.html")
		if err != nil {
			httpError(w, "error in template loader: %v", err)
			return
		}
		if err := tmpl.Execute(w, nil); err != nil {
			httpError(w, "error executing template", err)
		}

	})
	http.HandleFunc("/actions", func(w http.ResponseWriter, r *http.Request) {
		if err := r.ParseForm(); err != nil {
			httpError(w, "cannot parse form: %s", err)
			return
		}
		action := r.FormValue("action")
		if action == "" {
			http.Error(w, "no action provided", http.StatusBadRequest)
			return
		}

		zkPath := r.FormValue("zkpath")
		if zkPath == "" {
			http.Error(w, "no zookeeper path provided", http.StatusBadRequest)
			return
		}
		result := actionRepo.Apply(action, zkPath, r)

		templateLoader.ServeTemplate("action.html", result, w, r)
	})
	http.HandleFunc("/dbtopo", func(w http.ResponseWriter, r *http.Request) {
		if err := r.ParseForm(); err != nil {
			httpError(w, "cannot parse form: %s", err)
			return
		}
		result := DbTopologyResult{}
		topology, err := wr.DbTopology()
		if err != nil {
			result.Error = err.Error()
		} else {
			result.Topology = topology
		}
		templateLoader.ServeTemplate("dbtopo.html", result, w, r)
	})
	http.HandleFunc("/zk/", func(w http.ResponseWriter, r *http.Request) {
		zkTopoServ := topo.GetServerByName("zookeeper")
		if zkTopoServ == nil {
			http.Error(w, "can only look at zk with zktopo.Server", http.StatusInternalServerError)
			return
		}
		zconn := zkTopoServ.(*zktopo.Server).GetZConn()

		if err := r.ParseForm(); err != nil {
			httpError(w, "cannot parse form: %s", err)
			return
		}
		zkPath := r.URL.Path[strings.Index(r.URL.Path, "/zk"):]

		if cleanPath := path.Clean(zkPath); zkPath != cleanPath && zkPath != cleanPath+"/" {
			relog.Info("redirecting to %v", cleanPath)
			http.Redirect(w, r, cleanPath, http.StatusTemporaryRedirect)
			return
		}

		if strings.HasSuffix(zkPath, "/") {
			zkPath = zkPath[:len(zkPath)-1]
		}

		result := NewZkResult(zkPath)

		if zkPath == "/zk" {
			cells, err := zk.ResolveWildcards(zconn, []string{"/zk/*"})
			if err != nil {
				httpError(w, "zk error: %v", err)
				return
			}
			for i, cell := range cells {
				cells[i] = cell[4:] // cut off "/zk/"
			}
			result.Children = cells
		} else {

			if data, _, err := zconn.Get(zkPath); err != nil {
				result.Error = err.Error()
			} else {
				actionRepo.PopulateAvailableActions(result)
				result.Data = data
				if children, _, err := zconn.Children(zkPath); err != nil {
					result.Error = err.Error()
				} else {
					result.Children = children
				}
			}
		}
		templateLoader.ServeTemplate("zk.html", result, w, r)
	})
	relog.Fatal("%s", http.ListenAndServe(fmt.Sprintf(":%d", *port), nil))
}