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.StartAPI()
37 arvadostest.StartKeep(2, true)
39 // Get the various arvados, arvadosclient, and keep client objects
40 ac := arvados.NewClientFromEnv()
41 arv, err := arvadosclient.MakeArvadosClient()
42 c.Assert(err, check.Equals, nil)
43 arv.ApiToken = arvadostest.ActiveToken
44 kc, err := keepclient.MakeKeepClient(arv)
45 c.Assert(err, check.Equals, nil)
47 standardE4sV3JSON := `{
48 "Name": "Standard_E4s_v3",
49 "ProviderType": "Standard_E4s_v3",
52 "Scratch": 64000000000,
53 "IncludedScratch": 64000000000,
58 standardD32sV3JSON := `{
59 "Name": "Standard_D32s_v3",
60 "ProviderType": "Standard_D32s_v3",
63 "Scratch": 256000000000,
64 "IncludedScratch": 256000000000,
70 standardA1V2JSON := `{
72 "ProviderType": "Standard_A1_v2",
75 "Scratch": 10000000000,
76 "IncludedScratch": 10000000000,
82 standardA2V2JSON := `{
84 "ProviderType": "Standard_A2_v2",
87 "Scratch": 20000000000,
88 "IncludedScratch": 20000000000,
98 "size": "Standard_D1_v2"
100 "total_cpu_cores": 1,
101 "total_ram_mb": 3418,
102 "total_scratch_mb": 51170
106 // Our fixtures do not actually contain file contents. Populate the log collections we're going to use with the node.json file
107 createNodeJSON(c, arv, ac, kc, arvadostest.CompletedContainerRequestUUID, arvadostest.LogCollectionUUID, standardE4sV3JSON)
108 createNodeJSON(c, arv, ac, kc, arvadostest.CompletedContainerRequestUUID2, arvadostest.LogCollectionUUID2, standardD32sV3JSON)
110 createNodeJSON(c, arv, ac, kc, arvadostest.CompletedDiagnosticsContainerRequest1UUID, arvadostest.DiagnosticsContainerRequest1LogCollectionUUID, standardA1V2JSON)
111 createNodeJSON(c, arv, ac, kc, arvadostest.CompletedDiagnosticsContainerRequest2UUID, arvadostest.DiagnosticsContainerRequest2LogCollectionUUID, standardA1V2JSON)
112 createNodeJSON(c, arv, ac, kc, arvadostest.CompletedDiagnosticsHasher1ContainerRequestUUID, arvadostest.Hasher1LogCollectionUUID, standardA1V2JSON)
113 createNodeJSON(c, arv, ac, kc, arvadostest.CompletedDiagnosticsHasher2ContainerRequestUUID, arvadostest.Hasher2LogCollectionUUID, standardA2V2JSON)
114 createNodeJSON(c, arv, ac, kc, arvadostest.CompletedDiagnosticsHasher3ContainerRequestUUID, arvadostest.Hasher3LogCollectionUUID, legacyD1V2JSON)
117 func createNodeJSON(c *check.C, arv *arvadosclient.ArvadosClient, ac *arvados.Client, kc *keepclient.KeepClient, crUUID string, logUUID string, nodeJSON string) {
119 var cr arvados.ContainerRequest
120 err := ac.RequestAndDecode(&cr, "GET", "arvados/v1/container_requests/"+crUUID, nil, nil)
121 c.Assert(err, check.Equals, nil)
122 c.Assert(cr.LogUUID, check.Equals, logUUID)
124 // Get the log collection
125 var coll arvados.Collection
126 err = ac.RequestAndDecode(&coll, "GET", "arvados/v1/collections/"+cr.LogUUID, nil, nil)
127 c.Assert(err, check.IsNil)
129 // Create a node.json file -- the fixture doesn't actually contain the contents of the collection.
130 fs, err := coll.FileSystem(ac, kc)
131 c.Assert(err, check.IsNil)
132 f, err := fs.OpenFile("node.json", os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0777)
133 c.Assert(err, check.IsNil)
134 _, err = io.WriteString(f, nodeJSON)
135 c.Assert(err, check.IsNil)
137 c.Assert(err, check.IsNil)
139 // Flush the data to Keep
140 mtxt, err := fs.MarshalManifest(".")
141 c.Assert(err, check.IsNil)
142 c.Assert(mtxt, check.NotNil)
144 // Update collection record
145 err = ac.RequestAndDecode(&coll, "PUT", "arvados/v1/collections/"+cr.LogUUID, nil, map[string]interface{}{
146 "collection": map[string]interface{}{
147 "manifest_text": mtxt,
150 c.Assert(err, check.IsNil)
153 func (*Suite) TestUsage(c *check.C) {
154 var stdout, stderr bytes.Buffer
155 exitcode := Command.RunCommand("costanalyzer.test", []string{"-help", "-log-level=debug"}, &bytes.Buffer{}, &stdout, &stderr)
156 c.Check(exitcode, check.Equals, 1)
157 c.Check(stdout.String(), check.Equals, "")
158 c.Check(stderr.String(), check.Matches, `(?ms).*Usage:.*`)
161 func (*Suite) TestTimestampRange(c *check.C) {
162 var stdout, stderr bytes.Buffer
163 resultsDir := c.MkDir()
164 // Run costanalyzer with a timestamp range. This should pick up two container requests in "Final" state.
165 exitcode := Command.RunCommand("costanalyzer.test", []string{"-output", resultsDir, "-begin", "2020-11-02T00:00:00", "-end", "2020-11-03T23:59:00"}, &bytes.Buffer{}, &stdout, &stderr)
166 c.Check(exitcode, check.Equals, 0)
167 c.Assert(stderr.String(), check.Matches, "(?ms).*supplied uuids in .*")
169 uuidReport, err := ioutil.ReadFile(resultsDir + "/" + arvadostest.CompletedDiagnosticsContainerRequest1UUID + ".csv")
170 c.Assert(err, check.IsNil)
171 uuid2Report, err := ioutil.ReadFile(resultsDir + "/" + arvadostest.CompletedDiagnosticsContainerRequest2UUID + ".csv")
172 c.Assert(err, check.IsNil)
174 c.Check(string(uuidReport), check.Matches, "(?ms).*TOTAL,,,,,,763.467,,,,0.01")
175 c.Check(string(uuid2Report), check.Matches, "(?ms).*TOTAL,,,,,,488.775,,,,0.01")
176 re := regexp.MustCompile(`(?ms).*supplied uuids in (.*?)\n`)
177 matches := re.FindStringSubmatch(stderr.String()) // matches[1] contains a string like 'results/2020-11-02-18-57-45-aggregate-costaccounting.csv'
179 aggregateCostReport, err := ioutil.ReadFile(matches[1])
180 c.Assert(err, check.IsNil)
182 c.Check(string(aggregateCostReport), check.Matches, "(?ms).*TOTAL,1245.564,0.01")
185 func (*Suite) TestContainerRequestUUID(c *check.C) {
186 var stdout, stderr bytes.Buffer
187 resultsDir := c.MkDir()
188 // Run costanalyzer with 1 container request uuid
189 exitcode := Command.RunCommand("costanalyzer.test", []string{"-output", resultsDir, arvadostest.CompletedContainerRequestUUID}, &bytes.Buffer{}, &stdout, &stderr)
190 c.Check(exitcode, check.Equals, 0)
191 c.Assert(stderr.String(), check.Matches, "(?ms).*supplied uuids in .*")
193 uuidReport, err := ioutil.ReadFile(resultsDir + "/" + arvadostest.CompletedContainerRequestUUID + ".csv")
194 c.Assert(err, check.IsNil)
195 // Make sure the 'preemptible' flag was picked up
196 c.Check(string(uuidReport), check.Matches, "(?ms).*,Standard_E4s_v3,true,.*")
197 c.Check(string(uuidReport), check.Matches, "(?ms).*TOTAL,,,,,,86462.000,,,,7.01")
198 re := regexp.MustCompile(`(?ms).*supplied uuids in (.*?)\n`)
199 matches := re.FindStringSubmatch(stderr.String()) // matches[1] contains a string like 'results/2020-11-02-18-57-45-aggregate-costaccounting.csv'
201 aggregateCostReport, err := ioutil.ReadFile(matches[1])
202 c.Assert(err, check.IsNil)
204 c.Check(string(aggregateCostReport), check.Matches, "(?ms).*TOTAL,86462.000,7.01")
207 func (*Suite) TestCollectionUUID(c *check.C) {
208 var stdout, stderr bytes.Buffer
210 resultsDir := c.MkDir()
211 // Run costanalyzer with 1 collection uuid, without 'container_request' property
212 exitcode := Command.RunCommand("costanalyzer.test", []string{"-output", resultsDir, arvadostest.FooCollection}, &bytes.Buffer{}, &stdout, &stderr)
213 c.Check(exitcode, check.Equals, 2)
214 c.Assert(stderr.String(), check.Matches, "(?ms).*does not have a 'container_request' property.*")
216 // Update the collection, attach a 'container_request' property
217 ac := arvados.NewClientFromEnv()
218 var coll arvados.Collection
220 // Update collection record
221 err := ac.RequestAndDecode(&coll, "PUT", "arvados/v1/collections/"+arvadostest.FooCollection, nil, map[string]interface{}{
222 "collection": map[string]interface{}{
223 "properties": map[string]interface{}{
224 "container_request": arvadostest.CompletedContainerRequestUUID,
228 c.Assert(err, check.IsNil)
233 // Run costanalyzer with 1 collection uuid
234 resultsDir = c.MkDir()
235 exitcode = Command.RunCommand("costanalyzer.test", []string{"-output", resultsDir, arvadostest.FooCollection}, &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")