1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
15 "git.arvados.org/arvados.git/sdk/go/arvados"
16 "git.arvados.org/arvados.git/sdk/go/arvadosclient"
17 "git.arvados.org/arvados.git/sdk/go/arvadostest"
18 "git.arvados.org/arvados.git/sdk/go/keepclient"
22 func Test(t *testing.T) {
26 var _ = check.Suite(&Suite{})
30 func (s *Suite) TearDownSuite(c *check.C) {
31 // Undo any changes/additions to the database so they don't affect subsequent tests.
32 arvadostest.ResetEnv()
35 func (s *Suite) SetUpSuite(c *check.C) {
36 arvadostest.StartKeep(2, true)
38 // Get the various arvados, arvadosclient, and keep client objects
39 ac := arvados.NewClientFromEnv()
40 arv, err := arvadosclient.MakeArvadosClient()
41 c.Assert(err, check.Equals, nil)
42 arv.ApiToken = arvadostest.ActiveToken
43 kc, err := keepclient.MakeKeepClient(arv)
44 c.Assert(err, check.Equals, nil)
46 standardE4sV3JSON := `{
47 "Name": "Standard_E4s_v3",
48 "ProviderType": "Standard_E4s_v3",
51 "Scratch": 64000000000,
52 "IncludedScratch": 64000000000,
57 standardD32sV3JSON := `{
58 "Name": "Standard_D32s_v3",
59 "ProviderType": "Standard_D32s_v3",
62 "Scratch": 256000000000,
63 "IncludedScratch": 256000000000,
69 standardA1V2JSON := `{
71 "ProviderType": "Standard_A1_v2",
74 "Scratch": 10000000000,
75 "IncludedScratch": 10000000000,
81 standardA2V2JSON := `{
83 "ProviderType": "Standard_A2_v2",
86 "Scratch": 20000000000,
87 "IncludedScratch": 20000000000,
97 "size": "Standard_D1_v2"
100 "total_ram_mb": 3418,
101 "total_scratch_mb": 51170
105 // Our fixtures do not actually contain file contents. Populate the log collections we're going to use with the node.json file
106 createNodeJSON(c, arv, ac, kc, arvadostest.CompletedContainerRequestUUID, arvadostest.LogCollectionUUID, standardE4sV3JSON)
107 createNodeJSON(c, arv, ac, kc, arvadostest.CompletedContainerRequestUUID2, arvadostest.LogCollectionUUID2, standardD32sV3JSON)
109 createNodeJSON(c, arv, ac, kc, arvadostest.CompletedDiagnosticsContainerRequest1UUID, arvadostest.DiagnosticsContainerRequest1LogCollectionUUID, standardA1V2JSON)
110 createNodeJSON(c, arv, ac, kc, arvadostest.CompletedDiagnosticsContainerRequest2UUID, arvadostest.DiagnosticsContainerRequest2LogCollectionUUID, standardA1V2JSON)
111 createNodeJSON(c, arv, ac, kc, arvadostest.CompletedDiagnosticsHasher1ContainerRequestUUID, arvadostest.Hasher1LogCollectionUUID, standardA1V2JSON)
112 createNodeJSON(c, arv, ac, kc, arvadostest.CompletedDiagnosticsHasher2ContainerRequestUUID, arvadostest.Hasher2LogCollectionUUID, standardA2V2JSON)
113 createNodeJSON(c, arv, ac, kc, arvadostest.CompletedDiagnosticsHasher3ContainerRequestUUID, arvadostest.Hasher3LogCollectionUUID, legacyD1V2JSON)
116 func createNodeJSON(c *check.C, arv *arvadosclient.ArvadosClient, ac *arvados.Client, kc *keepclient.KeepClient, crUUID string, logUUID string, nodeJSON string) {
118 var cr arvados.ContainerRequest
119 err := ac.RequestAndDecode(&cr, "GET", "arvados/v1/container_requests/"+crUUID, nil, nil)
120 c.Assert(err, check.Equals, nil)
121 c.Assert(cr.LogUUID, check.Equals, logUUID)
123 // Get the log collection
124 var coll arvados.Collection
125 err = ac.RequestAndDecode(&coll, "GET", "arvados/v1/collections/"+cr.LogUUID, nil, nil)
126 c.Assert(err, check.IsNil)
128 // Create a node.json file -- the fixture doesn't actually contain the contents of the collection.
129 fs, err := coll.FileSystem(ac, kc)
130 c.Assert(err, check.IsNil)
131 f, err := fs.OpenFile("node.json", os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0777)
132 c.Assert(err, check.IsNil)
133 _, err = io.WriteString(f, nodeJSON)
134 c.Assert(err, check.IsNil)
136 c.Assert(err, check.IsNil)
138 // Flush the data to Keep
139 mtxt, err := fs.MarshalManifest(".")
140 c.Assert(err, check.IsNil)
141 c.Assert(mtxt, check.NotNil)
143 // Update collection record
144 err = ac.RequestAndDecode(&coll, "PUT", "arvados/v1/collections/"+cr.LogUUID, nil, map[string]interface{}{
145 "collection": map[string]interface{}{
146 "manifest_text": mtxt,
149 c.Assert(err, check.IsNil)
152 func (*Suite) TestUsage(c *check.C) {
153 var stdout, stderr bytes.Buffer
154 exitcode := Command.RunCommand("costanalyzer.test", []string{"-help", "-log-level=debug"}, &bytes.Buffer{}, &stdout, &stderr)
155 c.Check(exitcode, check.Equals, 1)
156 c.Check(stdout.String(), check.Equals, "")
157 c.Check(stderr.String(), check.Matches, `(?ms).*Usage:.*`)
160 func (*Suite) TestTimestampRange(c *check.C) {
161 var stdout, stderr bytes.Buffer
162 resultsDir := c.MkDir()
163 // Run costanalyzer with a timestamp range. This should pick up two container requests in "Final" state.
164 exitcode := Command.RunCommand("costanalyzer.test", []string{"-output", resultsDir, "-begin", "2020-11-02T00:00:00", "-end", "2020-11-03T23:59:00"}, &bytes.Buffer{}, &stdout, &stderr)
165 c.Check(exitcode, check.Equals, 0)
166 c.Assert(stderr.String(), check.Matches, "(?ms).*supplied uuids in .*")
168 uuidReport, err := ioutil.ReadFile(resultsDir + "/" + arvadostest.CompletedDiagnosticsContainerRequest1UUID + ".csv")
169 c.Assert(err, check.IsNil)
170 uuid2Report, err := ioutil.ReadFile(resultsDir + "/" + arvadostest.CompletedDiagnosticsContainerRequest2UUID + ".csv")
171 c.Assert(err, check.IsNil)
173 c.Check(string(uuidReport), check.Matches, "(?ms).*TOTAL,,,,,,763.467,,,,0.01")
174 c.Check(string(uuid2Report), check.Matches, "(?ms).*TOTAL,,,,,,488.775,,,,0.01")
175 re := regexp.MustCompile(`(?ms).*supplied uuids in (.*?)\n`)
176 matches := re.FindStringSubmatch(stderr.String()) // matches[1] contains a string like 'results/2020-11-02-18-57-45-aggregate-costaccounting.csv'
178 aggregateCostReport, err := ioutil.ReadFile(matches[1])
179 c.Assert(err, check.IsNil)
181 c.Check(string(aggregateCostReport), check.Matches, "(?ms).*TOTAL,1245.564,0.01")
184 func (*Suite) TestContainerRequestUUID(c *check.C) {
185 var stdout, stderr bytes.Buffer
186 resultsDir := c.MkDir()
187 // Run costanalyzer with 1 container request uuid
188 exitcode := Command.RunCommand("costanalyzer.test", []string{"-output", resultsDir, arvadostest.CompletedContainerRequestUUID}, &bytes.Buffer{}, &stdout, &stderr)
189 c.Check(exitcode, check.Equals, 0)
190 c.Assert(stderr.String(), check.Matches, "(?ms).*supplied uuids in .*")
192 uuidReport, err := ioutil.ReadFile(resultsDir + "/" + arvadostest.CompletedContainerRequestUUID + ".csv")
193 c.Assert(err, check.IsNil)
194 // Make sure the 'preemptible' flag was picked up
195 c.Check(string(uuidReport), check.Matches, "(?ms).*,Standard_E4s_v3,true,.*")
196 c.Check(string(uuidReport), check.Matches, "(?ms).*TOTAL,,,,,,86462.000,,,,7.01")
197 re := regexp.MustCompile(`(?ms).*supplied uuids in (.*?)\n`)
198 matches := re.FindStringSubmatch(stderr.String()) // matches[1] contains a string like 'results/2020-11-02-18-57-45-aggregate-costaccounting.csv'
200 aggregateCostReport, err := ioutil.ReadFile(matches[1])
201 c.Assert(err, check.IsNil)
203 c.Check(string(aggregateCostReport), check.Matches, "(?ms).*TOTAL,86462.000,7.01")
206 func (*Suite) TestCollectionUUID(c *check.C) {
207 var stdout, stderr bytes.Buffer
209 resultsDir := c.MkDir()
210 // Run costanalyzer with 1 collection uuid, without 'container_request' property
211 exitcode := Command.RunCommand("costanalyzer.test", []string{"-output", resultsDir, arvadostest.FooCollection}, &bytes.Buffer{}, &stdout, &stderr)
212 c.Check(exitcode, check.Equals, 2)
213 c.Assert(stderr.String(), check.Matches, "(?ms).*does not have a 'container_request' property.*")
215 // Update the collection, attach a 'container_request' property
216 ac := arvados.NewClientFromEnv()
217 var coll arvados.Collection
219 // Update collection record
220 err := ac.RequestAndDecode(&coll, "PUT", "arvados/v1/collections/"+arvadostest.FooCollection, nil, map[string]interface{}{
221 "collection": map[string]interface{}{
222 "properties": map[string]interface{}{
223 "container_request": arvadostest.CompletedContainerRequestUUID,
227 c.Assert(err, check.IsNil)
232 // Run costanalyzer with 1 collection uuid
233 resultsDir = c.MkDir()
234 exitcode = Command.RunCommand("costanalyzer.test", []string{"-output", resultsDir, arvadostest.FooCollection}, &bytes.Buffer{}, &stdout, &stderr)
235 c.Check(exitcode, check.Equals, 0)
236 c.Assert(stderr.String(), check.Matches, "(?ms).*supplied uuids in .*")
238 uuidReport, err := ioutil.ReadFile(resultsDir + "/" + arvadostest.CompletedContainerRequestUUID + ".csv")
239 c.Assert(err, check.IsNil)
240 c.Check(string(uuidReport), check.Matches, "(?ms).*TOTAL,,,,,,86462.000,,,,7.01")
241 re := regexp.MustCompile(`(?ms).*supplied uuids in (.*?)\n`)
242 matches := re.FindStringSubmatch(stderr.String()) // matches[1] contains a string like 'results/2020-11-02-18-57-45-aggregate-costaccounting.csv'
244 aggregateCostReport, err := ioutil.ReadFile(matches[1])
245 c.Assert(err, check.IsNil)
247 c.Check(string(aggregateCostReport), check.Matches, "(?ms).*TOTAL,86462.000,7.01")
250 func (*Suite) TestDoubleContainerRequestUUID(c *check.C) {
251 var stdout, stderr bytes.Buffer
252 resultsDir := c.MkDir()
253 // Run costanalyzer with 2 container request uuids
254 exitcode := Command.RunCommand("costanalyzer.test", []string{"-output", resultsDir, arvadostest.CompletedContainerRequestUUID, arvadostest.CompletedContainerRequestUUID2}, &bytes.Buffer{}, &stdout, &stderr)
255 c.Check(exitcode, check.Equals, 0)
256 c.Assert(stderr.String(), check.Matches, "(?ms).*supplied uuids in .*")
258 uuidReport, err := ioutil.ReadFile(resultsDir + "/" + arvadostest.CompletedContainerRequestUUID + ".csv")
259 c.Assert(err, check.IsNil)
260 c.Check(string(uuidReport), check.Matches, "(?ms).*TOTAL,,,,,,86462.000,,,,7.01")
262 uuidReport2, err := ioutil.ReadFile(resultsDir + "/" + arvadostest.CompletedContainerRequestUUID2 + ".csv")
263 c.Assert(err, check.IsNil)
264 c.Check(string(uuidReport2), check.Matches, "(?ms).*TOTAL,,,,,,86462.000,,,,42.27")
266 re := regexp.MustCompile(`(?ms).*supplied uuids in (.*?)\n`)
267 matches := re.FindStringSubmatch(stderr.String()) // matches[1] contains a string like 'results/2020-11-02-18-57-45-aggregate-costaccounting.csv'
269 aggregateCostReport, err := ioutil.ReadFile(matches[1])
270 c.Assert(err, check.IsNil)
272 c.Check(string(aggregateCostReport), check.Matches, "(?ms).*TOTAL,172924.000,49.28")
276 // Now move both container requests into an existing project, and then re-run
277 // the analysis with the project uuid. The results should be identical.
278 ac := arvados.NewClientFromEnv()
279 var cr arvados.ContainerRequest
280 err = ac.RequestAndDecode(&cr, "PUT", "arvados/v1/container_requests/"+arvadostest.CompletedContainerRequestUUID, nil, map[string]interface{}{
281 "container_request": map[string]interface{}{
282 "owner_uuid": arvadostest.AProjectUUID,
285 c.Assert(err, check.IsNil)
286 err = ac.RequestAndDecode(&cr, "PUT", "arvados/v1/container_requests/"+arvadostest.CompletedContainerRequestUUID2, nil, map[string]interface{}{
287 "container_request": map[string]interface{}{
288 "owner_uuid": arvadostest.AProjectUUID,
291 c.Assert(err, check.IsNil)
293 // Run costanalyzer with the project uuid
294 resultsDir = c.MkDir()
295 exitcode = Command.RunCommand("costanalyzer.test", []string{"-cache=false", "-log-level", "debug", "-output", resultsDir, arvadostest.AProjectUUID}, &bytes.Buffer{}, &stdout, &stderr)
296 c.Check(exitcode, check.Equals, 0)
297 c.Assert(stderr.String(), check.Matches, "(?ms).*supplied uuids in .*")
299 uuidReport, err = ioutil.ReadFile(resultsDir + "/" + arvadostest.CompletedContainerRequestUUID + ".csv")
300 c.Assert(err, check.IsNil)
301 c.Check(string(uuidReport), check.Matches, "(?ms).*TOTAL,,,,,,86462.000,,,,7.01")
303 uuidReport2, err = ioutil.ReadFile(resultsDir + "/" + arvadostest.CompletedContainerRequestUUID2 + ".csv")
304 c.Assert(err, check.IsNil)
305 c.Check(string(uuidReport2), check.Matches, "(?ms).*TOTAL,,,,,,86462.000,,,,42.27")
307 re = regexp.MustCompile(`(?ms).*supplied uuids in (.*?)\n`)
308 matches = re.FindStringSubmatch(stderr.String()) // matches[1] contains a string like 'results/2020-11-02-18-57-45-aggregate-costaccounting.csv'
310 aggregateCostReport, err = ioutil.ReadFile(matches[1])
311 c.Assert(err, check.IsNil)
313 c.Check(string(aggregateCostReport), check.Matches, "(?ms).*TOTAL,172924.000,49.28")
316 func (*Suite) TestUncommittedContainerRequest(c *check.C) {
317 var stdout, stderr bytes.Buffer
318 // Run costanalyzer with 2 container request uuids, one of which is in the Uncommitted state, without output directory specified
319 exitcode := Command.RunCommand("costanalyzer.test", []string{arvadostest.UncommittedContainerRequestUUID, arvadostest.CompletedDiagnosticsContainerRequest2UUID}, &bytes.Buffer{}, &stdout, &stderr)
320 c.Check(exitcode, check.Equals, 0)
321 c.Assert(stderr.String(), check.Not(check.Matches), "(?ms).*supplied uuids in .*")
322 c.Assert(stderr.String(), check.Matches, "(?ms).*No container associated with container request .*")
324 // Check that the total amount was printed to stdout
325 c.Check(stdout.String(), check.Matches, "0.01\n")
328 func (*Suite) TestMultipleContainerRequestUUIDWithReuse(c *check.C) {
329 var stdout, stderr bytes.Buffer
330 // Run costanalyzer with 2 container request uuids, without output directory specified
331 exitcode := Command.RunCommand("costanalyzer.test", []string{arvadostest.CompletedDiagnosticsContainerRequest1UUID, arvadostest.CompletedDiagnosticsContainerRequest2UUID}, &bytes.Buffer{}, &stdout, &stderr)
332 c.Check(exitcode, check.Equals, 0)
333 c.Assert(stderr.String(), check.Not(check.Matches), "(?ms).*supplied uuids in .*")
335 // Check that the total amount was printed to stdout
336 c.Check(stdout.String(), check.Matches, "0.01\n")
341 // Run costanalyzer with 2 container request uuids
342 resultsDir := c.MkDir()
343 exitcode = Command.RunCommand("costanalyzer.test", []string{"-output", resultsDir, arvadostest.CompletedDiagnosticsContainerRequest1UUID, arvadostest.CompletedDiagnosticsContainerRequest2UUID}, &bytes.Buffer{}, &stdout, &stderr)
344 c.Check(exitcode, check.Equals, 0)
345 c.Assert(stderr.String(), check.Matches, "(?ms).*supplied uuids in .*")
347 uuidReport, err := ioutil.ReadFile(resultsDir + "/" + arvadostest.CompletedDiagnosticsContainerRequest1UUID + ".csv")
348 c.Assert(err, check.IsNil)
349 c.Check(string(uuidReport), check.Matches, "(?ms).*TOTAL,,,,,,763.467,,,,0.01")
351 uuidReport2, err := ioutil.ReadFile(resultsDir + "/" + arvadostest.CompletedDiagnosticsContainerRequest2UUID + ".csv")
352 c.Assert(err, check.IsNil)
353 c.Check(string(uuidReport2), check.Matches, "(?ms).*TOTAL,,,,,,488.775,,,,0.01")
355 re := regexp.MustCompile(`(?ms).*supplied uuids in (.*?)\n`)
356 matches := re.FindStringSubmatch(stderr.String()) // matches[1] contains a string like 'results/2020-11-02-18-57-45-aggregate-costaccounting.csv'
358 aggregateCostReport, err := ioutil.ReadFile(matches[1])
359 c.Assert(err, check.IsNil)
361 c.Check(string(aggregateCostReport), check.Matches, "(?ms).*TOTAL,1245.564,0.01")