1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
21 "git.arvados.org/arvados.git/sdk/go/arvados"
22 "git.arvados.org/arvados.git/sdk/go/ctxlog"
23 "github.com/ghodss/yaml"
24 "github.com/prometheus/client_golang/prometheus"
25 "github.com/prometheus/common/expfmt"
26 "github.com/sirupsen/logrus"
27 "golang.org/x/sys/unix"
28 check "gopkg.in/check.v1"
31 // Gocheck boilerplate
32 func Test(t *testing.T) {
36 var _ = check.Suite(&LoadSuite{})
38 var emptyConfigYAML = `Clusters: {"z1111": {}}`
40 // Return a new Loader that reads cluster config from configdata
41 // (instead of the usual default /etc/arvados/config.yml), and logs to
42 // logdst or (if that's nil) c.Log.
43 func testLoader(c *check.C, configdata string, logdst io.Writer) *Loader {
44 logger := ctxlog.TestLogger(c)
50 ldr := NewLoader(bytes.NewBufferString(configdata), logger)
55 type LoadSuite struct{}
57 func (s *LoadSuite) SetUpSuite(c *check.C) {
58 os.Unsetenv("ARVADOS_API_HOST")
59 os.Unsetenv("ARVADOS_API_HOST_INSECURE")
60 os.Unsetenv("ARVADOS_API_TOKEN")
63 func (s *LoadSuite) TestEmpty(c *check.C) {
64 cfg, err := testLoader(c, "", nil).Load()
65 c.Check(cfg, check.IsNil)
66 c.Assert(err, check.ErrorMatches, `config does not define any clusters`)
69 func (s *LoadSuite) TestNoConfigs(c *check.C) {
70 cfg, err := testLoader(c, emptyConfigYAML, nil).Load()
71 c.Assert(err, check.IsNil)
72 c.Assert(cfg.Clusters, check.HasLen, 1)
73 cc, err := cfg.GetCluster("z1111")
74 c.Assert(err, check.IsNil)
75 c.Check(cc.ClusterID, check.Equals, "z1111")
76 c.Check(cc.API.MaxRequestAmplification, check.Equals, 4)
77 c.Check(cc.API.MaxItemsPerResponse, check.Equals, 1000)
80 func (s *LoadSuite) TestNullKeyDoesNotOverrideDefault(c *check.C) {
81 ldr := testLoader(c, `{"Clusters":{"z1111":{"API":}}}`, nil)
82 ldr.SkipDeprecated = true
83 cfg, err := ldr.Load()
84 c.Assert(err, check.IsNil)
85 c1, err := cfg.GetCluster("z1111")
86 c.Assert(err, check.IsNil)
87 c.Check(c1.ClusterID, check.Equals, "z1111")
88 c.Check(c1.API.MaxRequestAmplification, check.Equals, 4)
89 c.Check(c1.API.MaxItemsPerResponse, check.Equals, 1000)
92 func (s *LoadSuite) TestMungeLegacyConfigArgs(c *check.C) {
93 f, err := ioutil.TempFile("", "")
94 c.Check(err, check.IsNil)
95 defer os.Remove(f.Name())
96 io.WriteString(f, "Debug: true\n")
99 f, err = ioutil.TempFile("", "")
100 c.Check(err, check.IsNil)
101 defer os.Remove(f.Name())
102 io.WriteString(f, emptyConfigYAML)
105 for _, trial := range []struct {
110 []string{"-config", oldfile},
111 []string{"-old-config", oldfile},
114 []string{"-config=" + oldfile},
115 []string{"-old-config=" + oldfile},
118 []string{"-config", newfile},
119 []string{"-config", newfile},
122 []string{"-config=" + newfile},
123 []string{"-config=" + newfile},
126 []string{"-foo", oldfile},
127 []string{"-foo", oldfile},
130 []string{"-foo=" + oldfile},
131 []string{"-foo=" + oldfile},
134 []string{"-foo", "-config=" + oldfile},
135 []string{"-foo", "-old-config=" + oldfile},
138 []string{"-foo", "bar", "-config=" + oldfile},
139 []string{"-foo", "bar", "-old-config=" + oldfile},
142 []string{"-foo=bar", "baz", "-config=" + oldfile},
143 []string{"-foo=bar", "baz", "-old-config=" + oldfile},
146 []string{"-config=/dev/null"},
147 []string{"-config=/dev/null"},
150 []string{"-config=-"},
151 []string{"-config=-"},
154 []string{"-config="},
155 []string{"-config="},
158 []string{"-foo=bar", "baz", "-config"},
159 []string{"-foo=bar", "baz", "-config"},
166 var logbuf bytes.Buffer
167 logger := logrus.New()
171 args := ldr.MungeLegacyConfigArgs(logger, trial.argsIn, "-old-config")
172 c.Check(args, check.DeepEquals, trial.argsOut)
173 if fmt.Sprintf("%v", trial.argsIn) != fmt.Sprintf("%v", trial.argsOut) {
174 c.Check(logbuf.String(), check.Matches, `.*`+oldfile+` is not a cluster config file -- interpreting -config as -old-config.*\n`)
179 func (s *LoadSuite) TestSampleKeys(c *check.C) {
180 for _, yaml := range []string{
181 `{"Clusters":{"z1111":{}}}`,
182 `{"Clusters":{"z1111":{"InstanceTypes":{"Foo":{"RAM": "12345M"}}}}}`,
184 cfg, err := testLoader(c, yaml, nil).Load()
185 c.Assert(err, check.IsNil)
186 cc, err := cfg.GetCluster("z1111")
187 c.Assert(err, check.IsNil)
188 _, hasSample := cc.InstanceTypes["SAMPLE"]
189 c.Check(hasSample, check.Equals, false)
190 if strings.Contains(yaml, "Foo") {
191 c.Check(cc.InstanceTypes["Foo"].RAM, check.Equals, arvados.ByteSize(12345000000))
192 c.Check(cc.InstanceTypes["Foo"].Price, check.Equals, 0.0)
197 func (s *LoadSuite) TestMultipleClusters(c *check.C) {
198 ldr := testLoader(c, `{"Clusters":{"z1111":{},"z2222":{}}}`, nil)
199 ldr.SkipDeprecated = true
200 cfg, err := ldr.Load()
201 c.Assert(err, check.IsNil)
202 c1, err := cfg.GetCluster("z1111")
203 c.Assert(err, check.IsNil)
204 c.Check(c1.ClusterID, check.Equals, "z1111")
205 c2, err := cfg.GetCluster("z2222")
206 c.Assert(err, check.IsNil)
207 c.Check(c2.ClusterID, check.Equals, "z2222")
210 func (s *LoadSuite) TestDeprecatedOrUnknownWarning(c *check.C) {
211 var logbuf bytes.Buffer
212 _, err := testLoader(c, `
215 ManagementToken: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
216 SystemRootToken: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
218 BlobSigningKey: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
225 Host: z2222.arvadosapi.com
231 "http://host.example:12345": {}
234 "http://host.example:12345":
239 "http://host.example:12345": {}
241 zzzzz-nyw5e-aaaaaaaaaaaaaaa: {Replication: 2}
243 c.Assert(err, check.IsNil)
244 c.Log(logbuf.String())
245 logs := strings.Split(strings.TrimSuffix(logbuf.String(), "\n"), "\n")
246 for _, log := range logs {
247 c.Check(log, check.Matches, `.*deprecated or unknown config entry:.*(RunTimeEngine.*RuntimeEngine|BadKey1|BadKey2|KeepStore|ServiceS|RendezVous).*`)
249 c.Check(logs, check.HasLen, 6)
252 func (s *LoadSuite) checkSAMPLEKeys(c *check.C, path string, x interface{}) {
253 v := reflect.Indirect(reflect.ValueOf(x))
256 var stringKeys, sampleKey bool
260 if k.Kind() == reflect.String {
262 if k.String() == "SAMPLE" || k.String() == "xxxxx" {
264 s.checkSAMPLEKeys(c, path+"."+k.String(), iter.Value().Interface())
268 if stringKeys && !sampleKey {
269 c.Errorf("%s is a map with string keys (type %T) but config.default.yml has no SAMPLE key", path, x)
273 for i := 0; i < v.NumField(); i++ {
275 if val.CanInterface() {
276 s.checkSAMPLEKeys(c, path+"."+v.Type().Field(i).Name, val.Interface())
282 func (s *LoadSuite) TestDefaultConfigHasAllSAMPLEKeys(c *check.C) {
283 var logbuf bytes.Buffer
284 loader := testLoader(c, string(DefaultYAML), &logbuf)
285 cfg, err := loader.Load()
286 c.Assert(err, check.IsNil)
287 s.checkSAMPLEKeys(c, "", cfg)
290 func (s *LoadSuite) TestNoUnrecognizedKeysInDefaultConfig(c *check.C) {
291 var logbuf bytes.Buffer
292 var supplied map[string]interface{}
293 yaml.Unmarshal(DefaultYAML, &supplied)
295 loader := testLoader(c, string(DefaultYAML), &logbuf)
296 cfg, err := loader.Load()
297 c.Assert(err, check.IsNil)
298 var loaded map[string]interface{}
299 buf, err := yaml.Marshal(cfg)
300 c.Assert(err, check.IsNil)
301 err = yaml.Unmarshal(buf, &loaded)
302 c.Assert(err, check.IsNil)
304 c.Check(logbuf.String(), check.Matches, `(?ms).*SystemRootToken: secret token is not set.*`)
305 c.Check(logbuf.String(), check.Matches, `(?ms).*ManagementToken: secret token is not set.*`)
306 c.Check(logbuf.String(), check.Matches, `(?ms).*Collections.BlobSigningKey: secret token is not set.*`)
308 loader.logExtraKeys(loaded, supplied, "")
309 c.Check(logbuf.String(), check.Equals, "")
312 func (s *LoadSuite) TestNoWarningsForDumpedConfig(c *check.C) {
313 var logbuf bytes.Buffer
314 cfg, err := testLoader(c, `
317 ManagementToken: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
318 SystemRootToken: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
320 BlobSigningKey: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
323 IncludedScratch: 123456
325 c.Assert(err, check.IsNil)
326 yaml, err := yaml.Marshal(cfg)
327 c.Assert(err, check.IsNil)
328 // Well, *nearly* no warnings. SourceTimestamp and
329 // SourceSHA256 are included in a config-dump, but not
330 // expected in a real config file.
331 yaml = regexp.MustCompile(`(^|\n)(Source(Timestamp|SHA256): .*?\n)+`).ReplaceAll(yaml, []byte("$1"))
332 cfgDumped, err := testLoader(c, string(yaml), &logbuf).Load()
333 c.Assert(err, check.IsNil)
334 // SourceTimestamp and SourceSHA256 aren't expected to be
335 // preserved through dump+load
336 cfgDumped.SourceTimestamp = cfg.SourceTimestamp
337 cfgDumped.SourceSHA256 = cfg.SourceSHA256
338 c.Check(cfg, check.DeepEquals, cfgDumped)
339 c.Check(logbuf.String(), check.Equals, "")
342 func (s *LoadSuite) TestUnacceptableTokens(c *check.C) {
343 for _, trial := range []struct {
348 {false, "SystemRootToken", "SystemRootToken: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa_b_c"},
349 {false, "ManagementToken", "ManagementToken: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa b c"},
350 {false, "ManagementToken", "ManagementToken: \"$aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabc\""},
351 {false, "Collections.BlobSigningKey", "Collections: {BlobSigningKey: \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa⛵\"}"},
352 {true, "SystemRootToken", "SystemRootToken: a_b_c"},
353 {true, "ManagementToken", "ManagementToken: a b c"},
354 {true, "ManagementToken", "ManagementToken: \"$abc\""},
355 {true, "Collections.BlobSigningKey", "Collections: {BlobSigningKey: \"⛵\"}"},
357 c.Logf("trying bogus config: %s", trial.example)
358 _, err := testLoader(c, "Clusters:\n zzzzz:\n "+trial.example, nil).Load()
359 c.Check(err, check.ErrorMatches, `Clusters.zzzzz.`+trial.configPath+`: unacceptable characters in token.*`)
363 func (s *LoadSuite) TestPostgreSQLKeyConflict(c *check.C) {
364 _, err := testLoader(c, `
372 c.Check(err, check.ErrorMatches, `Clusters.zzzzz.PostgreSQL.Connection: multiple entries for "(dbname|host)".*`)
375 func (s *LoadSuite) TestBadClusterIDs(c *check.C) {
376 for _, data := range []string{`
379 ManagementToken: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
380 SystemRootToken: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
382 BlobSigningKey: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
386 ManagementToken: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
387 SystemRootToken: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
389 BlobSigningKey: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
392 Host: Zzzzz.arvadosapi.com
397 ManagementToken: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
398 SystemRootToken: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
400 BlobSigningKey: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
406 v, err := testLoader(c, data, nil).Load()
408 c.Logf("%#v", v.Clusters)
410 c.Check(err, check.ErrorMatches, `.*cluster ID should be 5 lowercase alphanumeric characters.*`)
414 func (s *LoadSuite) TestBadType(c *check.C) {
415 for _, data := range []string{`
428 ConnectionPool: "foo"
438 ConnectionPool: [] # {foo: bar} isn't caught here; we rely on config-check
442 v, err := testLoader(c, data, nil).Load()
444 c.Logf("%#v", v.Clusters["zzzzz"].PostgreSQL.ConnectionPool)
446 c.Check(err, check.ErrorMatches, `.*cannot unmarshal .*PostgreSQL.*`)
450 func (s *LoadSuite) TestMovedKeys(c *check.C) {
451 checkEquivalent(c, `# config has old keys only
455 MultiClusterRequestConcurrency: 3
456 MaxItemsPerResponse: 999
461 MaxRequestAmplification: 3
462 MaxItemsPerResponse: 999
464 checkEquivalent(c, `# config has both old and new keys; old values win
468 MultiClusterRequestConcurrency: 0
469 MaxItemsPerResponse: 555
471 MaxRequestAmplification: 3
472 MaxItemsPerResponse: 999
477 MaxRequestAmplification: 0
478 MaxItemsPerResponse: 555
482 func checkEquivalent(c *check.C, goty, expectedy string) string {
483 var logbuf bytes.Buffer
484 gotldr := testLoader(c, goty, &logbuf)
485 expectedldr := testLoader(c, expectedy, nil)
486 checkEquivalentLoaders(c, gotldr, expectedldr)
487 return logbuf.String()
490 func checkEqualYAML(c *check.C, got, expected interface{}) {
491 expectedyaml, err := yaml.Marshal(expected)
492 c.Assert(err, check.IsNil)
493 gotyaml, err := yaml.Marshal(got)
494 c.Assert(err, check.IsNil)
495 if !bytes.Equal(gotyaml, expectedyaml) {
496 cmd := exec.Command("diff", "-u", "--label", "expected", "--label", "got", "/dev/fd/3", "/dev/fd/4")
497 for _, y := range [][]byte{expectedyaml, gotyaml} {
498 pr, pw, err := os.Pipe()
499 c.Assert(err, check.IsNil)
501 go func(data []byte) {
505 cmd.ExtraFiles = append(cmd.ExtraFiles, pr)
507 diff, err := cmd.CombinedOutput()
508 // diff should report differences and exit non-zero.
509 c.Check(err, check.NotNil)
511 c.Error("got != expected; see diff (-expected +got) above")
515 func checkEquivalentLoaders(c *check.C, gotldr, expectedldr *Loader) {
516 got, err := gotldr.Load()
517 c.Assert(err, check.IsNil)
518 expected, err := expectedldr.Load()
519 c.Assert(err, check.IsNil)
520 // The inputs generally aren't even files, so SourceTimestamp
521 // can't be expected to match.
522 got.SourceTimestamp = expected.SourceTimestamp
523 // Obviously the content isn't identical -- otherwise we
524 // wouldn't need to check that it's equivalent.
525 got.SourceSHA256 = expected.SourceSHA256
526 checkEqualYAML(c, got, expected)
529 func checkListKeys(path string, x interface{}) (err error) {
530 v := reflect.Indirect(reflect.ValueOf(x))
536 if k.Kind() == reflect.String {
537 if err = checkListKeys(path+"."+k.String(), iter.Value().Interface()); err != nil {
545 for i := 0; i < v.NumField(); i++ {
547 structField := v.Type().Field(i)
548 fieldname := structField.Name
549 endsWithList := strings.HasSuffix(fieldname, "List")
550 isAnArray := structField.Type.Kind() == reflect.Slice
551 if endsWithList != isAnArray {
553 err = fmt.Errorf("%s.%s ends with 'List' but field is not an array (type %v)", path, fieldname, val.Kind())
556 if isAnArray && structField.Type.Elem().Kind() != reflect.Uint8 {
557 err = fmt.Errorf("%s.%s is an array but field name does not end in 'List' (slice of %v)", path, fieldname, structField.Type.Elem().Kind())
561 if val.CanInterface() {
562 checkListKeys(path+"."+fieldname, val.Interface())
569 func (s *LoadSuite) TestListKeys(c *check.C) {
572 }{[]string{"a", "b"}}
573 var m1 = make(map[string]interface{})
575 if err := checkListKeys("", m1); err != nil {
581 }{[]string{"a", "b"}}
582 var m2 = make(map[string]interface{})
584 if err := checkListKeys("", m2); err == nil {
585 c.Errorf("Should have produced an error")
591 var m3 = make(map[string]interface{})
593 if err := checkListKeys("", m3); err == nil {
594 c.Errorf("Should have produced an error")
597 loader := testLoader(c, string(DefaultYAML), nil)
598 cfg, err := loader.Load()
599 c.Assert(err, check.IsNil)
600 if err := checkListKeys("", cfg); err != nil {
605 func (s *LoadSuite) TestLoopbackInstanceTypes(c *check.C) {
606 ldr := testLoader(c, `
617 cfg, err := ldr.Load()
618 c.Check(err, check.ErrorMatches, `Clusters\.z1111\.InstanceTypes: cannot use multiple InstanceTypes with loopback driver`)
620 ldr = testLoader(c, `
628 cfg, err = ldr.Load()
629 c.Assert(err, check.IsNil)
630 cc, err := cfg.GetCluster("")
631 c.Assert(err, check.IsNil)
632 c.Check(cc.InstanceTypes, check.HasLen, 1)
633 c.Check(cc.InstanceTypes["localhost"].VCPUs, check.Equals, runtime.NumCPU())
635 ldr = testLoader(c, `
646 cfg, err = ldr.Load()
647 c.Assert(err, check.IsNil)
648 cc, err = cfg.GetCluster("")
649 c.Assert(err, check.IsNil)
650 c.Check(cc.InstanceTypes, check.HasLen, 1)
651 c.Check(cc.InstanceTypes["a"].VCPUs, check.Equals, 9)
654 func (s *LoadSuite) TestWarnUnusedLocalKeep(c *check.C) {
655 var logbuf bytes.Buffer
656 _, err := testLoader(c, `
663 c.Assert(err, check.IsNil)
664 c.Check(logbuf.String(), check.Matches, `(?ms).*LocalKeepBlobBuffersPerVCPU is 1 but will not be used because at least one volume \(z\) has lower replication than DefaultReplication \(1 < 2\) -- suggest changing to 0.*`)
667 _, err = testLoader(c, `
673 "http://0.0.0.0:12345": {}
675 c.Assert(err, check.IsNil)
676 c.Check(logbuf.String(), check.Matches, `(?ms).*LocalKeepBlobBuffersPerVCPU is 1 but will not be used because at least one volume \(z\) uses AccessViaHosts -- suggest changing to 0.*`)
679 func (s *LoadSuite) TestImplicitStorageClasses(c *check.C) {
680 // If StorageClasses and Volumes.*.StorageClasses are all
681 // empty, there is a default storage class named "default".
682 ldr := testLoader(c, `{"Clusters":{"z1111":{}}}`, nil)
683 cfg, err := ldr.Load()
684 c.Assert(err, check.IsNil)
685 cc, err := cfg.GetCluster("z1111")
686 c.Assert(err, check.IsNil)
687 c.Check(cc.StorageClasses, check.HasLen, 1)
688 c.Check(cc.StorageClasses["default"].Default, check.Equals, true)
689 c.Check(cc.StorageClasses["default"].Priority, check.Equals, 0)
691 // The implicit "default" storage class is used by all
693 ldr = testLoader(c, `
698 cfg, err = ldr.Load()
699 c.Assert(err, check.IsNil)
700 cc, err = cfg.GetCluster("z1111")
701 c.Assert(err, check.IsNil)
702 c.Check(cc.StorageClasses, check.HasLen, 1)
703 c.Check(cc.StorageClasses["default"].Default, check.Equals, true)
704 c.Check(cc.StorageClasses["default"].Priority, check.Equals, 0)
705 c.Check(cc.Volumes["z"].StorageClasses["default"], check.Equals, true)
707 // The "default" storage class isn't implicit if any classes
708 // are configured explicitly.
709 ldr = testLoader(c, `
720 cfg, err = ldr.Load()
721 c.Assert(err, check.IsNil)
722 cc, err = cfg.GetCluster("z1111")
723 c.Assert(err, check.IsNil)
724 c.Check(cc.StorageClasses, check.HasLen, 1)
725 c.Check(cc.StorageClasses["local"].Default, check.Equals, true)
726 c.Check(cc.StorageClasses["local"].Priority, check.Equals, 111)
728 // It is an error for a volume to refer to a storage class
729 // that isn't listed in StorageClasses.
730 ldr = testLoader(c, `
742 c.Assert(err, check.ErrorMatches, `z: volume refers to storage class "nx" that is not defined.*`)
744 // It is an error for a volume to refer to a storage class
745 // that isn't listed in StorageClasses ... even if it's
746 // "default", which would exist implicitly if it weren't
747 // referenced explicitly by a volume.
748 ldr = testLoader(c, `
756 c.Assert(err, check.ErrorMatches, `z: volume refers to storage class "default" that is not defined.*`)
758 // If the "default" storage class is configured explicitly, it
759 // is not used implicitly by any volumes, even if it's the
760 // only storage class.
761 var logbuf bytes.Buffer
762 ldr = testLoader(c, `
772 c.Assert(err, check.ErrorMatches, `z: volume has no StorageClasses listed`)
774 // If StorageClasses are configured explicitly, there must be
775 // at least one with Default: true. (Calling one "default" is
777 ldr = testLoader(c, `
788 c.Assert(err, check.ErrorMatches, `there is no default storage class.*`)
791 func (s *LoadSuite) TestPreemptiblePriceFactor(c *check.C) {
802 PreemptiblePriceFactor: 0.5
810 PreemptiblePriceFactor: 0.5
816 Type1.preemptible: # higher price than the auto-added variant would use -- should generate warning
826 Type2.preemptible: # identical to the auto-added variant -- so no warning
833 var logbuf bytes.Buffer
834 cfg, err := testLoader(c, yaml, &logbuf).Load()
835 c.Assert(err, check.IsNil)
836 cc, err := cfg.GetCluster("z1111")
837 c.Assert(err, check.IsNil)
838 c.Check(cc.InstanceTypes["Type1"].Price, check.Equals, 1.23)
839 c.Check(cc.InstanceTypes, check.HasLen, 1)
841 cc, err = cfg.GetCluster("z2222")
842 c.Assert(err, check.IsNil)
843 c.Check(cc.InstanceTypes["Type1"].Preemptible, check.Equals, false)
844 c.Check(cc.InstanceTypes["Type1"].Price, check.Equals, 1.23)
845 c.Check(cc.InstanceTypes["Type1.preemptible"].Preemptible, check.Equals, true)
846 c.Check(cc.InstanceTypes["Type1.preemptible"].Price, check.Equals, 1.23/2)
847 c.Check(cc.InstanceTypes["Type1.preemptible"].ProviderType, check.Equals, "Type1")
848 c.Check(cc.InstanceTypes, check.HasLen, 2)
850 cc, err = cfg.GetCluster("z3333")
851 c.Assert(err, check.IsNil)
852 // Don't overwrite the explicitly configured preemptible variant
853 c.Check(cc.InstanceTypes["Type1.preemptible"].Price, check.Equals, 1.23)
854 c.Check(cc.InstanceTypes, check.HasLen, 4)
855 c.Check(logbuf.String(), check.Matches, `(?ms).*Clusters\.z3333\.InstanceTypes\[Type1\.preemptible\]: already exists, so not automatically adding a preemptible variant of Type1.*`)
856 c.Check(logbuf.String(), check.Not(check.Matches), `(?ms).*Type2\.preemptible.*`)
857 c.Check(logbuf.String(), check.Not(check.Matches), `(?ms).*(z1111|z2222)[^\n]*InstanceTypes.*`)
860 func (s *LoadSuite) TestSourceTimestamp(c *check.C) {
861 conftime, err := time.Parse(time.RFC3339, "2022-03-04T05:06:07-08:00")
862 c.Assert(err, check.IsNil)
863 confdata := `Clusters: {zzzzz: {}}`
864 conffile := c.MkDir() + "/config.yml"
865 ioutil.WriteFile(conffile, []byte(confdata), 0777)
866 tv := unix.NsecToTimeval(conftime.UnixNano())
867 unix.Lutimes(conffile, []unix.Timeval{tv, tv})
868 for _, trial := range []struct {
873 {conffile, conftime},
875 c.Logf("trial: %+v", trial)
876 ldr := NewLoader(strings.NewReader(confdata), ctxlog.TestLogger(c))
877 ldr.Path = trial.configarg
878 cfg, err := ldr.Load()
879 c.Assert(err, check.IsNil)
880 c.Check(cfg.SourceTimestamp, check.Equals, cfg.SourceTimestamp.UTC())
881 c.Check(cfg.SourceTimestamp, check.Equals, ldr.sourceTimestamp)
882 c.Check(int(cfg.SourceTimestamp.Sub(trial.expectTime).Seconds()), check.Equals, 0)
883 c.Check(int(ldr.loadTimestamp.Sub(time.Now()).Seconds()), check.Equals, 0)
886 reg := prometheus.NewRegistry()
887 ldr.RegisterMetrics(reg)
888 enc := expfmt.NewEncoder(&buf, expfmt.FmtText)
889 got, _ := reg.Gather()
890 for _, mf := range got {
893 c.Check(buf.String(), check.Matches, `# HELP .*
895 arvados_config_load_timestamp_seconds{sha256="83aea5d82eb1d53372cd65c936c60acc1c6ef946e61977bbca7cfea709d201a8"} \Q`+fmt.Sprintf("%g", float64(ldr.loadTimestamp.UnixNano())/1e9)+`\E
898 arvados_config_source_timestamp_seconds{sha256="83aea5d82eb1d53372cd65c936c60acc1c6ef946e61977bbca7cfea709d201a8"} \Q`+fmt.Sprintf("%g", float64(cfg.SourceTimestamp.UnixNano())/1e9)+`\E
903 func (s *LoadSuite) TestGetHostRAM(c *check.C) {
904 hostram, err := getHostRAM()
905 c.Check(err, check.IsNil)
906 c.Logf("getHostRAM() == %v", hostram)
909 func (s *LoadSuite) TestGetFilesystemSize(c *check.C) {
911 size, err := getFilesystemSize(path)
912 c.Check(err, check.IsNil)
913 c.Logf("getFilesystemSize(%q) == %v", path, size)