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, 0)
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
208 resultsDir := c.MkDir()
210 // Create a collection with no container_request property
211 ac := arvados.NewClientFromEnv()
212 var coll arvados.Collection
213 err := ac.RequestAndDecode(&coll, "POST", "arvados/v1/collections", nil, nil)
214 c.Assert(err, check.IsNil)
216 exitcode := Command.RunCommand("costanalyzer.test", []string{"-output", resultsDir, coll.UUID}, &bytes.Buffer{}, &stdout, &stderr)
217 c.Check(exitcode, check.Equals, 2)
218 c.Assert(stderr.String(), check.Matches, "(?ms).*does not have a 'container_request' property.*")
223 // Add a container_request property
224 err = ac.RequestAndDecode(&coll, "PATCH", "arvados/v1/collections/"+coll.UUID, nil, map[string]interface{}{
225 "collection": map[string]interface{}{
226 "properties": map[string]interface{}{
227 "container_request": arvadostest.CompletedContainerRequestUUID,
231 c.Assert(err, check.IsNil)
233 // Re-run costanalyzer on the updated collection
234 resultsDir = c.MkDir()
235 exitcode = Command.RunCommand("costanalyzer.test", []string{"-output", resultsDir, coll.UUID}, &bytes.Buffer{}, &stdout, &stderr)
236 c.Check(exitcode, check.Equals, 0)
237 c.Assert(stderr.String(), check.Matches, "(?ms).*supplied uuids in .*")
239 uuidReport, err := ioutil.ReadFile(resultsDir + "/" + arvadostest.CompletedContainerRequestUUID + ".csv")
240 c.Assert(err, check.IsNil)
241 c.Check(string(uuidReport), check.Matches, "(?ms).*TOTAL,,,,,,86462.000,,,,7.01")
242 re := regexp.MustCompile(`(?ms).*supplied uuids in (.*?)\n`)
243 matches := re.FindStringSubmatch(stderr.String()) // matches[1] contains a string like 'results/2020-11-02-18-57-45-aggregate-costaccounting.csv'
245 aggregateCostReport, err := ioutil.ReadFile(matches[1])
246 c.Assert(err, check.IsNil)
248 c.Check(string(aggregateCostReport), check.Matches, "(?ms).*TOTAL,86462.000,7.01")
251 func (*Suite) TestDoubleContainerRequestUUID(c *check.C) {
252 var stdout, stderr bytes.Buffer
253 resultsDir := c.MkDir()
254 // Run costanalyzer with 2 container request uuids
255 exitcode := Command.RunCommand("costanalyzer.test", []string{"-output", resultsDir, arvadostest.CompletedContainerRequestUUID, arvadostest.CompletedContainerRequestUUID2}, &bytes.Buffer{}, &stdout, &stderr)
256 c.Check(exitcode, check.Equals, 0)
257 c.Assert(stderr.String(), check.Matches, "(?ms).*supplied uuids in .*")
259 uuidReport, err := ioutil.ReadFile(resultsDir + "/" + arvadostest.CompletedContainerRequestUUID + ".csv")
260 c.Assert(err, check.IsNil)
261 c.Check(string(uuidReport), check.Matches, "(?ms).*TOTAL,,,,,,86462.000,,,,7.01")
263 uuidReport2, err := ioutil.ReadFile(resultsDir + "/" + arvadostest.CompletedContainerRequestUUID2 + ".csv")
264 c.Assert(err, check.IsNil)
265 c.Check(string(uuidReport2), check.Matches, "(?ms).*TOTAL,,,,,,86462.000,,,,42.27")
267 re := regexp.MustCompile(`(?ms).*supplied uuids in (.*?)\n`)
268 matches := re.FindStringSubmatch(stderr.String()) // matches[1] contains a string like 'results/2020-11-02-18-57-45-aggregate-costaccounting.csv'
270 aggregateCostReport, err := ioutil.ReadFile(matches[1])
271 c.Assert(err, check.IsNil)
273 c.Check(string(aggregateCostReport), check.Matches, "(?ms).*TOTAL,172924.000,49.28")
277 // Now move both container requests into an existing project, and then re-run
278 // the analysis with the project uuid. The results should be identical.
279 ac := arvados.NewClientFromEnv()
280 var cr arvados.ContainerRequest
281 err = ac.RequestAndDecode(&cr, "PUT", "arvados/v1/container_requests/"+arvadostest.CompletedContainerRequestUUID, nil, map[string]interface{}{
282 "container_request": map[string]interface{}{
283 "owner_uuid": arvadostest.AProjectUUID,
286 c.Assert(err, check.IsNil)
287 err = ac.RequestAndDecode(&cr, "PUT", "arvados/v1/container_requests/"+arvadostest.CompletedContainerRequestUUID2, nil, map[string]interface{}{
288 "container_request": map[string]interface{}{
289 "owner_uuid": arvadostest.AProjectUUID,
292 c.Assert(err, check.IsNil)
294 // Run costanalyzer with the project uuid
295 resultsDir = c.MkDir()
296 exitcode = Command.RunCommand("costanalyzer.test", []string{"-cache=false", "-log-level", "debug", "-output", resultsDir, arvadostest.AProjectUUID}, &bytes.Buffer{}, &stdout, &stderr)
297 c.Check(exitcode, check.Equals, 0)
298 c.Assert(stderr.String(), check.Matches, "(?ms).*supplied uuids in .*")
300 uuidReport, err = ioutil.ReadFile(resultsDir + "/" + arvadostest.CompletedContainerRequestUUID + ".csv")
301 c.Assert(err, check.IsNil)
302 c.Check(string(uuidReport), check.Matches, "(?ms).*TOTAL,,,,,,86462.000,,,,7.01")
304 uuidReport2, err = ioutil.ReadFile(resultsDir + "/" + arvadostest.CompletedContainerRequestUUID2 + ".csv")
305 c.Assert(err, check.IsNil)
306 c.Check(string(uuidReport2), check.Matches, "(?ms).*TOTAL,,,,,,86462.000,,,,42.27")
308 re = regexp.MustCompile(`(?ms).*supplied uuids in (.*?)\n`)
309 matches = re.FindStringSubmatch(stderr.String()) // matches[1] contains a string like 'results/2020-11-02-18-57-45-aggregate-costaccounting.csv'
311 aggregateCostReport, err = ioutil.ReadFile(matches[1])
312 c.Assert(err, check.IsNil)
314 c.Check(string(aggregateCostReport), check.Matches, "(?ms).*TOTAL,172924.000,49.28")
317 func (*Suite) TestUncommittedContainerRequest(c *check.C) {
318 var stdout, stderr bytes.Buffer
319 // Run costanalyzer with 2 container request uuids, one of which is in the Uncommitted state, without output directory specified
320 exitcode := Command.RunCommand("costanalyzer.test", []string{arvadostest.UncommittedContainerRequestUUID, arvadostest.CompletedDiagnosticsContainerRequest2UUID}, &bytes.Buffer{}, &stdout, &stderr)
321 c.Check(exitcode, check.Equals, 0)
322 c.Assert(stderr.String(), check.Not(check.Matches), "(?ms).*supplied uuids in .*")
323 c.Assert(stderr.String(), check.Matches, "(?ms).*No container associated with container request .*")
325 // Check that the total amount was printed to stdout
326 c.Check(stdout.String(), check.Matches, "0.01\n")
329 func (*Suite) TestMultipleContainerRequestUUIDWithReuse(c *check.C) {
330 var stdout, stderr bytes.Buffer
331 // Run costanalyzer with 2 container request uuids, without output directory specified
332 exitcode := Command.RunCommand("costanalyzer.test", []string{arvadostest.CompletedDiagnosticsContainerRequest1UUID, arvadostest.CompletedDiagnosticsContainerRequest2UUID}, &bytes.Buffer{}, &stdout, &stderr)
333 c.Check(exitcode, check.Equals, 0)
334 c.Assert(stderr.String(), check.Not(check.Matches), "(?ms).*supplied uuids in .*")
336 // Check that the total amount was printed to stdout
337 c.Check(stdout.String(), check.Matches, "0.01\n")
342 // Run costanalyzer with 2 container request uuids
343 resultsDir := c.MkDir()
344 exitcode = Command.RunCommand("costanalyzer.test", []string{"-output", resultsDir, arvadostest.CompletedDiagnosticsContainerRequest1UUID, arvadostest.CompletedDiagnosticsContainerRequest2UUID}, &bytes.Buffer{}, &stdout, &stderr)
345 c.Check(exitcode, check.Equals, 0)
346 c.Assert(stderr.String(), check.Matches, "(?ms).*supplied uuids in .*")
348 uuidReport, err := ioutil.ReadFile(resultsDir + "/" + arvadostest.CompletedDiagnosticsContainerRequest1UUID + ".csv")
349 c.Assert(err, check.IsNil)
350 c.Check(string(uuidReport), check.Matches, "(?ms).*TOTAL,,,,,,763.467,,,,0.01")
352 uuidReport2, err := ioutil.ReadFile(resultsDir + "/" + arvadostest.CompletedDiagnosticsContainerRequest2UUID + ".csv")
353 c.Assert(err, check.IsNil)
354 c.Check(string(uuidReport2), check.Matches, "(?ms).*TOTAL,,,,,,488.775,,,,0.01")
356 re := regexp.MustCompile(`(?ms).*supplied uuids in (.*?)\n`)
357 matches := re.FindStringSubmatch(stderr.String()) // matches[1] contains a string like 'results/2020-11-02-18-57-45-aggregate-costaccounting.csv'
359 aggregateCostReport, err := ioutil.ReadFile(matches[1])
360 c.Assert(err, check.IsNil)
362 c.Check(string(aggregateCostReport), check.Matches, "(?ms).*TOTAL,1245.564,0.01")