7 "git.curoverse.com/arvados.git/sdk/go/keepclient"
19 // ======================
20 // Configuration settings
22 // TODO(twp): make all of these configurable via command line flags
23 // and/or configuration file settings.
25 // Default TCP address on which to listen for requests.
26 // Initialized by the --listen flag.
27 const DEFAULT_ADDR = ":25107"
29 // A Keep "block" is 64MB.
30 const BLOCKSIZE = 64 * 1024 * 1024
32 // A Keep volume must have at least MIN_FREE_KILOBYTES available
33 // in order to permit writes.
34 const MIN_FREE_KILOBYTES = BLOCKSIZE / 1024
36 var PROC_MOUNTS = "/proc/mounts"
38 // enforce_permissions controls whether permission signatures
39 // should be enforced (affecting GET and DELETE requests).
40 // Initialized by the --enforce-permissions flag.
41 var enforce_permissions bool
43 // permission_ttl is the time duration for which new permission
44 // signatures (returned by PUT requests) will be valid.
45 // Initialized by the --permission-ttl flag.
46 var permission_ttl time.Duration
48 // data_manager_token represents the API token used by the
49 // Data Manager, and is required on certain privileged operations.
50 // Initialized by the --data-manager-token-file flag.
51 var data_manager_token string
53 // never_delete can be used to prevent the DELETE handler from
54 // actually deleting anything.
55 var never_delete = false
60 type KeepError struct {
66 BadRequestError = &KeepError{400, "Bad Request"}
67 UnauthorizedError = &KeepError{401, "Unauthorized"}
68 CollisionError = &KeepError{500, "Collision"}
69 RequestHashError = &KeepError{422, "Hash mismatch in request"}
70 PermissionError = &KeepError{403, "Forbidden"}
71 DiskHashError = &KeepError{500, "Hash mismatch in stored data"}
72 ExpiredError = &KeepError{401, "Expired permission signature"}
73 NotFoundError = &KeepError{404, "Not Found"}
74 GenericError = &KeepError{500, "Fail"}
75 FullError = &KeepError{503, "Full"}
76 TooLongError = &KeepError{504, "Timeout"}
77 MethodDisabledError = &KeepError{405, "Method disabled"}
80 func (e *KeepError) Error() string {
84 // ========================
85 // Internal data structures
87 // These global variables are used by multiple parts of the
88 // program. They are good candidates for moving into their own
91 // The Keep VolumeManager maintains a list of available volumes.
92 // Initialized by the --volumes flag (or by FindKeepVolumes).
93 var KeepVM VolumeManager
95 // The pull list manager and trash queue are threadsafe queues which
96 // support atomic update operations. The PullHandler and TrashHandler
97 // store results from Data Manager /pull and /trash requests here.
99 // See the Keep and Data Manager design documents for more details:
100 // https://arvados.org/projects/arvados/wiki/Keep_Design_Doc
101 // https://arvados.org/projects/arvados/wiki/Data_Manager_Design_Doc
104 var trashq *WorkQueue
106 // TODO(twp): continue moving as much code as possible out of main
107 // so it can be effectively tested. Esp. handling and postprocessing
108 // of command line flags (identifying Keep volumes and initializing
109 // permission arguments).
112 log.Println("Keep started: pid", os.Getpid())
114 // Parse command-line flags:
116 // -listen=ipaddr:port
117 // Interface on which to listen for requests. Use :port without
118 // an ipaddr to listen on all network interfaces.
120 // -listen=127.0.0.1:4949
121 // -listen=10.0.1.24:8000
122 // -listen=:25107 (to listen to port 25107 on all interfaces)
125 // A comma-separated list of directories to use as Keep volumes.
127 // -volumes=/var/keep01,/var/keep02,/var/keep03/subdir
129 // If -volumes is empty or is not present, Keep will select volumes
130 // by looking at currently mounted filesystems for /keep top-level
134 data_manager_token_file string
136 permission_key_file string
137 permission_ttl_sec int
143 &data_manager_token_file,
144 "data-manager-token-file",
146 "File with the API token used by the Data Manager. All DELETE "+
147 "requests or GET /index requests must carry this token.")
149 &enforce_permissions,
150 "enforce-permissions",
152 "Enforce permission signatures on requests.")
157 "Interface on which to listen for requests, in the format "+
158 "ipaddr:port. e.g. -listen=10.0.1.24:8000. Use -listen=:port "+
159 "to listen on all network interfaces.")
164 "If set, nothing will be deleted. HTTP 405 will be returned "+
165 "for valid DELETE requests.")
167 &permission_key_file,
168 "permission-key-file",
170 "File containing the secret key for generating and verifying "+
171 "permission signatures.")
176 "Expiration time (in seconds) for newly generated permission "+
182 "If set, all read and write operations on local Keep volumes will "+
188 "Comma-separated list of directories to use for Keep volumes, "+
189 "e.g. -volumes=/var/keep1,/var/keep2. If empty or not "+
190 "supplied, Keep will scan mounted filesystems for volumes "+
191 "with a /keep top-level directory.")
197 "Path to write pid file")
201 // Look for local keep volumes.
202 var keepvols []string
204 // TODO(twp): decide whether this is desirable default behavior.
205 // In production we may want to require the admin to specify
206 // Keep volumes explicitly.
207 keepvols = FindKeepVolumes()
209 keepvols = strings.Split(volumearg, ",")
212 // Check that the specified volumes actually exist.
213 var goodvols []Volume = nil
214 for _, v := range keepvols {
215 if _, err := os.Stat(v); err == nil {
216 log.Println("adding Keep volume:", v)
217 newvol := MakeUnixVolume(v, serialize_io)
218 goodvols = append(goodvols, &newvol)
220 log.Printf("bad Keep volume: %s\n", err)
224 if len(goodvols) == 0 {
225 log.Fatal("could not find any keep volumes")
228 // Initialize data manager token and permission key.
229 // If these tokens are specified but cannot be read,
230 // raise a fatal error.
231 if data_manager_token_file != "" {
232 if buf, err := ioutil.ReadFile(data_manager_token_file); err == nil {
233 data_manager_token = strings.TrimSpace(string(buf))
235 log.Fatalf("reading data manager token: %s\n", err)
238 if permission_key_file != "" {
239 if buf, err := ioutil.ReadFile(permission_key_file); err == nil {
240 PermissionSecret = bytes.TrimSpace(buf)
242 log.Fatalf("reading permission key: %s\n", err)
246 // Initialize permission TTL
247 permission_ttl = time.Duration(permission_ttl_sec) * time.Second
249 // If --enforce-permissions is true, we must have a permission key
251 if PermissionSecret == nil {
252 if enforce_permissions {
253 log.Fatal("--enforce-permissions requires a permission key")
255 log.Println("Running without a PermissionSecret. Block locators " +
256 "returned by this server will not be signed, and will be rejected " +
257 "by a server that enforces permissions.")
258 log.Println("To fix this, run Keep with --permission-key-file=<path> " +
259 "to define the location of a file containing the permission key.")
263 // Start a round-robin VolumeManager with the volumes we have found.
264 KeepVM = MakeRRVolumeManager(goodvols)
266 // Tell the built-in HTTP server to direct all requests to the REST router.
267 loggingRouter := MakeLoggingRESTRouter()
268 http.HandleFunc("/", func(resp http.ResponseWriter, req *http.Request) {
269 loggingRouter.ServeHTTP(resp, req)
272 // Set up a TCP listener.
273 listener, err := net.Listen("tcp", listen)
278 // Initialize Pull queue and worker
279 keepClient := &keepclient.KeepClient{
283 Client: &http.Client{},
286 // Initialize the pullq and worker
287 pullq = NewWorkQueue()
288 go RunPullWorker(pullq, keepClient)
290 // Initialize the trashq and worker
291 trashq = NewWorkQueue()
292 go RunTrashWorker(trashq)
294 // Shut down the server gracefully (by closing the listener)
295 // if SIGTERM is received.
296 term := make(chan os.Signal, 1)
297 go func(sig <-chan os.Signal) {
299 log.Println("caught signal:", s)
302 signal.Notify(term, syscall.SIGTERM)
305 f, err := os.Create(pidfile)
307 fmt.Fprint(f, os.Getpid())
310 log.Printf("Error writing pid file (%s): %s", pidfile, err.Error())
314 // Start listening for requests.
315 srv := &http.Server{Addr: listen}
318 log.Println("shutting down")