perkeep/dev/devcam/server.go

394 lines
11 KiB
Go

/*
Copyright 2013 The Camlistore Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// This program runs camlistored in "dev" mode,
// to facilitate hacking on camlistore.
package main
import (
"errors"
"flag"
"fmt"
"log"
"os"
"os/exec"
"os/signal"
"path/filepath"
"strconv"
"strings"
"syscall"
"camlistore.org/pkg/cmdmain"
"camlistore.org/pkg/osutil"
)
type serverCmd struct {
// start of flag vars
all bool
hostname string
port string
tls bool
wipe bool
debug bool
mongo bool
mysql bool
postgres bool
slow bool
throttle int
latency int
noBuild bool
fullClosure bool
// end of flag vars
camliSrcRoot string // the camlistore source tree
listen string // address + port to listen on
camliRoot string // the temp dir where blobs are stored
}
func init() {
cmdmain.RegisterCommand("server", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmd := new(serverCmd)
flags.BoolVar(&cmd.all, "all", false, "Listen on all interfaces.")
flags.StringVar(&cmd.hostname, "hostname", "", "Hostname to advertise, defaults to the hostname reported by the kernel.")
flags.StringVar(&cmd.port, "port", "3179", "Port to listen on.")
flags.BoolVar(&cmd.tls, "tls", false, "Use TLS.")
flags.BoolVar(&cmd.wipe, "wipe", false, "Wipe the blobs on disk and the indexer.")
flags.BoolVar(&cmd.debug, "debug", false, "Enable http debugging.")
flags.BoolVar(&cmd.mongo, "mongo", false, "Use mongodb as the indexer. Excludes -mysql and -postgres.")
flags.BoolVar(&cmd.mysql, "mysql", false, "Use mysql as the indexer. Excludes -mongo and -postgres.")
flags.BoolVar(&cmd.postgres, "postgres", false, "Use postgres as the indexer. Excludes -mongo and -mysql.")
flags.BoolVar(&cmd.slow, "slow", false, "Add artificial latency.")
flags.IntVar(&cmd.throttle, "throttle", 150, "If -slow, this is the rate in kBps, to which we should throttle.")
flags.IntVar(&cmd.latency, "latency", 90, "If -slow, this is the added latency, in ms.")
flags.BoolVar(&cmd.noBuild, "nobuild", false, "Do not rebuild anything.")
flags.BoolVar(&cmd.fullClosure, "fullclosure", false, "Use the ondisk closure library.")
return cmd
})
}
func (c *serverCmd) Usage() {
fmt.Fprintf(cmdmain.Stderr, "Usage: devcam [globalopts] server [serveropts]\n")
}
func (c *serverCmd) Examples() []string {
return []string{
"-wipe -mysql -fullclosure",
}
}
func (c *serverCmd) Describe() string {
return "Start the camlistored server."
}
func (c *serverCmd) checkFlags(args []string) error {
if len(args) != 0 {
c.Usage()
}
if (c.mongo && c.mysql) || (c.mongo && c.postgres) || (c.mysql && c.postgres) {
return fmt.Errorf("-mongo, -mysql, and -postgres are mutually exclusive.")
}
if _, err := strconv.ParseInt(c.port, 0, 0); err != nil {
return fmt.Errorf("Invalid -port value: %q", c.port)
}
return nil
}
func (c *serverCmd) build(name string) error {
var target string
switch name {
case "camlistored":
target = filepath.Join("camlistore.org", "server", "camlistored")
case "camtool":
target = filepath.Join("camlistore.org", "cmd", "camtool")
default:
return fmt.Errorf("Could not build, invalid target: %v", name)
}
binPath := filepath.Join(c.camliSrcRoot, "bin", name)
fi, err := os.Stat(binPath)
if err != nil {
return fmt.Errorf("Could not stat %v: %v", binPath, err)
}
args := []string{
"run", "make.go",
"--quiet",
"--embed_static=false",
"--sqlite=false",
fmt.Sprintf("--if_mods_since=%d", fi.ModTime().Unix()),
"--targets=" + target,
}
cmd := exec.Command("go", args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("Error building %v: %v", target, err)
}
return nil
}
func (c *serverCmd) setCamliRoot() error {
user := os.Getenv("USER")
if user == "" {
return errors.New("Could not get USER env var")
}
c.camliRoot = filepath.Join(os.TempDir(), "camliroot-"+user, "port"+c.port)
if c.wipe {
log.Printf("Wiping %v", c.camliRoot)
if err := os.RemoveAll(c.camliRoot); err != nil {
return fmt.Errorf("Could not wipe %v: %v", c.camliRoot, err)
}
}
return nil
}
func (c *serverCmd) makeSuffixdir(fullpath string) {
if err := os.MkdirAll(fullpath, 0755); err != nil {
log.Fatalf("Could not create %v: %v", fullpath, err)
}
}
func (c *serverCmd) setEnvVars() error {
if c.slow {
setenv("DEV_THROTTLE_KBPS", fmt.Sprintf("%d", c.throttle))
setenv("DEV_THROTTLE_LATENCY_MS", fmt.Sprintf("%d", c.latency))
}
if c.debug {
setenv("CAMLI_HTTP_DEBUG", "1")
}
user := os.Getenv("USER")
if user == "" {
return errors.New("Could not get USER env var")
}
setenv("CAMLI_DBNAME", "devcamli"+user)
setenv("CAMLI_MYSQL_ENABLED", "false")
setenv("CAMLI_MONGO_ENABLED", "false")
setenv("CAMLI_POSTGRES_ENABLED", "false")
switch {
case c.mongo:
setenv("CAMLI_MONGO_ENABLED", "true")
setenv("CAMLI_INDEXER_PATH", "/index-mongo/")
case c.postgres:
setenv("CAMLI_POSTGRES_ENABLED", "true")
setenv("CAMLI_INDEXER_PATH", "/index-postgres/")
case c.mysql:
setenv("CAMLI_MYSQL_ENABLED", "true")
setenv("CAMLI_INDEXER_PATH", "/index-mysql/")
default:
setenv("CAMLI_INDEXER_PATH", "/index-mem/")
}
base := "http://localhost:" + c.port
c.listen = "127.0.0.1:" + c.port
if c.all {
c.listen = "0.0.0.0:" + c.port
if c.hostname == "" {
hostname, err := os.Hostname()
if err != nil {
return fmt.Errorf("Could not get system hostname: %v", err)
}
base = "http://" + hostname + ":" + c.port
} else {
base = "http://" + c.hostname + ":" + c.port
}
}
setenv("CAMLI_TLS", "false")
if c.tls {
base = strings.Replace(base, "http://", "https://", 1)
setenv("CAMLI_TLS", "true")
}
setenv("CAMLI_BASEURL", base)
setenv("CAMLI_DEV_CAMLI_ROOT", c.camliSrcRoot)
setenv("CAMLI_AUTH", "userpass:camlistore:pass"+c.port+":+localhost")
setenv("CAMLI_ADVERTISED_PASSWORD", "pass"+c.port)
fullSuffix := func(name string) string {
return filepath.Join(c.camliRoot, name)
}
suffixes := map[string]string{
"CAMLI_ROOT": fullSuffix("bs"),
"CAMLI_ROOT_SHARD1": fullSuffix("s1"),
"CAMLI_ROOT_SHARD2": fullSuffix("s2"),
"CAMLI_ROOT_REPLICA1": fullSuffix("r1"),
"CAMLI_ROOT_REPLICA2": fullSuffix("r2"),
"CAMLI_ROOT_REPLICA3": fullSuffix("r3"),
"CAMLI_ROOT_CACHE": fullSuffix("cache"),
"CAMLI_ROOT_ENCMETA": fullSuffix("encmeta"),
"CAMLI_ROOT_ENCBLOB": fullSuffix("encblob"),
}
for k, v := range suffixes {
c.makeSuffixdir(v)
setenv(k, v)
}
setenv("CAMLI_PORT", c.port)
setenv("CAMLI_SECRET_RING", filepath.Join(c.camliSrcRoot,
filepath.FromSlash("pkg/jsonsign/testdata/test-secring.gpg")))
return nil
}
func (c *serverCmd) setupIndexer() error {
args := []string{"dbinit"}
switch {
case c.postgres:
args = append(args,
"-dbtype=postgres",
"-user=postgres",
"-password=postgres",
"-host=localhost",
"-dbname="+os.Getenv("CAMLI_DBNAME"))
case c.mysql:
args = append(args,
"-user=root",
"-password=root",
"-host=localhost",
"-dbname="+os.Getenv("CAMLI_DBNAME"))
default:
return nil
}
if c.wipe {
args = append(args, "-wipe")
} else {
args = append(args, "-ignoreexists")
}
binPath := filepath.Join(c.camliSrcRoot, "bin", "camtool")
cmd := exec.Command(binPath, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("Could not run camtool dbinit: %v", err)
}
return nil
}
func (c *serverCmd) syncTemplateBlobs() error {
if c.wipe {
templateDir := filepath.Join(c.camliSrcRoot, "dev-server-template")
if _, err := os.Stat(templateDir); err != nil {
if os.IsNotExist(err) {
return nil
}
return err
}
blobsDir := filepath.Join(c.camliRoot, "sha1")
if err := cpDir(templateDir, blobsDir); err != nil {
return fmt.Errorf("Could not cp template blobs: %v", err)
}
}
return nil
}
func (c *serverCmd) setFullClosure() error {
if c.fullClosure {
oldsvn := filepath.Join(c.camliRoot, filepath.FromSlash("tmp/closure-lib/.svn"))
if err := os.RemoveAll(oldsvn); err != nil {
return fmt.Errorf("Could not remove svn checkout of closure-lib %v: %v",
oldsvn, err)
}
log.Println("Updating closure library...")
args := []string{"run", "third_party/closure/updatelibrary.go", "-verbose"}
cmd := exec.Command("go", args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("Could not run updatelibrary.go: %v", err)
}
setenv("CAMLI_DEV_CLOSURE_DIR", "third_party/closure/lib/closure")
}
return nil
}
func handleKillCamliSignal(camliProc *os.Process) {
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGTERM, syscall.SIGINT)
for {
sig := <-c
sysSig, ok := sig.(syscall.Signal)
if !ok {
log.Fatal("Not a unix signal")
}
switch sysSig {
case syscall.SIGTERM, syscall.SIGINT:
log.Print("Received kill signal, terminating.")
err := camliProc.Kill()
if err != nil {
log.Fatalf("Failed to kill camli: %v ", err)
}
default:
log.Fatal("Received another signal, should not happen.")
}
}
}
func (c *serverCmd) RunCommand(args []string) error {
err := c.checkFlags(args)
if err != nil {
return cmdmain.UsageError(fmt.Sprint(err))
}
c.camliSrcRoot, err = osutil.GoPackagePath("camlistore.org")
if err != nil {
return errors.New("Package camlistore.org not found in $GOPATH (or $GOPATH not defined).")
}
err = os.Chdir(c.camliSrcRoot)
if err != nil {
return fmt.Errorf("Could not chdir to %v: %v", c.camliSrcRoot, err)
}
if !c.noBuild {
for _, name := range []string{"camlistored", "camtool"} {
err := c.build(name)
if err != nil {
return fmt.Errorf("Could not build %v: %v", name, err)
}
}
}
if err := c.setCamliRoot(); err != nil {
return fmt.Errorf("Could not setup the camli root: %v", err)
}
if err := c.setEnvVars(); err != nil {
return fmt.Errorf("Could not setup the env vars: %v", err)
}
if err := c.setupIndexer(); err != nil {
return fmt.Errorf("Could not setup the indexer: %v", err)
}
if err := c.syncTemplateBlobs(); err != nil {
return fmt.Errorf("Could not copy the template blobs: %v", err)
}
if err := c.setFullClosure(); err != nil {
return fmt.Errorf("Could not setup the closure lib: %v", err)
}
log.Printf("Starting dev server on %v/ui/ with password \"pass%v\"\n",
os.Getenv("CAMLI_BASEURL"), c.port)
camliBin := filepath.Join(c.camliSrcRoot, "bin", "camlistored")
cmdArgs := []string{
"-configfile=" + filepath.Join(c.camliSrcRoot, "config", "dev-server-config.json"),
"-listen=" + c.listen}
cmd := exec.Command(camliBin, cmdArgs...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Start(); err != nil {
return fmt.Errorf("Could not start camlistored: %v", err)
}
go handleKillCamliSignal(cmd.Process)
cmd.Wait()
return nil
}