3114: Merge branch 'master' into 3114-copy-to-project
[arvados.git] / services / keepstore / keepstore.go
1 package main
2
3 import (
4         "bytes"
5         "flag"
6         "fmt"
7         "io/ioutil"
8         "log"
9         "net"
10         "net/http"
11         "os"
12         "os/signal"
13         "strings"
14         "syscall"
15         "time"
16 )
17
18 // ======================
19 // Configuration settings
20 //
21 // TODO(twp): make all of these configurable via command line flags
22 // and/or configuration file settings.
23
24 // Default TCP address on which to listen for requests.
25 // Initialized by the --listen flag.
26 const DEFAULT_ADDR = ":25107"
27
28 // A Keep "block" is 64MB.
29 const BLOCKSIZE = 64 * 1024 * 1024
30
31 // A Keep volume must have at least MIN_FREE_KILOBYTES available
32 // in order to permit writes.
33 const MIN_FREE_KILOBYTES = BLOCKSIZE / 1024
34
35 var PROC_MOUNTS = "/proc/mounts"
36
37 // The Keep VolumeManager maintains a list of available volumes.
38 // Initialized by the --volumes flag (or by FindKeepVolumes).
39 var KeepVM VolumeManager
40
41 // enforce_permissions controls whether permission signatures
42 // should be enforced (affecting GET and DELETE requests).
43 // Initialized by the --enforce-permissions flag.
44 var enforce_permissions bool
45
46 // permission_ttl is the time duration for which new permission
47 // signatures (returned by PUT requests) will be valid.
48 // Initialized by the --permission-ttl flag.
49 var permission_ttl time.Duration
50
51 // data_manager_token represents the API token used by the
52 // Data Manager, and is required on certain privileged operations.
53 // Initialized by the --data-manager-token-file flag.
54 var data_manager_token string
55
56 // never_delete can be used to prevent the DELETE handler from
57 // actually deleting anything.
58 var never_delete = false
59
60 // ==========
61 // Error types.
62 //
63 type KeepError struct {
64         HTTPCode int
65         ErrMsg   string
66 }
67
68 var (
69         BadRequestError     = &KeepError{400, "Bad Request"}
70         CollisionError      = &KeepError{500, "Collision"}
71         RequestHashError    = &KeepError{422, "Hash mismatch in request"}
72         PermissionError     = &KeepError{403, "Forbidden"}
73         DiskHashError       = &KeepError{500, "Hash mismatch in stored data"}
74         ExpiredError        = &KeepError{401, "Expired permission signature"}
75         NotFoundError       = &KeepError{404, "Not Found"}
76         GenericError        = &KeepError{500, "Fail"}
77         FullError           = &KeepError{503, "Full"}
78         TooLongError        = &KeepError{504, "Timeout"}
79         MethodDisabledError = &KeepError{405, "Method disabled"}
80 )
81
82 func (e *KeepError) Error() string {
83         return e.ErrMsg
84 }
85
86 // TODO(twp): continue moving as much code as possible out of main
87 // so it can be effectively tested. Esp. handling and postprocessing
88 // of command line flags (identifying Keep volumes and initializing
89 // permission arguments).
90
91 func main() {
92         log.Println("Keep started: pid", os.Getpid())
93
94         // Parse command-line flags:
95         //
96         // -listen=ipaddr:port
97         //    Interface on which to listen for requests. Use :port without
98         //    an ipaddr to listen on all network interfaces.
99         //    Examples:
100         //      -listen=127.0.0.1:4949
101         //      -listen=10.0.1.24:8000
102         //      -listen=:25107 (to listen to port 25107 on all interfaces)
103         //
104         // -volumes
105         //    A comma-separated list of directories to use as Keep volumes.
106         //    Example:
107         //      -volumes=/var/keep01,/var/keep02,/var/keep03/subdir
108         //
109         //    If -volumes is empty or is not present, Keep will select volumes
110         //    by looking at currently mounted filesystems for /keep top-level
111         //    directories.
112
113         var (
114                 data_manager_token_file string
115                 listen                  string
116                 permission_key_file     string
117                 permission_ttl_sec      int
118                 serialize_io            bool
119                 volumearg               string
120                 pidfile                 string
121         )
122         flag.StringVar(
123                 &data_manager_token_file,
124                 "data-manager-token-file",
125                 "",
126                 "File with the API token used by the Data Manager. All DELETE "+
127                         "requests or GET /index requests must carry this token.")
128         flag.BoolVar(
129                 &enforce_permissions,
130                 "enforce-permissions",
131                 false,
132                 "Enforce permission signatures on requests.")
133         flag.StringVar(
134                 &listen,
135                 "listen",
136                 DEFAULT_ADDR,
137                 "Interface on which to listen for requests, in the format "+
138                         "ipaddr:port. e.g. -listen=10.0.1.24:8000. Use -listen=:port "+
139                         "to listen on all network interfaces.")
140         flag.BoolVar(
141                 &never_delete,
142                 "never-delete",
143                 false,
144                 "If set, nothing will be deleted. HTTP 405 will be returned "+
145                         "for valid DELETE requests.")
146         flag.StringVar(
147                 &permission_key_file,
148                 "permission-key-file",
149                 "",
150                 "File containing the secret key for generating and verifying "+
151                         "permission signatures.")
152         flag.IntVar(
153                 &permission_ttl_sec,
154                 "permission-ttl",
155                 1209600,
156                 "Expiration time (in seconds) for newly generated permission "+
157                         "signatures.")
158         flag.BoolVar(
159                 &serialize_io,
160                 "serialize",
161                 false,
162                 "If set, all read and write operations on local Keep volumes will "+
163                         "be serialized.")
164         flag.StringVar(
165                 &volumearg,
166                 "volumes",
167                 "",
168                 "Comma-separated list of directories to use for Keep volumes, "+
169                         "e.g. -volumes=/var/keep1,/var/keep2. If empty or not "+
170                         "supplied, Keep will scan mounted filesystems for volumes "+
171                         "with a /keep top-level directory.")
172
173         flag.StringVar(
174                 &pidfile,
175                 "pid",
176                 "",
177                 "Path to write pid file")
178
179         flag.Parse()
180
181         // Look for local keep volumes.
182         var keepvols []string
183         if volumearg == "" {
184                 // TODO(twp): decide whether this is desirable default behavior.
185                 // In production we may want to require the admin to specify
186                 // Keep volumes explicitly.
187                 keepvols = FindKeepVolumes()
188         } else {
189                 keepvols = strings.Split(volumearg, ",")
190         }
191
192         // Check that the specified volumes actually exist.
193         var goodvols []Volume = nil
194         for _, v := range keepvols {
195                 if _, err := os.Stat(v); err == nil {
196                         log.Println("adding Keep volume:", v)
197                         newvol := MakeUnixVolume(v, serialize_io)
198                         goodvols = append(goodvols, &newvol)
199                 } else {
200                         log.Printf("bad Keep volume: %s\n", err)
201                 }
202         }
203
204         if len(goodvols) == 0 {
205                 log.Fatal("could not find any keep volumes")
206         }
207
208         // Initialize data manager token and permission key.
209         // If these tokens are specified but cannot be read,
210         // raise a fatal error.
211         if data_manager_token_file != "" {
212                 if buf, err := ioutil.ReadFile(data_manager_token_file); err == nil {
213                         data_manager_token = strings.TrimSpace(string(buf))
214                 } else {
215                         log.Fatalf("reading data manager token: %s\n", err)
216                 }
217         }
218         if permission_key_file != "" {
219                 if buf, err := ioutil.ReadFile(permission_key_file); err == nil {
220                         PermissionSecret = bytes.TrimSpace(buf)
221                 } else {
222                         log.Fatalf("reading permission key: %s\n", err)
223                 }
224         }
225
226         // Initialize permission TTL
227         permission_ttl = time.Duration(permission_ttl_sec) * time.Second
228
229         // If --enforce-permissions is true, we must have a permission key
230         // to continue.
231         if PermissionSecret == nil {
232                 if enforce_permissions {
233                         log.Fatal("--enforce-permissions requires a permission key")
234                 } else {
235                         log.Println("Running without a PermissionSecret. Block locators " +
236                                 "returned by this server will not be signed, and will be rejected " +
237                                 "by a server that enforces permissions.")
238                         log.Println("To fix this, run Keep with --permission-key-file=<path> " +
239                                 "to define the location of a file containing the permission key.")
240                 }
241         }
242
243         // Start a round-robin VolumeManager with the volumes we have found.
244         KeepVM = MakeRRVolumeManager(goodvols)
245
246         // Tell the built-in HTTP server to direct all requests to the REST
247         // router.
248         http.Handle("/", MakeRESTRouter())
249
250         // Set up a TCP listener.
251         listener, err := net.Listen("tcp", listen)
252         if err != nil {
253                 log.Fatal(err)
254         }
255
256         // Shut down the server gracefully (by closing the listener)
257         // if SIGTERM is received.
258         term := make(chan os.Signal, 1)
259         go func(sig <-chan os.Signal) {
260                 s := <-sig
261                 log.Println("caught signal:", s)
262                 listener.Close()
263         }(term)
264         signal.Notify(term, syscall.SIGTERM)
265
266         if pidfile != "" {
267                 f, err := os.Create(pidfile)
268                 if err == nil {
269                         fmt.Fprint(f, os.Getpid())
270                         f.Close()
271                 } else {
272                         log.Printf("Error writing pid file (%s): %s", pidfile, err.Error())
273                 }
274         }
275
276         // Start listening for requests.
277         srv := &http.Server{Addr: listen}
278         srv.Serve(listener)
279
280         log.Println("shutting down")
281
282         if pidfile != "" {
283                 os.Remove(pidfile)
284         }
285 }