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 // Use a small page size to exercise paging without adding
42 // Get the various arvados, arvadosclient, and keep client objects
43 ac := arvados.NewClientFromEnv()
44 arv, err := arvadosclient.MakeArvadosClient()
45 c.Assert(err, check.Equals, nil)
46 arv.ApiToken = arvadostest.ActiveToken
47 kc, err := keepclient.MakeKeepClient(arv)
48 c.Assert(err, check.Equals, nil)
50 standardE4sV3JSON := `{
51 "Name": "Standard_E4s_v3",
52 "ProviderType": "Standard_E4s_v3",
55 "Scratch": 64000000000,
56 "IncludedScratch": 64000000000,
61 standardD32sV3JSON := `{
62 "Name": "Standard_D32s_v3",
63 "ProviderType": "Standard_D32s_v3",
66 "Scratch": 256000000000,
67 "IncludedScratch": 256000000000,
73 standardA1V2JSON := `{
75 "ProviderType": "Standard_A1_v2",
78 "Scratch": 10000000000,
79 "IncludedScratch": 10000000000,
85 standardA2V2JSON := `{
87 "ProviderType": "Standard_A2_v2",
90 "Scratch": 20000000000,
91 "IncludedScratch": 20000000000,
101 "size": "Standard_D1_v2"
103 "total_cpu_cores": 1,
104 "total_ram_mb": 3418,
105 "total_scratch_mb": 51170
109 // Our fixtures do not actually contain file contents. Populate the log collections we're going to use with the node.json file
110 createNodeJSON(c, arv, ac, kc, arvadostest.CompletedContainerRequestUUID, arvadostest.LogCollectionUUID, standardE4sV3JSON)
111 createNodeJSON(c, arv, ac, kc, arvadostest.CompletedContainerRequestUUID2, arvadostest.LogCollectionUUID2, standardD32sV3JSON)
113 createNodeJSON(c, arv, ac, kc, arvadostest.CompletedDiagnosticsContainerRequest1UUID, arvadostest.DiagnosticsContainerRequest1LogCollectionUUID, standardA1V2JSON)
114 createNodeJSON(c, arv, ac, kc, arvadostest.CompletedDiagnosticsContainerRequest2UUID, arvadostest.DiagnosticsContainerRequest2LogCollectionUUID, standardA1V2JSON)
115 createNodeJSON(c, arv, ac, kc, arvadostest.CompletedDiagnosticsHasher1ContainerRequestUUID, arvadostest.Hasher1LogCollectionUUID, standardA1V2JSON)
116 createNodeJSON(c, arv, ac, kc, arvadostest.CompletedDiagnosticsHasher2ContainerRequestUUID, arvadostest.Hasher2LogCollectionUUID, standardA2V2JSON)
117 createNodeJSON(c, arv, ac, kc, arvadostest.CompletedDiagnosticsHasher3ContainerRequestUUID, arvadostest.Hasher3LogCollectionUUID, legacyD1V2JSON)
120 func createNodeJSON(c *check.C, arv *arvadosclient.ArvadosClient, ac *arvados.Client, kc *keepclient.KeepClient, crUUID string, logUUID string, nodeJSON string) {
122 var cr arvados.ContainerRequest
123 err := ac.RequestAndDecode(&cr, "GET", "arvados/v1/container_requests/"+crUUID, nil, nil)
124 c.Assert(err, check.Equals, nil)
125 c.Assert(cr.LogUUID, check.Equals, logUUID)
127 // Get the log collection
128 var coll arvados.Collection
129 err = ac.RequestAndDecode(&coll, "GET", "arvados/v1/collections/"+cr.LogUUID, nil, nil)
130 c.Assert(err, check.IsNil)
132 // Create a node.json file -- the fixture doesn't actually contain the contents of the collection.
133 fs, err := coll.FileSystem(ac, kc)
134 c.Assert(err, check.IsNil)
135 f, err := fs.OpenFile("node.json", os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0777)
136 c.Assert(err, check.IsNil)
137 _, err = io.WriteString(f, nodeJSON)
138 c.Assert(err, check.IsNil)
140 c.Assert(err, check.IsNil)
142 // Flush the data to Keep
143 mtxt, err := fs.MarshalManifest(".")
144 c.Assert(err, check.IsNil)
145 c.Assert(mtxt, check.NotNil)
147 // Update collection record
148 err = ac.RequestAndDecode(&coll, "PUT", "arvados/v1/collections/"+cr.LogUUID, nil, map[string]interface{}{
149 "collection": map[string]interface{}{
150 "manifest_text": mtxt,
153 c.Assert(err, check.IsNil)
156 func (*Suite) TestUsage(c *check.C) {
157 var stdout, stderr bytes.Buffer
158 exitcode := Command.RunCommand("costanalyzer.test", []string{"-help", "-log-level=debug"}, &bytes.Buffer{}, &stdout, &stderr)
159 c.Check(exitcode, check.Equals, 0)
160 c.Check(stdout.String(), check.Equals, "")
161 c.Check(stderr.String(), check.Matches, `(?ms).*Usage:.*`)
164 func (*Suite) TestTimestampRange(c *check.C) {
165 var stdout, stderr bytes.Buffer
166 resultsDir := c.MkDir()
167 // Run costanalyzer with a timestamp range. This should pick up two container requests in "Final" state.
168 exitcode := Command.RunCommand("costanalyzer.test", []string{"-output", resultsDir, "-begin", "2020-11-02T00:00:00", "-end", "2020-11-03T23:59:00"}, &bytes.Buffer{}, &stdout, &stderr)
169 c.Check(exitcode, check.Equals, 0)
170 c.Assert(stderr.String(), check.Matches, "(?ms).*supplied uuids in .*")
172 uuidReport, err := ioutil.ReadFile(resultsDir + "/" + arvadostest.CompletedDiagnosticsContainerRequest1UUID + ".csv")
173 c.Assert(err, check.IsNil)
174 uuid2Report, err := ioutil.ReadFile(resultsDir + "/" + arvadostest.CompletedDiagnosticsContainerRequest2UUID + ".csv")
175 c.Assert(err, check.IsNil)
177 c.Check(string(uuidReport), check.Matches, "(?ms).*TOTAL,,,,,,763.467,,,,0.01")
178 c.Check(string(uuid2Report), check.Matches, "(?ms).*TOTAL,,,,,,488.775,,,,0.01")
179 re := regexp.MustCompile(`(?ms).*supplied uuids in (.*?)\n`)
180 matches := re.FindStringSubmatch(stderr.String()) // matches[1] contains a string like 'results/2020-11-02-18-57-45-aggregate-costaccounting.csv'
182 aggregateCostReport, err := ioutil.ReadFile(matches[1])
183 c.Assert(err, check.IsNil)
185 c.Check(string(aggregateCostReport), check.Matches, "(?ms).*TOTAL,1245.564,0.01")
188 func (*Suite) TestContainerRequestUUID(c *check.C) {
189 var stdout, stderr bytes.Buffer
190 resultsDir := c.MkDir()
191 // Run costanalyzer with 1 container request uuid
192 exitcode := Command.RunCommand("costanalyzer.test", []string{"-output", resultsDir, arvadostest.CompletedContainerRequestUUID}, &bytes.Buffer{}, &stdout, &stderr)
193 c.Check(exitcode, check.Equals, 0)
194 c.Assert(stderr.String(), check.Matches, "(?ms).*supplied uuids in .*")
196 uuidReport, err := ioutil.ReadFile(resultsDir + "/" + arvadostest.CompletedContainerRequestUUID + ".csv")
197 c.Assert(err, check.IsNil)
198 // Make sure the 'preemptible' flag was picked up
199 c.Check(string(uuidReport), check.Matches, "(?ms).*,Standard_E4s_v3,true,.*")
200 c.Check(string(uuidReport), check.Matches, "(?ms).*TOTAL,,,,,,86462.000,,,,7.01")
201 re := regexp.MustCompile(`(?ms).*supplied uuids in (.*?)\n`)
202 matches := re.FindStringSubmatch(stderr.String()) // matches[1] contains a string like 'results/2020-11-02-18-57-45-aggregate-costaccounting.csv'
204 aggregateCostReport, err := ioutil.ReadFile(matches[1])
205 c.Assert(err, check.IsNil)
207 c.Check(string(aggregateCostReport), check.Matches, "(?ms).*TOTAL,86462.000,7.01")
210 func (*Suite) TestCollectionUUID(c *check.C) {
211 var stdout, stderr bytes.Buffer
212 resultsDir := c.MkDir()
214 // Create a collection with no container_request property
215 ac := arvados.NewClientFromEnv()
216 var coll arvados.Collection
217 err := ac.RequestAndDecode(&coll, "POST", "arvados/v1/collections", nil, nil)
218 c.Assert(err, check.IsNil)
220 exitcode := Command.RunCommand("costanalyzer.test", []string{"-output", resultsDir, coll.UUID}, &bytes.Buffer{}, &stdout, &stderr)
221 c.Check(exitcode, check.Equals, 2)
222 c.Assert(stderr.String(), check.Matches, "(?ms).*does not have a 'container_request' property.*")
227 // Add a container_request property
228 err = ac.RequestAndDecode(&coll, "PATCH", "arvados/v1/collections/"+coll.UUID, nil, map[string]interface{}{
229 "collection": map[string]interface{}{
230 "properties": map[string]interface{}{
231 "container_request": arvadostest.CompletedContainerRequestUUID,
235 c.Assert(err, check.IsNil)
237 // Re-run costanalyzer on the updated collection
238 resultsDir = c.MkDir()
239 exitcode = Command.RunCommand("costanalyzer.test", []string{"-output", resultsDir, coll.UUID}, &bytes.Buffer{}, &stdout, &stderr)
240 c.Check(exitcode, check.Equals, 0)
241 c.Assert(stderr.String(), check.Matches, "(?ms).*supplied uuids in .*")
243 uuidReport, err := ioutil.ReadFile(resultsDir + "/" + arvadostest.CompletedContainerRequestUUID + ".csv")
244 c.Assert(err, check.IsNil)
245 c.Check(string(uuidReport), check.Matches, "(?ms).*TOTAL,,,,,,86462.000,,,,7.01")
246 re := regexp.MustCompile(`(?ms).*supplied uuids in (.*?)\n`)
247 matches := re.FindStringSubmatch(stderr.String()) // matches[1] contains a string like 'results/2020-11-02-18-57-45-aggregate-costaccounting.csv'
249 aggregateCostReport, err := ioutil.ReadFile(matches[1])
250 c.Assert(err, check.IsNil)
252 c.Check(string(aggregateCostReport), check.Matches, "(?ms).*TOTAL,86462.000,7.01")
255 func (*Suite) TestDoubleContainerRequestUUID(c *check.C) {
256 var stdout, stderr bytes.Buffer
257 resultsDir := c.MkDir()
258 // Run costanalyzer with 2 container request uuids
259 exitcode := Command.RunCommand("costanalyzer.test", []string{"-output", resultsDir, arvadostest.CompletedContainerRequestUUID, arvadostest.CompletedContainerRequestUUID2}, &bytes.Buffer{}, &stdout, &stderr)
260 c.Check(exitcode, check.Equals, 0)
261 c.Assert(stderr.String(), check.Matches, "(?ms).*supplied uuids in .*")
263 uuidReport, err := ioutil.ReadFile(resultsDir + "/" + arvadostest.CompletedContainerRequestUUID + ".csv")
264 c.Assert(err, check.IsNil)
265 c.Check(string(uuidReport), check.Matches, "(?ms).*TOTAL,,,,,,86462.000,,,,7.01")
267 uuidReport2, err := ioutil.ReadFile(resultsDir + "/" + arvadostest.CompletedContainerRequestUUID2 + ".csv")
268 c.Assert(err, check.IsNil)
269 c.Check(string(uuidReport2), check.Matches, "(?ms).*TOTAL,,,,,,86462.000,,,,42.27")
271 re := regexp.MustCompile(`(?ms).*supplied uuids in (.*?)\n`)
272 matches := re.FindStringSubmatch(stderr.String()) // matches[1] contains a string like 'results/2020-11-02-18-57-45-aggregate-costaccounting.csv'
274 aggregateCostReport, err := ioutil.ReadFile(matches[1])
275 c.Assert(err, check.IsNil)
277 c.Check(string(aggregateCostReport), check.Matches, "(?ms).*TOTAL,172924.000,49.28")
281 // Now move both container requests into an existing project, and then re-run
282 // the analysis with the project uuid. The results should be identical.
283 ac := arvados.NewClientFromEnv()
284 var cr arvados.ContainerRequest
285 err = ac.RequestAndDecode(&cr, "PUT", "arvados/v1/container_requests/"+arvadostest.CompletedContainerRequestUUID, nil, map[string]interface{}{
286 "container_request": map[string]interface{}{
287 "owner_uuid": arvadostest.AProjectUUID,
290 c.Assert(err, check.IsNil)
291 err = ac.RequestAndDecode(&cr, "PUT", "arvados/v1/container_requests/"+arvadostest.CompletedContainerRequestUUID2, nil, map[string]interface{}{
292 "container_request": map[string]interface{}{
293 "owner_uuid": arvadostest.AProjectUUID,
296 c.Assert(err, check.IsNil)
298 // Run costanalyzer with the project uuid
299 resultsDir = c.MkDir()
300 exitcode = Command.RunCommand("costanalyzer.test", []string{"-cache=false", "-log-level", "debug", "-output", resultsDir, arvadostest.AProjectUUID}, &bytes.Buffer{}, &stdout, &stderr)
301 c.Check(exitcode, check.Equals, 0)
302 c.Assert(stderr.String(), check.Matches, "(?ms).*supplied uuids in .*")
304 uuidReport, err = ioutil.ReadFile(resultsDir + "/" + arvadostest.CompletedContainerRequestUUID + ".csv")
305 c.Assert(err, check.IsNil)
306 c.Check(string(uuidReport), check.Matches, "(?ms).*TOTAL,,,,,,86462.000,,,,7.01")
308 uuidReport2, err = ioutil.ReadFile(resultsDir + "/" + arvadostest.CompletedContainerRequestUUID2 + ".csv")
309 c.Assert(err, check.IsNil)
310 c.Check(string(uuidReport2), check.Matches, "(?ms).*TOTAL,,,,,,86462.000,,,,42.27")
312 re = regexp.MustCompile(`(?ms).*supplied uuids in (.*?)\n`)
313 matches = re.FindStringSubmatch(stderr.String()) // matches[1] contains a string like 'results/2020-11-02-18-57-45-aggregate-costaccounting.csv'
315 aggregateCostReport, err = ioutil.ReadFile(matches[1])
316 c.Assert(err, check.IsNil)
318 c.Check(string(aggregateCostReport), check.Matches, "(?ms).*TOTAL,172924.000,49.28")
321 func (*Suite) TestUncommittedContainerRequest(c *check.C) {
322 var stdout, stderr bytes.Buffer
323 // Run costanalyzer with 2 container request uuids, one of which is in the Uncommitted state, without output directory specified
324 exitcode := Command.RunCommand("costanalyzer.test", []string{arvadostest.UncommittedContainerRequestUUID, arvadostest.CompletedDiagnosticsContainerRequest2UUID}, &bytes.Buffer{}, &stdout, &stderr)
325 c.Check(exitcode, check.Equals, 0)
326 c.Assert(stderr.String(), check.Not(check.Matches), "(?ms).*supplied uuids in .*")
327 c.Assert(stderr.String(), check.Matches, "(?ms).*No container associated with container request .*")
329 // Check that the total amount was printed to stdout
330 c.Check(stdout.String(), check.Matches, "0.01\n")
333 func (*Suite) TestMultipleContainerRequestUUIDWithReuse(c *check.C) {
334 var stdout, stderr bytes.Buffer
335 // Run costanalyzer with 2 container request uuids, without output directory specified
336 exitcode := Command.RunCommand("costanalyzer.test", []string{arvadostest.CompletedDiagnosticsContainerRequest1UUID, arvadostest.CompletedDiagnosticsContainerRequest2UUID}, &bytes.Buffer{}, &stdout, &stderr)
337 c.Check(exitcode, check.Equals, 0)
338 c.Assert(stderr.String(), check.Not(check.Matches), "(?ms).*supplied uuids in .*")
340 // Check that the total amount was printed to stdout
341 c.Check(stdout.String(), check.Matches, "0.01\n")
346 // Run costanalyzer with 2 container request uuids
347 resultsDir := c.MkDir()
348 exitcode = Command.RunCommand("costanalyzer.test", []string{"-output", resultsDir, arvadostest.CompletedDiagnosticsContainerRequest1UUID, arvadostest.CompletedDiagnosticsContainerRequest2UUID}, &bytes.Buffer{}, &stdout, &stderr)
349 c.Check(exitcode, check.Equals, 0)
350 c.Assert(stderr.String(), check.Matches, "(?ms).*supplied uuids in .*")
352 uuidReport, err := ioutil.ReadFile(resultsDir + "/" + arvadostest.CompletedDiagnosticsContainerRequest1UUID + ".csv")
353 c.Assert(err, check.IsNil)
354 c.Check(string(uuidReport), check.Matches, "(?ms).*TOTAL,,,,,,763.467,,,,0.01")
356 uuidReport2, err := ioutil.ReadFile(resultsDir + "/" + arvadostest.CompletedDiagnosticsContainerRequest2UUID + ".csv")
357 c.Assert(err, check.IsNil)
358 c.Check(string(uuidReport2), check.Matches, "(?ms).*TOTAL,,,,,,488.775,,,,0.01")
360 re := regexp.MustCompile(`(?ms).*supplied uuids in (.*?)\n`)
361 matches := re.FindStringSubmatch(stderr.String()) // matches[1] contains a string like 'results/2020-11-02-18-57-45-aggregate-costaccounting.csv'
363 aggregateCostReport, err := ioutil.ReadFile(matches[1])
364 c.Assert(err, check.IsNil)
366 c.Check(string(aggregateCostReport), check.Matches, "(?ms).*TOTAL,1245.564,0.01")