Merge branch '8784-dir-listings'
[arvados.git] / services / keepstore / config.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 package main
6
7 import (
8         "bytes"
9         "encoding/json"
10         "fmt"
11         "io/ioutil"
12         "strings"
13         "time"
14
15         "git.curoverse.com/arvados.git/sdk/go/arvados"
16         log "github.com/Sirupsen/logrus"
17 )
18
19 type Config struct {
20         Debug  bool
21         Listen string
22
23         LogFormat string
24
25         PIDFile string
26
27         MaxBuffers  int
28         MaxRequests int
29
30         BlobSignatureTTL    arvados.Duration
31         BlobSigningKeyFile  string
32         RequireSignatures   bool
33         SystemAuthTokenFile string
34         EnableDelete        bool
35         TrashLifetime       arvados.Duration
36         TrashCheckInterval  arvados.Duration
37
38         Volumes VolumeList
39
40         blobSigningKey  []byte
41         systemAuthToken string
42         debugLogf       func(string, ...interface{})
43 }
44
45 var theConfig = DefaultConfig()
46
47 const rfc3339NanoFixed = "2006-01-02T15:04:05.000000000Z07:00"
48
49 // DefaultConfig returns the default configuration.
50 func DefaultConfig() *Config {
51         return &Config{
52                 Listen:             ":25107",
53                 LogFormat:          "json",
54                 MaxBuffers:         128,
55                 RequireSignatures:  true,
56                 BlobSignatureTTL:   arvados.Duration(14 * 24 * time.Hour),
57                 TrashLifetime:      arvados.Duration(14 * 24 * time.Hour),
58                 TrashCheckInterval: arvados.Duration(24 * time.Hour),
59                 Volumes:            []Volume{},
60         }
61 }
62
63 // Start should be called exactly once: after setting all public
64 // fields, and before using the config.
65 func (cfg *Config) Start() error {
66         if cfg.Debug {
67                 log.SetLevel(log.DebugLevel)
68                 cfg.debugLogf = log.Printf
69                 cfg.debugLogf("debugging enabled")
70         } else {
71                 cfg.debugLogf = func(string, ...interface{}) {}
72         }
73
74         switch strings.ToLower(cfg.LogFormat) {
75         case "text":
76                 log.SetFormatter(&log.TextFormatter{
77                         FullTimestamp:   true,
78                         TimestampFormat: rfc3339NanoFixed,
79                 })
80         case "json":
81                 log.SetFormatter(&log.JSONFormatter{
82                         TimestampFormat: rfc3339NanoFixed,
83                 })
84         default:
85                 return fmt.Errorf(`unsupported log format %q (try "text" or "json")`, cfg.LogFormat)
86         }
87
88         if cfg.MaxBuffers < 0 {
89                 return fmt.Errorf("MaxBuffers must be greater than zero")
90         }
91         bufs = newBufferPool(cfg.MaxBuffers, BlockSize)
92
93         if cfg.MaxRequests < 1 {
94                 cfg.MaxRequests = cfg.MaxBuffers * 2
95                 log.Printf("MaxRequests <1 or not specified; defaulting to MaxBuffers * 2 == %d", cfg.MaxRequests)
96         }
97
98         if cfg.BlobSigningKeyFile != "" {
99                 buf, err := ioutil.ReadFile(cfg.BlobSigningKeyFile)
100                 if err != nil {
101                         return fmt.Errorf("reading blob signing key file: %s", err)
102                 }
103                 cfg.blobSigningKey = bytes.TrimSpace(buf)
104                 if len(cfg.blobSigningKey) == 0 {
105                         return fmt.Errorf("blob signing key file %q is empty", cfg.BlobSigningKeyFile)
106                 }
107         } else if cfg.RequireSignatures {
108                 return fmt.Errorf("cannot enable RequireSignatures (-enforce-permissions) without a blob signing key")
109         } else {
110                 log.Println("Running without a blob signing key. Block locators " +
111                         "returned by this server will not be signed, and will be rejected " +
112                         "by a server that enforces permissions.")
113                 log.Println("To fix this, use the BlobSigningKeyFile config entry.")
114         }
115
116         if fn := cfg.SystemAuthTokenFile; fn != "" {
117                 buf, err := ioutil.ReadFile(fn)
118                 if err != nil {
119                         return fmt.Errorf("cannot read system auth token file %q: %s", fn, err)
120                 }
121                 cfg.systemAuthToken = strings.TrimSpace(string(buf))
122         }
123
124         if cfg.EnableDelete {
125                 log.Print("Trash/delete features are enabled. WARNING: this has not " +
126                         "been extensively tested. You should disable this unless you can afford to lose data.")
127         }
128
129         if len(cfg.Volumes) == 0 {
130                 if (&unixVolumeAdder{cfg}).Discover() == 0 {
131                         return fmt.Errorf("no volumes found")
132                 }
133         }
134         for _, v := range cfg.Volumes {
135                 if err := v.Start(); err != nil {
136                         return fmt.Errorf("volume %s: %s", v, err)
137                 }
138                 log.Printf("Using volume %v (writable=%v)", v, v.Writable())
139         }
140         return nil
141 }
142
143 // VolumeTypes is built up by init() funcs in the source files that
144 // define the volume types.
145 var VolumeTypes = []func() VolumeWithExamples{}
146
147 type VolumeList []Volume
148
149 // UnmarshalJSON, given an array of objects, deserializes each object
150 // as the volume type indicated by the object's Type field.
151 func (vols *VolumeList) UnmarshalJSON(data []byte) error {
152         typeMap := map[string]func() VolumeWithExamples{}
153         for _, factory := range VolumeTypes {
154                 t := factory().Type()
155                 if _, ok := typeMap[t]; ok {
156                         log.Fatal("volume type %+q is claimed by multiple VolumeTypes")
157                 }
158                 typeMap[t] = factory
159         }
160
161         var mapList []map[string]interface{}
162         err := json.Unmarshal(data, &mapList)
163         if err != nil {
164                 return err
165         }
166         for _, mapIn := range mapList {
167                 typeIn, ok := mapIn["Type"].(string)
168                 if !ok {
169                         return fmt.Errorf("invalid volume type %+v", mapIn["Type"])
170                 }
171                 factory, ok := typeMap[typeIn]
172                 if !ok {
173                         return fmt.Errorf("unsupported volume type %+q", typeIn)
174                 }
175                 data, err := json.Marshal(mapIn)
176                 if err != nil {
177                         return err
178                 }
179                 vol := factory()
180                 err = json.Unmarshal(data, vol)
181                 if err != nil {
182                         return err
183                 }
184                 *vols = append(*vols, vol)
185         }
186         return nil
187 }
188
189 // MarshalJSON adds a "Type" field to each volume corresponding to its
190 // Type().
191 func (vl *VolumeList) MarshalJSON() ([]byte, error) {
192         data := []byte{'['}
193         for _, vs := range *vl {
194                 j, err := json.Marshal(vs)
195                 if err != nil {
196                         return nil, err
197                 }
198                 if len(data) > 1 {
199                         data = append(data, byte(','))
200                 }
201                 t, err := json.Marshal(vs.Type())
202                 if err != nil {
203                         panic(err)
204                 }
205                 data = append(data, j[0])
206                 data = append(data, []byte(`"Type":`)...)
207                 data = append(data, t...)
208                 data = append(data, byte(','))
209                 data = append(data, j[1:]...)
210         }
211         return append(data, byte(']')), nil
212 }