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