13647: Don't do API calls while loading controller config.
[arvados.git] / lib / config / deprecated_keepstore.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 package config
6
7 import (
8         "bufio"
9         "bytes"
10         "crypto/rand"
11         "encoding/json"
12         "fmt"
13         "io/ioutil"
14         "math/big"
15         "net"
16         "os"
17         "strconv"
18         "strings"
19         "time"
20
21         "git.curoverse.com/arvados.git/sdk/go/arvados"
22         "github.com/sirupsen/logrus"
23 )
24
25 const defaultKeepstoreConfigPath = "/etc/arvados/keepstore/keepstore.yml"
26
27 type oldKeepstoreConfig struct {
28         Debug  *bool
29         Listen *string
30
31         LogFormat *string
32
33         PIDFile *string
34
35         MaxBuffers  *int
36         MaxRequests *int
37
38         BlobSignatureTTL    *arvados.Duration
39         BlobSigningKeyFile  *string
40         RequireSignatures   *bool
41         SystemAuthTokenFile *string
42         EnableDelete        *bool
43         TrashLifetime       *arvados.Duration
44         TrashCheckInterval  *arvados.Duration
45         PullWorkers         *int
46         TrashWorkers        *int
47         EmptyTrashWorkers   *int
48         TLSCertificateFile  *string
49         TLSKeyFile          *string
50
51         Volumes *oldKeepstoreVolumeList
52
53         ManagementToken *string
54
55         DiscoverVolumesFromMountsFile string // not a real legacy config -- just useful for tests
56 }
57
58 type oldKeepstoreVolumeList []oldKeepstoreVolume
59
60 type oldKeepstoreVolume struct {
61         arvados.Volume
62         Type string `json:",omitempty"`
63
64         // Azure driver configs
65         StorageAccountName    string           `json:",omitempty"`
66         StorageAccountKeyFile string           `json:",omitempty"`
67         StorageBaseURL        string           `json:",omitempty"`
68         ContainerName         string           `json:",omitempty"`
69         AzureReplication      int              `json:",omitempty"`
70         RequestTimeout        arvados.Duration `json:",omitempty"`
71         ListBlobsRetryDelay   arvados.Duration `json:",omitempty"`
72         ListBlobsMaxAttempts  int              `json:",omitempty"`
73
74         // S3 driver configs
75         AccessKeyFile      string           `json:",omitempty"`
76         SecretKeyFile      string           `json:",omitempty"`
77         Endpoint           string           `json:",omitempty"`
78         Region             string           `json:",omitempty"`
79         Bucket             string           `json:",omitempty"`
80         LocationConstraint bool             `json:",omitempty"`
81         IndexPageSize      int              `json:",omitempty"`
82         S3Replication      int              `json:",omitempty"`
83         ConnectTimeout     arvados.Duration `json:",omitempty"`
84         ReadTimeout        arvados.Duration `json:",omitempty"`
85         RaceWindow         arvados.Duration `json:",omitempty"`
86         UnsafeDelete       bool             `json:",omitempty"`
87
88         // Directory driver configs
89         Root                 string
90         DirectoryReplication int
91         Serialize            bool
92
93         // Common configs
94         ReadOnly       bool     `json:",omitempty"`
95         StorageClasses []string `json:",omitempty"`
96 }
97
98 // update config using values from an old-style keepstore config file.
99 func (ldr *Loader) loadOldKeepstoreConfig(cfg *arvados.Config) error {
100         if ldr.KeepstorePath == "" {
101                 return nil
102         }
103         hostname, err := os.Hostname()
104         if err != nil {
105                 return fmt.Errorf("getting hostname: %s", err)
106         }
107
108         var oc oldKeepstoreConfig
109         err = ldr.loadOldConfigHelper("keepstore", ldr.KeepstorePath, &oc)
110         if os.IsNotExist(err) && ldr.KeepstorePath == defaultKeepstoreConfigPath {
111                 return nil
112         } else if err != nil {
113                 return err
114         }
115
116         cluster, err := cfg.GetCluster("")
117         if err != nil {
118                 return err
119         }
120
121         myURL := arvados.URL{Scheme: "http"}
122         if oc.TLSCertificateFile != nil && oc.TLSKeyFile != nil {
123                 myURL.Scheme = "https"
124         }
125
126         if v := oc.Debug; v == nil {
127         } else if *v && cluster.SystemLogs.LogLevel != "debug" {
128                 cluster.SystemLogs.LogLevel = "debug"
129         } else if !*v && cluster.SystemLogs.LogLevel != "info" {
130                 cluster.SystemLogs.LogLevel = "info"
131         }
132
133         if v := oc.TLSCertificateFile; v != nil {
134                 cluster.TLS.Certificate = "file://" + *v
135         }
136         if v := oc.TLSKeyFile; v != nil {
137                 cluster.TLS.Key = "file://" + *v
138         }
139         if v := oc.Listen; v != nil {
140                 if _, ok := cluster.Services.Keepstore.InternalURLs[arvados.URL{Scheme: myURL.Scheme, Host: *v}]; ok {
141                         // already listed
142                         myURL.Host = *v
143                 } else if len(*v) > 1 && (*v)[0] == ':' {
144                         myURL.Host = net.JoinHostPort(hostname, (*v)[1:])
145                         cluster.Services.Keepstore.InternalURLs[myURL] = arvados.ServiceInstance{}
146                 } else {
147                         return fmt.Errorf("unable to migrate Listen value %q -- you must update Services.Keepstore.InternalURLs manually, and comment out the Listen entry in your legacy keepstore config file", *v)
148                 }
149         } else {
150                 for url := range cluster.Services.Keepstore.InternalURLs {
151                         if host, _, _ := net.SplitHostPort(url.Host); host == hostname {
152                                 myURL = url
153                                 break
154                         }
155                 }
156                 if myURL.Host == "" {
157                         return fmt.Errorf("unable to migrate legacy keepstore config: no 'Listen' key, and hostname %q does not match an entry in Services.Keepstore.InternalURLs", hostname)
158                 }
159         }
160
161         if v := oc.LogFormat; v != nil {
162                 cluster.SystemLogs.Format = *v
163         }
164         if v := oc.MaxBuffers; v != nil {
165                 cluster.API.MaxKeepBlobBuffers = *v
166         }
167         if v := oc.MaxRequests; v != nil {
168                 cluster.API.MaxConcurrentRequests = *v
169         }
170         if v := oc.BlobSignatureTTL; v != nil {
171                 cluster.Collections.BlobSigningTTL = *v
172         }
173         if v := oc.BlobSigningKeyFile; v != nil {
174                 buf, err := ioutil.ReadFile(*v)
175                 if err != nil {
176                         return fmt.Errorf("error reading BlobSigningKeyFile: %s", err)
177                 }
178                 if key := strings.TrimSpace(string(buf)); key != cluster.Collections.BlobSigningKey {
179                         cluster.Collections.BlobSigningKey = key
180                 }
181         }
182         if v := oc.RequireSignatures; v != nil {
183                 cluster.Collections.BlobSigning = *v
184         }
185         if v := oc.SystemAuthTokenFile; v != nil {
186                 f, err := os.Open(*v)
187                 if err != nil {
188                         return fmt.Errorf("error opening SystemAuthTokenFile: %s", err)
189                 }
190                 defer f.Close()
191                 buf, err := ioutil.ReadAll(f)
192                 if err != nil {
193                         return fmt.Errorf("error reading SystemAuthTokenFile: %s", err)
194                 }
195                 if key := strings.TrimSpace(string(buf)); key != cluster.SystemRootToken {
196                         cluster.SystemRootToken = key
197                 }
198         }
199         if v := oc.EnableDelete; v != nil {
200                 cluster.Collections.BlobTrash = *v
201         }
202         if v := oc.TrashLifetime; v != nil {
203                 cluster.Collections.BlobTrashLifetime = *v
204         }
205         if v := oc.TrashCheckInterval; v != nil {
206                 cluster.Collections.BlobTrashCheckInterval = *v
207         }
208         if v := oc.TrashWorkers; v != nil {
209                 cluster.Collections.BlobTrashConcurrency = *v
210         }
211         if v := oc.EmptyTrashWorkers; v != nil {
212                 cluster.Collections.BlobDeleteConcurrency = *v
213         }
214         if v := oc.PullWorkers; v != nil {
215                 cluster.Collections.BlobReplicateConcurrency = *v
216         }
217         if oc.Volumes == nil || len(*oc.Volumes) == 0 {
218                 ldr.Logger.Warn("no volumes in legacy config; discovering local directory volumes")
219                 err := ldr.discoverLocalVolumes(cluster, oc.DiscoverVolumesFromMountsFile, myURL)
220                 if err != nil {
221                         return fmt.Errorf("error discovering local directory volumes: %s", err)
222                 }
223         } else {
224                 err := ldr.migrateOldKeepstoreVolumes(cluster, oc, myURL)
225                 if err != nil {
226                         return err
227                 }
228         }
229
230         cfg.Clusters[cluster.ClusterID] = *cluster
231         return nil
232 }
233
234 // Merge Volumes section of old keepstore config into cluster config.
235 func (ldr *Loader) migrateOldKeepstoreVolumes(cluster *arvados.Cluster, oc oldKeepstoreConfig, myURL arvados.URL) error {
236         for i, oldvol := range *oc.Volumes {
237                 var accessViaHosts map[arvados.URL]arvados.VolumeAccess
238                 oldUUID, found := ldr.alreadyMigrated(oldvol, cluster.Volumes, myURL)
239                 if found {
240                         accessViaHosts = cluster.Volumes[oldUUID].AccessViaHosts
241                         writers := false
242                         for _, va := range accessViaHosts {
243                                 if !va.ReadOnly {
244                                         writers = true
245                                 }
246                         }
247                         if writers || len(accessViaHosts) == 0 {
248                                 ldr.Logger.Infof("ignoring volume #%d's parameters in legacy keepstore config: using matching entry in cluster config instead", i)
249                                 if len(accessViaHosts) > 0 {
250                                         cluster.Volumes[oldUUID].AccessViaHosts[myURL] = arvados.VolumeAccess{ReadOnly: oldvol.ReadOnly}
251                                 }
252                                 continue
253                         }
254                 }
255                 var newvol arvados.Volume
256                 if found {
257                         ldr.Logger.Infof("ignoring volume #%d's parameters in legacy keepstore config: using matching entry in cluster config instead", i)
258                         newvol = cluster.Volumes[oldUUID]
259                         // Remove the old entry. It will be added back
260                         // below, possibly with a new UUID.
261                         delete(cluster.Volumes, oldUUID)
262                 } else {
263                         v, err := ldr.translateOldKeepstoreVolume(oldvol)
264                         if err != nil {
265                                 return err
266                         }
267                         newvol = v
268                 }
269                 if accessViaHosts == nil {
270                         accessViaHosts = make(map[arvados.URL]arvados.VolumeAccess, 1)
271                 }
272                 accessViaHosts[myURL] = arvados.VolumeAccess{ReadOnly: oldvol.ReadOnly}
273                 newvol.AccessViaHosts = accessViaHosts
274
275                 volUUID := oldUUID
276                 if oldvol.ReadOnly {
277                 } else if oc.Listen == nil {
278                         ldr.Logger.Warn("cannot find optimal volume UUID because Listen address is not given in legacy keepstore config")
279                 } else if uuid, _, err := findKeepServicesItem(cluster, *oc.Listen); err != nil {
280                         ldr.Logger.WithError(err).Warn("cannot find optimal volume UUID: failed to find a matching keep_service listing for this legacy keepstore config")
281                 } else if len(uuid) != 27 {
282                         ldr.Logger.WithField("UUID", uuid).Warn("cannot find optimal volume UUID: keep_service UUID does not have expected format")
283                 } else {
284                         rendezvousUUID := cluster.ClusterID + "-nyw5e-" + uuid[12:]
285                         if _, ok := cluster.Volumes[rendezvousUUID]; ok {
286                                 ldr.Logger.Warn("suggesting a random volume UUID because the volume ID matching our keep_service UUID is already in use")
287                         } else {
288                                 volUUID = rendezvousUUID
289                         }
290                         si := cluster.Services.Keepstore.InternalURLs[myURL]
291                         si.Rendezvous = uuid[12:]
292                         cluster.Services.Keepstore.InternalURLs[myURL] = si
293                 }
294                 if volUUID == "" {
295                         volUUID = newUUID(cluster.ClusterID, "nyw5e")
296                         ldr.Logger.WithField("UUID", volUUID).Infof("suggesting a random volume UUID for volume #%d in legacy config", i)
297                 }
298                 cluster.Volumes[volUUID] = newvol
299         }
300         return nil
301 }
302
303 func (ldr *Loader) translateOldKeepstoreVolume(oldvol oldKeepstoreVolume) (arvados.Volume, error) {
304         var newvol arvados.Volume
305         var params interface{}
306         switch oldvol.Type {
307         case "S3":
308                 accesskeydata, err := ioutil.ReadFile(oldvol.AccessKeyFile)
309                 if err != nil && oldvol.AccessKeyFile != "" {
310                         return newvol, fmt.Errorf("error reading AccessKeyFile: %s", err)
311                 }
312                 secretkeydata, err := ioutil.ReadFile(oldvol.SecretKeyFile)
313                 if err != nil && oldvol.SecretKeyFile != "" {
314                         return newvol, fmt.Errorf("error reading SecretKeyFile: %s", err)
315                 }
316                 newvol = arvados.Volume{
317                         Driver:         "S3",
318                         ReadOnly:       oldvol.ReadOnly,
319                         Replication:    oldvol.S3Replication,
320                         StorageClasses: array2boolmap(oldvol.StorageClasses),
321                 }
322                 params = arvados.S3VolumeDriverParameters{
323                         AccessKey:          string(bytes.TrimSpace(accesskeydata)),
324                         SecretKey:          string(bytes.TrimSpace(secretkeydata)),
325                         Endpoint:           oldvol.Endpoint,
326                         Region:             oldvol.Region,
327                         Bucket:             oldvol.Bucket,
328                         LocationConstraint: oldvol.LocationConstraint,
329                         IndexPageSize:      oldvol.IndexPageSize,
330                         ConnectTimeout:     oldvol.ConnectTimeout,
331                         ReadTimeout:        oldvol.ReadTimeout,
332                         RaceWindow:         oldvol.RaceWindow,
333                         UnsafeDelete:       oldvol.UnsafeDelete,
334                 }
335         case "Azure":
336                 keydata, err := ioutil.ReadFile(oldvol.StorageAccountKeyFile)
337                 if err != nil && oldvol.StorageAccountKeyFile != "" {
338                         return newvol, fmt.Errorf("error reading StorageAccountKeyFile: %s", err)
339                 }
340                 newvol = arvados.Volume{
341                         Driver:         "Azure",
342                         ReadOnly:       oldvol.ReadOnly,
343                         Replication:    oldvol.AzureReplication,
344                         StorageClasses: array2boolmap(oldvol.StorageClasses),
345                 }
346                 params = arvados.AzureVolumeDriverParameters{
347                         StorageAccountName:   oldvol.StorageAccountName,
348                         StorageAccountKey:    string(bytes.TrimSpace(keydata)),
349                         StorageBaseURL:       oldvol.StorageBaseURL,
350                         ContainerName:        oldvol.ContainerName,
351                         RequestTimeout:       oldvol.RequestTimeout,
352                         ListBlobsRetryDelay:  oldvol.ListBlobsRetryDelay,
353                         ListBlobsMaxAttempts: oldvol.ListBlobsMaxAttempts,
354                 }
355         case "Directory":
356                 newvol = arvados.Volume{
357                         Driver:         "Directory",
358                         ReadOnly:       oldvol.ReadOnly,
359                         Replication:    oldvol.DirectoryReplication,
360                         StorageClasses: array2boolmap(oldvol.StorageClasses),
361                 }
362                 params = arvados.DirectoryVolumeDriverParameters{
363                         Root:      oldvol.Root,
364                         Serialize: oldvol.Serialize,
365                 }
366         default:
367                 return newvol, fmt.Errorf("unsupported volume type %q", oldvol.Type)
368         }
369         dp, err := json.Marshal(params)
370         if err != nil {
371                 return newvol, err
372         }
373         newvol.DriverParameters = json.RawMessage(dp)
374         if newvol.Replication < 1 {
375                 newvol.Replication = 1
376         }
377         return newvol, nil
378 }
379
380 func (ldr *Loader) alreadyMigrated(oldvol oldKeepstoreVolume, newvols map[string]arvados.Volume, myURL arvados.URL) (string, bool) {
381         for uuid, newvol := range newvols {
382                 if oldvol.Type != newvol.Driver {
383                         continue
384                 }
385                 switch oldvol.Type {
386                 case "S3":
387                         var params arvados.S3VolumeDriverParameters
388                         if err := json.Unmarshal(newvol.DriverParameters, &params); err == nil &&
389                                 oldvol.Endpoint == params.Endpoint &&
390                                 oldvol.Region == params.Region &&
391                                 oldvol.Bucket == params.Bucket &&
392                                 oldvol.LocationConstraint == params.LocationConstraint {
393                                 return uuid, true
394                         }
395                 case "Azure":
396                         var params arvados.AzureVolumeDriverParameters
397                         if err := json.Unmarshal(newvol.DriverParameters, &params); err == nil &&
398                                 oldvol.StorageAccountName == params.StorageAccountName &&
399                                 oldvol.StorageBaseURL == params.StorageBaseURL &&
400                                 oldvol.ContainerName == params.ContainerName {
401                                 return uuid, true
402                         }
403                 case "Directory":
404                         var params arvados.DirectoryVolumeDriverParameters
405                         if err := json.Unmarshal(newvol.DriverParameters, &params); err == nil &&
406                                 oldvol.Root == params.Root {
407                                 if _, ok := newvol.AccessViaHosts[myURL]; ok || len(newvol.AccessViaHosts) == 0 {
408                                         return uuid, true
409                                 }
410                         }
411                 }
412         }
413         return "", false
414 }
415
416 func (ldr *Loader) discoverLocalVolumes(cluster *arvados.Cluster, mountsFile string, myURL arvados.URL) error {
417         if mountsFile == "" {
418                 mountsFile = "/proc/mounts"
419         }
420         f, err := os.Open(mountsFile)
421         if err != nil {
422                 return fmt.Errorf("error opening %s: %s", mountsFile, err)
423         }
424         defer f.Close()
425         scanner := bufio.NewScanner(f)
426         for scanner.Scan() {
427                 args := strings.Fields(scanner.Text())
428                 dev, mount := args[0], args[1]
429                 if mount == "/" {
430                         continue
431                 }
432                 if dev != "tmpfs" && !strings.HasPrefix(dev, "/dev/") {
433                         continue
434                 }
435                 keepdir := mount + "/keep"
436                 if st, err := os.Stat(keepdir); err != nil || !st.IsDir() {
437                         continue
438                 }
439
440                 ro := false
441                 for _, fsopt := range strings.Split(args[3], ",") {
442                         if fsopt == "ro" {
443                                 ro = true
444                         }
445                 }
446
447                 uuid := newUUID(cluster.ClusterID, "nyw5e")
448                 ldr.Logger.WithFields(logrus.Fields{
449                         "UUID":                       uuid,
450                         "Driver":                     "Directory",
451                         "DriverParameters.Root":      keepdir,
452                         "DriverParameters.Serialize": false,
453                         "ReadOnly":                   ro,
454                         "Replication":                1,
455                 }).Warn("adding local directory volume")
456
457                 p, err := json.Marshal(arvados.DirectoryVolumeDriverParameters{
458                         Root:      keepdir,
459                         Serialize: false,
460                 })
461                 if err != nil {
462                         panic(err)
463                 }
464                 cluster.Volumes[uuid] = arvados.Volume{
465                         Driver:           "Directory",
466                         DriverParameters: p,
467                         ReadOnly:         ro,
468                         Replication:      1,
469                         AccessViaHosts: map[arvados.URL]arvados.VolumeAccess{
470                                 myURL: {ReadOnly: ro},
471                         },
472                 }
473         }
474         if err := scanner.Err(); err != nil {
475                 return fmt.Errorf("reading %s: %s", mountsFile, err)
476         }
477         return nil
478 }
479
480 func array2boolmap(keys []string) map[string]bool {
481         m := map[string]bool{}
482         for _, k := range keys {
483                 m[k] = true
484         }
485         return m
486 }
487
488 func newUUID(clusterID, infix string) string {
489         randint, err := rand.Int(rand.Reader, big.NewInt(0).Exp(big.NewInt(36), big.NewInt(15), big.NewInt(0)))
490         if err != nil {
491                 panic(err)
492         }
493         randstr := randint.Text(36)
494         for len(randstr) < 15 {
495                 randstr = "0" + randstr
496         }
497         return fmt.Sprintf("%s-%s-%s", clusterID, infix, randstr)
498 }
499
500 // Return the UUID and URL for the controller's keep_services listing
501 // corresponding to this host/process.
502 func findKeepServicesItem(cluster *arvados.Cluster, listen string) (uuid string, url arvados.URL, err error) {
503         client, err := arvados.NewClientFromConfig(cluster)
504         if err != nil {
505                 return
506         }
507         client.AuthToken = cluster.SystemRootToken
508         var svcList arvados.KeepServiceList
509         err = client.RequestAndDecode(&svcList, "GET", "arvados/v1/keep_services", nil, nil)
510         if err != nil {
511                 return
512         }
513         hostname, err := os.Hostname()
514         if err != nil {
515                 err = fmt.Errorf("error getting hostname: %s", err)
516                 return
517         }
518         var tried []string
519         for _, ks := range svcList.Items {
520                 if ks.ServiceType == "proxy" {
521                         continue
522                 } else if keepServiceIsMe(ks, hostname, listen) {
523                         return ks.UUID, keepServiceURL(ks), nil
524                 } else {
525                         tried = append(tried, fmt.Sprintf("%s:%d", ks.ServiceHost, ks.ServicePort))
526                 }
527         }
528         err = fmt.Errorf("listen address %q does not match any of the non-proxy keep_services entries %q", listen, tried)
529         return
530 }
531
532 func keepServiceURL(ks arvados.KeepService) arvados.URL {
533         url := arvados.URL{
534                 Scheme: "http",
535                 Host:   net.JoinHostPort(ks.ServiceHost, strconv.Itoa(ks.ServicePort)),
536         }
537         if ks.ServiceSSLFlag {
538                 url.Scheme = "https"
539         }
540         return url
541 }
542
543 var localhostOrAllInterfaces = map[string]bool{
544         "localhost": true,
545         "127.0.0.1": true,
546         "::1":       true,
547         "::":        true,
548         "":          true,
549 }
550
551 // Return true if the given KeepService entry matches the given
552 // hostname and (keepstore config file) listen address.
553 //
554 // If the KeepService host is some variant of "localhost", we assume
555 // this is a testing or single-node environment, ignore the given
556 // hostname, and return true if the port numbers match.
557 //
558 // The hostname isn't assumed to be a FQDN: a hostname "foo.bar" will
559 // match a KeepService host "foo.bar", but also "foo.bar.example",
560 // "foo.bar.example.org", etc.
561 func keepServiceIsMe(ks arvados.KeepService, hostname string, listen string) bool {
562         // Extract the port name/number from listen, and resolve it to
563         // a port number to compare with ks.ServicePort.
564         _, listenport, err := net.SplitHostPort(listen)
565         if err != nil && strings.HasPrefix(listen, ":") {
566                 listenport = listen[1:]
567         }
568         if lp, err := net.LookupPort("tcp", listenport); err != nil {
569                 return false
570         } else if !(lp == ks.ServicePort ||
571                 (lp == 0 && ks.ServicePort == 80)) {
572                 return false
573         }
574
575         kshost := strings.ToLower(ks.ServiceHost)
576         return localhostOrAllInterfaces[kshost] || strings.HasPrefix(kshost+".", strings.ToLower(hostname)+".")
577 }
578
579 // Warn about pending keepstore migration tasks that haven't already
580 // been warned about in loadOldKeepstoreConfig() -- i.e., unmigrated
581 // keepstore hosts other than the present host, and obsolete content
582 // in the keep_services table.
583 func (ldr *Loader) checkPendingKeepstoreMigrations(cluster arvados.Cluster) error {
584         if cluster.Services.Controller.ExternalURL.String() == "" {
585                 ldr.Logger.Debug("Services.Controller.ExternalURL not configured -- skipping check for pending keepstore config migrations")
586                 return nil
587         }
588         if ldr.SkipAPICalls {
589                 ldr.Logger.Debug("(Loader).SkipAPICalls == true -- skipping check for pending keepstore config migrations")
590                 return nil
591         }
592         client, err := arvados.NewClientFromConfig(&cluster)
593         if err != nil {
594                 return err
595         }
596         client.AuthToken = cluster.SystemRootToken
597         var svcList arvados.KeepServiceList
598         err = client.RequestAndDecode(&svcList, "GET", "arvados/v1/keep_services", nil, nil)
599         if err != nil {
600                 ldr.Logger.WithError(err).Warn("error retrieving keep_services list -- skipping check for pending keepstore config migrations")
601                 return nil
602         }
603         hostname, err := os.Hostname()
604         if err != nil {
605                 return fmt.Errorf("error getting hostname: %s", err)
606         }
607         sawTimes := map[time.Time]bool{}
608         for _, ks := range svcList.Items {
609                 sawTimes[ks.CreatedAt] = true
610                 sawTimes[ks.ModifiedAt] = true
611         }
612         if len(sawTimes) <= 1 {
613                 // If all timestamps in the arvados/v1/keep_services
614                 // response are identical, it's a clear sign the
615                 // response was generated on the fly from the cluster
616                 // config, rather than real database records. In that
617                 // case (as well as the case where none are listed at
618                 // all) it's pointless to look for entries that
619                 // haven't yet been migrated to the config file.
620                 return nil
621         }
622         needDBRows := false
623         for _, ks := range svcList.Items {
624                 if ks.ServiceType == "proxy" {
625                         if len(cluster.Services.Keepproxy.InternalURLs) == 0 {
626                                 needDBRows = true
627                                 ldr.Logger.Warn("you should migrate your keepproxy configuration to the cluster configuration file")
628                         }
629                         continue
630                 }
631                 kshost := strings.ToLower(ks.ServiceHost)
632                 if localhostOrAllInterfaces[kshost] || strings.HasPrefix(kshost+".", strings.ToLower(hostname)+".") {
633                         // it would be confusing to recommend
634                         // migrating *this* host's legacy keepstore
635                         // config immediately after explaining that
636                         // very migration process in more detail.
637                         continue
638                 }
639                 ksurl := keepServiceURL(ks)
640                 if _, ok := cluster.Services.Keepstore.InternalURLs[ksurl]; ok {
641                         // already added to InternalURLs
642                         continue
643                 }
644                 ldr.Logger.Warnf("you should migrate the legacy keepstore configuration file on host %s", ks.ServiceHost)
645         }
646         if !needDBRows {
647                 ldr.Logger.Warn("you should delete all of your manually added keep_services listings using `arv --format=uuid keep_service list | xargs -n1 arv keep_service delete --uuid` -- when those are deleted, the services listed in your cluster configuration will be used instead")
648         }
649         return nil
650 }
651
652 // Warn about keepstore servers that have no volumes.
653 func (ldr *Loader) checkEmptyKeepstores(cluster arvados.Cluster) error {
654 servers:
655         for url := range cluster.Services.Keepstore.InternalURLs {
656                 for _, vol := range cluster.Volumes {
657                         if len(vol.AccessViaHosts) == 0 {
658                                 // accessible by all servers
659                                 return nil
660                         }
661                         if _, ok := vol.AccessViaHosts[url]; ok {
662                                 continue servers
663                         }
664                 }
665                 ldr.Logger.Warnf("keepstore configured at %s does not have access to any volumes", url)
666         }
667         return nil
668 }
669
670 // Warn about AccessViaHosts entries that don't correspond to any of
671 // the listed keepstore services.
672 func (ldr *Loader) checkUnlistedKeepstores(cluster arvados.Cluster) error {
673         for uuid, vol := range cluster.Volumes {
674                 if uuid == "SAMPLE" {
675                         continue
676                 }
677                 for url := range vol.AccessViaHosts {
678                         if _, ok := cluster.Services.Keepstore.InternalURLs[url]; !ok {
679                                 ldr.Logger.Warnf("Volumes.%s.AccessViaHosts refers to nonexistent keepstore server %s", uuid, url)
680                         }
681                 }
682         }
683         return nil
684 }