2011-03-30 00:42:49 +00:00
|
|
|
/*
|
|
|
|
Copyright 2011 Google Inc.
|
|
|
|
|
|
|
|
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.
|
|
|
|
*/
|
2010-06-12 21:45:58 +00:00
|
|
|
|
|
|
|
package main
|
|
|
|
|
2010-07-11 04:18:16 +00:00
|
|
|
import (
|
2010-11-29 04:06:22 +00:00
|
|
|
"camli/auth"
|
2011-03-16 06:16:24 +00:00
|
|
|
"camli/blobref"
|
|
|
|
"camli/client"
|
2010-12-06 06:34:46 +00:00
|
|
|
"camli/httputil"
|
2010-12-06 06:29:11 +00:00
|
|
|
"camli/webserver"
|
2011-02-03 23:45:35 +00:00
|
|
|
"camli/blobserver"
|
2011-02-04 22:31:23 +00:00
|
|
|
"camli/blobserver/localdisk"
|
2011-04-02 03:45:40 +00:00
|
|
|
_ "camli/blobserver/s3"
|
2011-02-03 23:56:02 +00:00
|
|
|
"camli/blobserver/handlers"
|
2011-03-05 22:25:08 +00:00
|
|
|
"camli/mysqlindexer" // TODO: temporary for testing; wrong place kinda
|
2011-03-13 23:38:32 +00:00
|
|
|
"camli/search" // TODO: temporary for testing; wrong place kinda
|
2010-07-11 04:18:16 +00:00
|
|
|
"flag"
|
|
|
|
"fmt"
|
|
|
|
"http"
|
2011-01-26 05:33:19 +00:00
|
|
|
"log"
|
2011-02-03 06:42:31 +00:00
|
|
|
"strings"
|
2010-07-11 04:18:16 +00:00
|
|
|
"os"
|
|
|
|
)
|
2010-07-07 04:57:53 +00:00
|
|
|
|
2011-04-01 19:48:43 +00:00
|
|
|
var flagUseConfigFiles = flag.Bool("useconfigfiles", false,
|
|
|
|
"Use the ~/.camli/config files and enable the /config HTTP handler." +
|
|
|
|
"+If false, all configuration is done ")
|
|
|
|
var flagPasswordFile = flag.String("passwordfile", "password.txt",
|
|
|
|
"Password file, relative to the ~USER/.camli/ directory.")
|
2011-03-05 23:09:36 +00:00
|
|
|
|
2011-04-01 19:48:43 +00:00
|
|
|
// If useConfigFiles is off:
|
|
|
|
var flagStorageRoot = flag.String("root", "/tmp/camliroot", "Root directory to store files")
|
2011-03-07 04:11:36 +00:00
|
|
|
var flagQueuePartitions = flag.String("queue-partitions", "queue-indexer",
|
|
|
|
"Comma-separated list of queue partitions to reference uploaded blobs into. "+
|
|
|
|
"Typically one for your indexer and one per mirror full syncer.")
|
2011-03-05 23:09:36 +00:00
|
|
|
// TODO: Temporary
|
2011-04-01 19:48:43 +00:00
|
|
|
var flagRequestLog = flag.Bool("reqlog", false, "Log incoming requests")
|
2011-03-05 23:09:36 +00:00
|
|
|
var flagDevMySql = flag.Bool("devmysqlindexer", false, "Temporary option to enable MySQL indexer on /indexer")
|
2011-03-13 23:38:32 +00:00
|
|
|
var flagDevSearch = flag.Bool("devsearch", false, "Temporary option to enable search interface at /camli/search")
|
2011-03-19 01:00:17 +00:00
|
|
|
var flagDatabaseName = flag.String("dbname", "devcamlistore", "MySQL database name")
|
2010-06-12 21:45:58 +00:00
|
|
|
|
2011-04-01 19:48:43 +00:00
|
|
|
|
2011-02-03 23:45:35 +00:00
|
|
|
var storage blobserver.Storage
|
2011-02-03 06:42:31 +00:00
|
|
|
|
|
|
|
const camliPrefix = "/camli/"
|
|
|
|
const partitionPrefix = "/partition-"
|
|
|
|
|
|
|
|
var InvalidCamliPath = os.NewError("Invalid Camlistore request path")
|
|
|
|
|
2011-03-07 04:11:36 +00:00
|
|
|
type partitionConfig struct {
|
|
|
|
name string
|
|
|
|
writable, readable, queue bool
|
|
|
|
mirrors []blobserver.Partition
|
|
|
|
urlbase string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *partitionConfig) Name() string { return p.name }
|
|
|
|
func (p *partitionConfig) Writable() bool { return p.writable }
|
|
|
|
func (p *partitionConfig) Readable() bool { return p.readable }
|
|
|
|
func (p *partitionConfig) IsQueue() bool { return p.queue }
|
|
|
|
func (p *partitionConfig) URLBase() string { return p.urlbase }
|
|
|
|
func (p *partitionConfig) GetMirrorPartitions() []blobserver.Partition { return p.mirrors }
|
|
|
|
|
|
|
|
var _ blobserver.Partition = &partitionConfig{}
|
|
|
|
var mainPartition = &partitionConfig{"", true, true, false, nil, "http://localhost"}
|
|
|
|
|
|
|
|
func parseCamliPath(path string) (partitionName string, action string, err os.Error) {
|
2011-02-03 06:42:31 +00:00
|
|
|
camIdx := strings.Index(path, camliPrefix)
|
|
|
|
if camIdx == -1 {
|
|
|
|
err = InvalidCamliPath
|
|
|
|
return
|
|
|
|
}
|
|
|
|
action = path[camIdx+len(camliPrefix):]
|
|
|
|
if camIdx == 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if !strings.HasPrefix(path, partitionPrefix) {
|
|
|
|
err = InvalidCamliPath
|
|
|
|
return
|
|
|
|
}
|
2011-03-07 04:11:36 +00:00
|
|
|
partitionName = path[len(partitionPrefix):camIdx]
|
|
|
|
if !isValidPartitionName(partitionName) {
|
2011-02-03 06:42:31 +00:00
|
|
|
err = InvalidCamliPath
|
2011-03-16 06:16:24 +00:00
|
|
|
return
|
2011-02-03 06:42:31 +00:00
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func pickPartitionHandlerMaybe(req *http.Request) (handler http.HandlerFunc, intercept bool) {
|
|
|
|
if !strings.HasPrefix(req.URL.Path, partitionPrefix) {
|
|
|
|
intercept = false
|
|
|
|
return
|
|
|
|
}
|
|
|
|
return http.HandlerFunc(handleCamli), true
|
|
|
|
}
|
|
|
|
|
|
|
|
func unsupportedHandler(conn http.ResponseWriter, req *http.Request) {
|
2011-03-05 23:32:08 +00:00
|
|
|
httputil.BadRequestError(conn, "Unsupported camlistore path or method.")
|
2011-02-03 06:42:31 +00:00
|
|
|
}
|
2010-12-14 02:20:31 +00:00
|
|
|
|
2010-10-04 15:28:14 +00:00
|
|
|
func handleCamli(conn http.ResponseWriter, req *http.Request) {
|
2011-03-07 04:11:36 +00:00
|
|
|
partName, action, err := parseCamliPath(req.URL.Path)
|
2011-02-03 06:42:31 +00:00
|
|
|
if err != nil {
|
2011-03-05 23:32:08 +00:00
|
|
|
log.Printf("Invalid request for method %q, path %q",
|
2011-02-03 06:42:31 +00:00
|
|
|
req.Method, req.URL.Path)
|
|
|
|
unsupportedHandler(conn, req)
|
|
|
|
return
|
2010-07-18 18:08:45 +00:00
|
|
|
}
|
2011-03-07 04:11:36 +00:00
|
|
|
partition := queuePartitionMap[partName]
|
|
|
|
if partition == nil {
|
|
|
|
httputil.BadRequestError(conn, "Unconfigured partition.")
|
|
|
|
return
|
|
|
|
}
|
2011-03-05 22:25:08 +00:00
|
|
|
handleCamliUsingStorage(conn, req, action, partition, storage)
|
|
|
|
}
|
|
|
|
|
2011-03-07 04:11:36 +00:00
|
|
|
func makeIndexHandler(storage blobserver.Storage) func(conn http.ResponseWriter, req *http.Request) {
|
2011-03-05 22:25:08 +00:00
|
|
|
const prefix = "/indexer"
|
2011-03-07 04:11:36 +00:00
|
|
|
partition := &partitionConfig{
|
|
|
|
name: "indexer",
|
|
|
|
writable: true,
|
|
|
|
readable: false,
|
|
|
|
queue: false,
|
|
|
|
urlbase: mainPartition.urlbase + prefix,
|
|
|
|
}
|
|
|
|
return func(conn http.ResponseWriter, req *http.Request) {
|
|
|
|
if !strings.HasPrefix(req.URL.Path, prefix) {
|
|
|
|
panic("bogus request")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
path := req.URL.Path[len(prefix):]
|
|
|
|
_, action, err := parseCamliPath(path)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("Invalid request for method %q, path %q",
|
|
|
|
req.Method, req.URL.Path)
|
|
|
|
unsupportedHandler(conn, req)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
log.Printf("INDEXER action %s on partition %q", action, partition)
|
|
|
|
handleCamliUsingStorage(conn, req, action, partition, storage)
|
2011-03-05 22:25:08 +00:00
|
|
|
}
|
|
|
|
}
|
2011-02-03 06:42:31 +00:00
|
|
|
|
2011-03-07 04:11:36 +00:00
|
|
|
func handleCamliUsingStorage(conn http.ResponseWriter, req *http.Request, action string, partition blobserver.Partition, storage blobserver.Storage) {
|
2011-02-03 06:42:31 +00:00
|
|
|
handler := unsupportedHandler
|
2011-01-26 05:33:19 +00:00
|
|
|
if *flagRequestLog {
|
2011-02-03 06:42:31 +00:00
|
|
|
log.Printf("method %q; partition %q; action %q", req.Method, partition, action)
|
2011-01-26 05:33:19 +00:00
|
|
|
}
|
2010-07-11 04:18:16 +00:00
|
|
|
switch req.Method {
|
|
|
|
case "GET":
|
2011-02-03 06:42:31 +00:00
|
|
|
switch action {
|
|
|
|
case "enumerate-blobs":
|
2011-02-04 01:08:04 +00:00
|
|
|
handler = auth.RequireAuth(handlers.CreateEnumerateHandler(storage, partition))
|
2011-02-08 16:24:16 +00:00
|
|
|
case "stat":
|
|
|
|
handler = auth.RequireAuth(handlers.CreateStatHandler(storage, partition))
|
2010-07-26 05:18:21 +00:00
|
|
|
default:
|
2011-02-04 01:28:05 +00:00
|
|
|
handler = handlers.CreateGetHandler(storage)
|
2010-07-26 05:18:21 +00:00
|
|
|
}
|
2010-07-11 04:18:16 +00:00
|
|
|
case "POST":
|
2011-02-03 06:42:31 +00:00
|
|
|
switch action {
|
2011-02-08 16:24:16 +00:00
|
|
|
case "stat":
|
|
|
|
handler = auth.RequireAuth(handlers.CreateStatHandler(storage, partition))
|
2011-02-03 06:42:31 +00:00
|
|
|
case "upload":
|
2011-03-07 04:11:36 +00:00
|
|
|
handler = auth.RequireAuth(handlers.CreateUploadHandler(storage, partition))
|
2011-02-03 06:42:31 +00:00
|
|
|
case "remove":
|
|
|
|
// Currently only allows removing from a non-main partition.
|
2011-02-03 23:56:02 +00:00
|
|
|
handler = auth.RequireAuth(handlers.CreateRemoveHandler(storage, partition))
|
2010-07-11 04:18:16 +00:00
|
|
|
}
|
|
|
|
case "PUT": // no longer part of spec
|
2011-03-07 04:11:36 +00:00
|
|
|
handler = auth.RequireAuth(handlers.CreateNonStandardPutHandler(storage, partition))
|
2010-06-13 00:15:49 +00:00
|
|
|
}
|
2010-07-18 18:08:45 +00:00
|
|
|
handler(conn, req)
|
2010-06-12 21:45:58 +00:00
|
|
|
}
|
|
|
|
|
2010-10-04 15:28:14 +00:00
|
|
|
func handleRoot(conn http.ResponseWriter, req *http.Request) {
|
2011-02-04 22:31:23 +00:00
|
|
|
fmt.Fprintf(conn, "<html><body>This is camlistored, a <a href='http://camlistore.org'>Camlistore</a> storage daemon.</body></html>\n")
|
|
|
|
}
|
|
|
|
|
|
|
|
func exitFailure(pattern string, args ...interface{}) {
|
|
|
|
if !strings.HasSuffix(pattern, "\n") {
|
|
|
|
pattern = pattern + "\n"
|
|
|
|
}
|
|
|
|
fmt.Fprintf(os.Stderr, pattern, args...)
|
|
|
|
os.Exit(1)
|
2010-06-12 21:45:58 +00:00
|
|
|
}
|
|
|
|
|
2011-03-07 04:11:36 +00:00
|
|
|
var queuePartitionMap = make(map[string]blobserver.Partition)
|
|
|
|
|
|
|
|
func setupMirrorPartitions() {
|
|
|
|
queuePartitionMap[""] = mainPartition
|
|
|
|
if *flagQueuePartitions == "" {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
for _, partName := range strings.Split(*flagQueuePartitions, ",", -1) {
|
|
|
|
if _, dup := queuePartitionMap[partName]; dup {
|
|
|
|
log.Fatalf("Duplicate partition in --queue-partitions")
|
|
|
|
}
|
|
|
|
part := &partitionConfig{name: partName, writable: false, readable: true, queue: true}
|
|
|
|
part.urlbase = mainPartition.urlbase + "/partition-" + partName
|
|
|
|
mainPartition.mirrors = append(mainPartition.mirrors, part)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-06-12 21:45:58 +00:00
|
|
|
func main() {
|
|
|
|
flag.Parse()
|
|
|
|
|
2011-04-01 19:48:43 +00:00
|
|
|
if *flagUseConfigFiles {
|
|
|
|
configFileMain()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
commandLineConfigurationMain()
|
|
|
|
}
|
|
|
|
|
|
|
|
func commandLineConfigurationMain() {
|
2010-11-15 03:52:52 +00:00
|
|
|
auth.AccessPassword = os.Getenv("CAMLI_PASSWORD")
|
|
|
|
if len(auth.AccessPassword) == 0 {
|
2011-02-04 22:31:23 +00:00
|
|
|
exitFailure("No CAMLI_PASSWORD environment variable set.")
|
2010-06-12 21:45:58 +00:00
|
|
|
}
|
|
|
|
|
2011-04-02 03:45:40 +00:00
|
|
|
if *flagStorageRoot == "" {
|
2011-02-04 22:31:23 +00:00
|
|
|
exitFailure("No storage root specified in --root")
|
2010-06-12 21:45:58 +00:00
|
|
|
}
|
|
|
|
|
2011-04-02 03:45:40 +00:00
|
|
|
var err os.Error
|
|
|
|
storage, err = localdisk.New(*flagStorageRoot)
|
|
|
|
if err != nil {
|
|
|
|
exitFailure("Error for --root of %q: %v", *flagStorageRoot, err)
|
2011-02-04 22:31:23 +00:00
|
|
|
}
|
2010-12-14 02:20:31 +00:00
|
|
|
|
2010-12-06 06:29:11 +00:00
|
|
|
ws := webserver.New()
|
2011-03-07 04:11:36 +00:00
|
|
|
|
|
|
|
mainPartition.urlbase = ws.BaseURL()
|
|
|
|
log.Printf("Base URL is %q", mainPartition.urlbase)
|
|
|
|
setupMirrorPartitions() // after mainPartition.urlbase is set
|
|
|
|
|
|
|
|
|
2011-02-03 06:42:31 +00:00
|
|
|
ws.RegisterPreMux(webserver.HandlerPicker(pickPartitionHandlerMaybe))
|
2010-12-06 06:29:11 +00:00
|
|
|
ws.HandleFunc("/", handleRoot)
|
|
|
|
ws.HandleFunc("/camli/", handleCamli)
|
2011-03-05 22:25:08 +00:00
|
|
|
|
2011-03-16 06:16:24 +00:00
|
|
|
var (
|
|
|
|
myIndexer *mysqlindexer.Indexer
|
|
|
|
ownerBlobRef *blobref.BlobRef
|
|
|
|
)
|
2011-03-13 23:38:32 +00:00
|
|
|
if *flagDevSearch || *flagDevMySql {
|
2011-03-16 06:16:24 +00:00
|
|
|
ownerBlobRef = client.SignerPublicKeyBlobref()
|
|
|
|
if ownerBlobRef == nil {
|
|
|
|
log.Fatalf("Public key not configured.")
|
|
|
|
}
|
|
|
|
|
2011-03-13 23:38:32 +00:00
|
|
|
myIndexer = &mysqlindexer.Indexer{
|
2011-03-05 23:32:08 +00:00
|
|
|
Host: "localhost",
|
|
|
|
User: "root",
|
|
|
|
Password: "root",
|
2011-03-19 01:00:17 +00:00
|
|
|
Database: *flagDatabaseName,
|
2011-03-16 06:16:24 +00:00
|
|
|
OwnerBlobRef: ownerBlobRef,
|
|
|
|
KeyFetcher: blobref.NewSerialFetcher(
|
|
|
|
blobref.NewConfigDirFetcher(),
|
|
|
|
storage),
|
2011-03-05 23:09:36 +00:00
|
|
|
}
|
|
|
|
if ok, err := myIndexer.IsAlive(); !ok {
|
|
|
|
log.Fatalf("Could not connect indexer to MySQL server: %s", err)
|
|
|
|
}
|
2011-03-13 23:38:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: temporary
|
|
|
|
if *flagDevSearch {
|
|
|
|
ws.HandleFunc("/camli/search", func(conn http.ResponseWriter, req *http.Request) {
|
2011-03-16 06:16:24 +00:00
|
|
|
handler := auth.RequireAuth(search.CreateHandler(myIndexer, ownerBlobRef))
|
2011-03-13 23:38:32 +00:00
|
|
|
handler(conn, req)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: temporary
|
|
|
|
if *flagDevMySql {
|
2011-03-07 04:11:36 +00:00
|
|
|
ws.HandleFunc("/indexer/", makeIndexHandler(myIndexer))
|
2011-03-05 22:25:08 +00:00
|
|
|
}
|
|
|
|
|
2010-12-06 06:29:11 +00:00
|
|
|
ws.Handle("/js/", http.FileServer("../../clients/js", "/js/"))
|
|
|
|
ws.Serve()
|
2010-06-12 21:45:58 +00:00
|
|
|
}
|