X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/b6a101cdfe327b839ced7cfc626c6bd78201ff0f..86660414472d4ff0d8267f9845a753497bd41692:/lib/config/load.go diff --git a/lib/config/load.go b/lib/config/load.go index d03ef5388d..fbd01488a0 100644 --- a/lib/config/load.go +++ b/lib/config/load.go @@ -16,6 +16,7 @@ import ( "io/ioutil" "os" "regexp" + "runtime" "strconv" "strings" "time" @@ -25,6 +26,7 @@ import ( "github.com/imdario/mergo" "github.com/prometheus/client_golang/prometheus" "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" ) //go:embed config.default.yml @@ -53,6 +55,8 @@ type Loader struct { // read configdata from, or the time when we read configdata // from a pipe. sourceTimestamp time.Time + // UTC time when configdata was read. + loadTimestamp time.Time } // NewLoader returns a new Loader with Stdin and Logger set to the @@ -83,7 +87,7 @@ func (ldr *Loader) SetupFlags(flagset *flag.FlagSet) { flagset.StringVar(&ldr.CrunchDispatchSlurmPath, "legacy-crunch-dispatch-slurm-config", defaultCrunchDispatchSlurmConfigPath, "Legacy crunch-dispatch-slurm configuration `file`") flagset.StringVar(&ldr.WebsocketPath, "legacy-ws-config", defaultWebsocketConfigPath, "Legacy arvados-ws configuration `file`") flagset.StringVar(&ldr.KeepproxyPath, "legacy-keepproxy-config", defaultKeepproxyConfigPath, "Legacy keepproxy configuration `file`") - flagset.StringVar(&ldr.GitHttpdPath, "legacy-git-httpd-config", defaultGitHttpdConfigPath, "Legacy arv-git-httpd configuration `file`") + flagset.StringVar(&ldr.GitHttpdPath, "legacy-git-httpd-config", defaultGitHttpdConfigPath, "Legacy arvados-git-httpd configuration `file`") flagset.StringVar(&ldr.KeepBalancePath, "legacy-keepbalance-config", defaultKeepBalanceConfigPath, "Legacy keep-balance configuration `file`") flagset.BoolVar(&ldr.SkipLegacy, "skip-legacy", false, "Don't load legacy config files") } @@ -173,32 +177,36 @@ func (ldr *Loader) MungeLegacyConfigArgs(lgr logrus.FieldLogger, args []string, return munged } -func (ldr *Loader) loadBytes(path string) ([]byte, time.Time, error) { +func (ldr *Loader) loadBytes(path string) (buf []byte, sourceTime, loadTime time.Time, err error) { + loadTime = time.Now().UTC() if path == "-" { - buf, err := ioutil.ReadAll(ldr.Stdin) - return buf, time.Now().UTC(), err + buf, err = ioutil.ReadAll(ldr.Stdin) + sourceTime = loadTime + return } f, err := os.Open(path) if err != nil { - return nil, time.Time{}, err + return } defer f.Close() fi, err := f.Stat() if err != nil { - return nil, time.Time{}, err + return } - buf, err := ioutil.ReadAll(f) - return buf, fi.ModTime().UTC(), err + sourceTime = fi.ModTime().UTC() + buf, err = ioutil.ReadAll(f) + return } func (ldr *Loader) Load() (*arvados.Config, error) { if ldr.configdata == nil { - buf, t, err := ldr.loadBytes(ldr.Path) + buf, sourceTime, loadTime, err := ldr.loadBytes(ldr.Path) if err != nil { return nil, err } ldr.configdata = buf - ldr.sourceTimestamp = t + ldr.sourceTimestamp = sourceTime + ldr.loadTimestamp = loadTime } // FIXME: We should reject YAML if the same key is used twice @@ -291,7 +299,10 @@ func (ldr *Loader) Load() (*arvados.Config, error) { ldr.loadOldKeepBalanceConfig, ) } - loadFuncs = append(loadFuncs, ldr.setImplicitStorageClasses) + loadFuncs = append(loadFuncs, + ldr.setImplicitStorageClasses, + ldr.setLoopbackInstanceType, + ) for _, f := range loadFuncs { err = f(&cfg) if err != nil { @@ -334,6 +345,7 @@ func (ldr *Loader) Load() (*arvados.Config, error) { ldr.checkEnum("Containers.LocalKeepLogsToContainerLog", cc.Containers.LocalKeepLogsToContainerLog, "none", "all", "errors"), ldr.checkEmptyKeepstores(cc), ldr.checkUnlistedKeepstores(cc), + ldr.checkLocalKeepBlobBuffers(cc), ldr.checkStorageClasses(cc), ldr.checkCUDAVersions(cc), // TODO: check non-empty Rendezvous on @@ -355,7 +367,7 @@ func (ldr *Loader) checkClusterID(label, clusterID string, emptyStringOk bool) e if emptyStringOk && clusterID == "" { return nil } else if !acceptableClusterIDRe.MatchString(clusterID) { - return fmt.Errorf("%s: cluster ID should be 5 alphanumeric characters", label) + return fmt.Errorf("%s: cluster ID should be 5 lowercase alphanumeric characters", label) } return nil } @@ -408,6 +420,67 @@ func (ldr *Loader) checkEnum(label, value string, accepted ...string) error { return fmt.Errorf("%s: unacceptable value %q: must be one of %q", label, value, accepted) } +func (ldr *Loader) setLoopbackInstanceType(cfg *arvados.Config) error { + for id, cc := range cfg.Clusters { + if !cc.Containers.CloudVMs.Enable || cc.Containers.CloudVMs.Driver != "loopback" { + continue + } + if len(cc.InstanceTypes) == 1 { + continue + } + if len(cc.InstanceTypes) > 1 { + return fmt.Errorf("Clusters.%s.InstanceTypes: cannot use multiple InstanceTypes with loopback driver", id) + } + // No InstanceTypes configured. Fill in implicit + // default. + hostram, err := getHostRAM() + if err != nil { + return err + } + scratch, err := getFilesystemSize(os.TempDir()) + if err != nil { + return err + } + cc.InstanceTypes = arvados.InstanceTypeMap{"localhost": { + Name: "localhost", + ProviderType: "localhost", + VCPUs: runtime.NumCPU(), + RAM: hostram, + Scratch: scratch, + IncludedScratch: scratch, + }} + cfg.Clusters[id] = cc + } + return nil +} + +func getFilesystemSize(path string) (arvados.ByteSize, error) { + var stat unix.Statfs_t + err := unix.Statfs(path, &stat) + if err != nil { + return 0, err + } + return arvados.ByteSize(stat.Blocks * uint64(stat.Bsize)), nil +} + +var reMemTotal = regexp.MustCompile(`(^|\n)MemTotal: *(\d+) kB\n`) + +func getHostRAM() (arvados.ByteSize, error) { + buf, err := os.ReadFile("/proc/meminfo") + if err != nil { + return 0, err + } + m := reMemTotal.FindSubmatch(buf) + if m == nil { + return 0, errors.New("error parsing /proc/meminfo: no MemTotal") + } + kb, err := strconv.ParseInt(string(m[2]), 10, 64) + if err != nil { + return 0, fmt.Errorf("error parsing /proc/meminfo: %q: %w", m[2], err) + } + return arvados.ByteSize(kb) * 1024, nil +} + func (ldr *Loader) setImplicitStorageClasses(cfg *arvados.Config) error { cluster: for id, cc := range cfg.Clusters { @@ -431,6 +504,24 @@ cluster: return nil } +func (ldr *Loader) checkLocalKeepBlobBuffers(cc arvados.Cluster) error { + kbb := cc.Containers.LocalKeepBlobBuffersPerVCPU + if kbb == 0 { + return nil + } + for uuid, vol := range cc.Volumes { + if len(vol.AccessViaHosts) > 0 { + ldr.Logger.Warnf("LocalKeepBlobBuffersPerVCPU is %d but will not be used because at least one volume (%s) uses AccessViaHosts -- suggest changing to 0", kbb, uuid) + return nil + } + if !vol.ReadOnly && vol.Replication < cc.Collections.DefaultReplication { + ldr.Logger.Warnf("LocalKeepBlobBuffersPerVCPU is %d but will not be used because at least one volume (%s) has lower replication than DefaultReplication (%d < %d) -- suggest changing to 0", kbb, uuid, vol.Replication, cc.Collections.DefaultReplication) + return nil + } + } + return nil +} + func (ldr *Loader) checkStorageClasses(cc arvados.Cluster) error { classOnVolume := map[string]bool{} for volid, vol := range cc.Volumes { @@ -579,12 +670,22 @@ func (ldr *Loader) autofillPreemptible(label string, cc *arvados.Cluster) { // called before Load(). Metrics are not updated by subsequent calls // to Load(). func (ldr *Loader) RegisterMetrics(reg *prometheus.Registry) { + hash := fmt.Sprintf("%x", sha256.Sum256(ldr.configdata)) vec := prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: "arvados", Subsystem: "config", Name: "source_timestamp_seconds", Help: "Timestamp of config file when it was loaded.", }, []string{"sha256"}) - vec.WithLabelValues(fmt.Sprintf("%x", sha256.Sum256(ldr.configdata))).Set(float64(ldr.sourceTimestamp.UnixNano()) / 1e9) + vec.WithLabelValues(hash).Set(float64(ldr.sourceTimestamp.UnixNano()) / 1e9) + reg.MustRegister(vec) + + vec = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: "arvados", + Subsystem: "config", + Name: "load_timestamp_seconds", + Help: "Time when config file was loaded.", + }, []string{"sha256"}) + vec.WithLabelValues(hash).Set(float64(ldr.loadTimestamp.UnixNano()) / 1e9) reg.MustRegister(vec) }