"syscall"
"time"
- "git.curoverse.com/arvados.git/lib/config"
- "git.curoverse.com/arvados.git/sdk/go/arvados"
- "git.curoverse.com/arvados.git/sdk/go/arvadosclient"
- "git.curoverse.com/arvados.git/sdk/go/health"
- "git.curoverse.com/arvados.git/sdk/go/httpserver"
- "git.curoverse.com/arvados.git/sdk/go/keepclient"
+ "git.arvados.org/arvados.git/lib/config"
+ "git.arvados.org/arvados.git/sdk/go/arvados"
+ "git.arvados.org/arvados.git/sdk/go/arvadosclient"
+ "git.arvados.org/arvados.git/sdk/go/health"
+ "git.arvados.org/arvados.git/sdk/go/httpserver"
+ "git.arvados.org/arvados.git/sdk/go/keepclient"
"github.com/coreos/go-systemd/daemon"
"github.com/ghodss/yaml"
"github.com/gorilla/mux"
const rfc3339NanoFixed = "2006-01-02T15:04:05.000000000Z07:00"
-func configure(logger log.FieldLogger, args []string) *arvados.Cluster {
+func configure(logger log.FieldLogger, args []string) (*arvados.Cluster, error) {
flags := flag.NewFlagSet(args[0], flag.ExitOnError)
- flags.Usage = usage
dumpConfig := flags.Bool("dump-config", false, "write current configuration to stdout and exit")
getVersion := flags.Bool("version", false, "Print version information and exit.")
// Print version information if requested
if *getVersion {
fmt.Printf("keepproxy %s\n", version)
- return nil
+ return nil, nil
}
cfg, err := loader.Load()
if err != nil {
- log.Fatal(err)
+ return nil, err
}
-
cluster, err := cfg.GetCluster("")
if err != nil {
- log.Fatal(err)
+ return nil, err
}
if *dumpConfig {
out, err := yaml.Marshal(cfg)
if err != nil {
- log.Fatal(err)
+ return nil, err
}
- _, err = os.Stdout.Write(out)
- if err != nil {
- log.Fatal(err)
+ if _, err := os.Stdout.Write(out); err != nil {
+ return nil, err
}
- return nil
+ return nil, nil
}
- return cluster
+ return cluster, nil
}
func main() {
TimestampFormat: rfc3339NanoFixed,
}
- cluster := configure(logger, os.Args)
+ cluster, err := configure(logger, os.Args)
+ if err != nil {
+ log.Fatal(err)
+ }
if cluster == nil {
return
}
func run(logger log.FieldLogger, cluster *arvados.Cluster) error {
client, err := arvados.NewClientFromConfig(cluster)
if err != nil {
- log.Fatal(err)
+ return err
}
client.AuthToken = cluster.SystemRootToken
arv, err := arvadosclient.New(client)
if err != nil {
- log.Fatalf("Error setting up arvados client %s", err.Error())
+ return fmt.Errorf("Error setting up arvados client %v", err)
+ }
+
+ // If a config file is available, use the keepstores defined there
+ // instead of the legacy autodiscover mechanism via the API server
+ for k := range cluster.Services.Keepstore.InternalURLs {
+ arv.KeepServiceURIs = append(arv.KeepServiceURIs, strings.TrimRight(k.String(), "/"))
}
if cluster.SystemLogs.LogLevel == "debug" {
}
kc, err := keepclient.MakeKeepClient(arv)
if err != nil {
- log.Fatalf("Error setting up keep client %s", err.Error())
+ return fmt.Errorf("Error setting up keep client %v", err)
}
keepclient.RefreshServiceDiscoveryOnSIGHUP()
var lErr error
listener, lErr = net.Listen("tcp", listen.Host)
if lErr != nil {
- log.Fatalf("listen(%s): %s", listen.Host, lErr)
+ return fmt.Errorf("listen(%s): %v", listen.Host, lErr)
}
if _, err := daemon.SdNotify(false, "READY=1"); err != nil {
log.Printf("Error notifying init daemon: %v", err)
}
- log.Println("Listening at", listener.Addr())
+ log.Println("listening at", listener.Addr())
// Shut down the server gracefully (by closing the listener)
// if SIGTERM is received.
signal.Notify(term, syscall.SIGINT)
// Start serving requests.
- router = MakeRESTRouter(kc, time.Duration(cluster.API.KeepServiceRequestTimeout), cluster.SystemRootToken)
+ router = MakeRESTRouter(kc, time.Duration(keepclient.DefaultProxyRequestTimeout), cluster.ManagementToken)
return http.Serve(listener, httpserver.AddRequestIDs(httpserver.LogRequests(router)))
}
-type ApiTokenCache struct {
+type APITokenCache struct {
tokens map[string]int64
lock sync.Mutex
expireTime int64
}
-// Cache the token and set an expire time. If we already have an expire time
-// on the token, it is not updated.
-func (this *ApiTokenCache) RememberToken(token string) {
- this.lock.Lock()
- defer this.lock.Unlock()
+// RememberToken caches the token and set an expire time. If we already have
+// an expire time on the token, it is not updated.
+func (cache *APITokenCache) RememberToken(token string) {
+ cache.lock.Lock()
+ defer cache.lock.Unlock()
now := time.Now().Unix()
- if this.tokens[token] == 0 {
- this.tokens[token] = now + this.expireTime
+ if cache.tokens[token] == 0 {
+ cache.tokens[token] = now + cache.expireTime
}
}
-// Check if the cached token is known and still believed to be valid.
-func (this *ApiTokenCache) RecallToken(token string) bool {
- this.lock.Lock()
- defer this.lock.Unlock()
+// RecallToken checks if the cached token is known and still believed to be
+// valid.
+func (cache *APITokenCache) RecallToken(token string) bool {
+ cache.lock.Lock()
+ defer cache.lock.Unlock()
now := time.Now().Unix()
- if this.tokens[token] == 0 {
+ if cache.tokens[token] == 0 {
// Unknown token
return false
- } else if now < this.tokens[token] {
+ } else if now < cache.tokens[token] {
// Token is known and still valid
return true
} else {
// Token is expired
- this.tokens[token] = 0
+ cache.tokens[token] = 0
return false
}
}
+// GetRemoteAddress returns a string with the remote address for the request.
+// If the X-Forwarded-For header is set and has a non-zero length, it returns a
+// string made from a comma separated list of all the remote addresses,
+// starting with the one(s) from the X-Forwarded-For header.
func GetRemoteAddress(req *http.Request) string {
if xff := req.Header.Get("X-Forwarded-For"); xff != "" {
return xff + "," + req.RemoteAddr
return req.RemoteAddr
}
-func CheckAuthorizationHeader(kc *keepclient.KeepClient, cache *ApiTokenCache, req *http.Request) (pass bool, tok string) {
+func CheckAuthorizationHeader(kc *keepclient.KeepClient, cache *APITokenCache, req *http.Request) (pass bool, tok string) {
parts := strings.SplitN(req.Header.Get("Authorization"), " ", 2)
if len(parts) < 2 || !(parts[0] == "OAuth2" || parts[0] == "Bearer") || len(parts[1]) == 0 {
return false, ""
type proxyHandler struct {
http.Handler
*keepclient.KeepClient
- *ApiTokenCache
+ *APITokenCache
timeout time.Duration
transport *http.Transport
}
KeepClient: kc,
timeout: timeout,
transport: &transport,
- ApiTokenCache: &ApiTokenCache{
+ APITokenCache: &APITokenCache{
tokens: make(map[string]int64),
expireTime: 300,
},
SetCorsHeaders(resp)
}
-var BadAuthorizationHeader = errors.New("Missing or invalid Authorization header")
-var ContentLengthMismatch = errors.New("Actual length != expected content length")
-var MethodNotSupported = errors.New("Method not supported")
+var errBadAuthorizationHeader = errors.New("Missing or invalid Authorization header")
+var errContentLengthMismatch = errors.New("Actual length != expected content length")
+var errMethodNotSupported = errors.New("Method not supported")
var removeHint, _ = regexp.Compile("\\+K@[a-z0-9]{5}(\\+|$)")
var pass bool
var tok string
- if pass, tok = CheckAuthorizationHeader(kc, h.ApiTokenCache, req); !pass {
- status, err = http.StatusForbidden, BadAuthorizationHeader
+ if pass, tok = CheckAuthorizationHeader(kc, h.APITokenCache, req); !pass {
+ status, err = http.StatusForbidden, errBadAuthorizationHeader
return
}
defer reader.Close()
}
default:
- status, err = http.StatusNotImplemented, MethodNotSupported
+ status, err = http.StatusNotImplemented, errMethodNotSupported
return
}
case "GET":
responseLength, err = io.Copy(resp, reader)
if err == nil && expectLength > -1 && responseLength != expectLength {
- err = ContentLengthMismatch
+ err = errContentLengthMismatch
}
}
case keepclient.Error:
}
}
-var LengthRequiredError = errors.New(http.StatusText(http.StatusLengthRequired))
-var LengthMismatchError = errors.New("Locator size hint does not match Content-Length header")
+var errLengthRequired = errors.New(http.StatusText(http.StatusLengthRequired))
+var errLengthMismatch = errors.New("Locator size hint does not match Content-Length header")
func (h *proxyHandler) Put(resp http.ResponseWriter, req *http.Request) {
if err := h.checkLoop(resp, req); err != nil {
_, err = fmt.Sscanf(req.Header.Get("Content-Length"), "%d", &expectLength)
if err != nil || expectLength < 0 {
- err = LengthRequiredError
+ err = errLengthRequired
status = http.StatusLengthRequired
return
}
status = http.StatusBadRequest
return
} else if loc.Size > 0 && int64(loc.Size) != expectLength {
- err = LengthMismatchError
+ err = errLengthMismatch
status = http.StatusBadRequest
return
}
var pass bool
var tok string
- if pass, tok = CheckAuthorizationHeader(kc, h.ApiTokenCache, req); !pass {
- err = BadAuthorizationHeader
+ if pass, tok = CheckAuthorizationHeader(kc, h.APITokenCache, req); !pass {
+ err = errBadAuthorizationHeader
status = http.StatusForbidden
return
}
// Check if the client specified the number of replicas
if req.Header.Get("X-Keep-Desired-Replicas") != "" {
var r int
- _, err := fmt.Sscanf(req.Header.Get(keepclient.X_Keep_Desired_Replicas), "%d", &r)
+ _, err := fmt.Sscanf(req.Header.Get(keepclient.XKeepDesiredReplicas), "%d", &r)
if err == nil {
kc.Want_replicas = r
}
}
// Tell the client how many successful PUTs we accomplished
- resp.Header().Set(keepclient.X_Keep_Replicas_Stored, fmt.Sprintf("%d", wroteReplicas))
+ resp.Header().Set(keepclient.XKeepReplicasStored, fmt.Sprintf("%d", wroteReplicas))
switch err.(type) {
case nil:
}()
kc := h.makeKeepClient(req)
- ok, token := CheckAuthorizationHeader(kc, h.ApiTokenCache, req)
+ ok, token := CheckAuthorizationHeader(kc, h.APITokenCache, req)
if !ok {
- status, err = http.StatusForbidden, BadAuthorizationHeader
+ status, err = http.StatusForbidden, errBadAuthorizationHeader
return
}
// Only GET method is supported
if req.Method != "GET" {
- status, err = http.StatusNotImplemented, MethodNotSupported
+ status, err = http.StatusNotImplemented, errMethodNotSupported
return
}