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