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