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 (s *Suite) SetUpTest(c *check.C) {
121 func createNodeJSON(c *check.C, arv *arvadosclient.ArvadosClient, ac *arvados.Client, kc *keepclient.KeepClient, crUUID string, logUUID string, nodeJSON string) {
123 var cr arvados.ContainerRequest
124 err := ac.RequestAndDecode(&cr, "GET", "arvados/v1/container_requests/"+crUUID, nil, nil)
125 c.Assert(err, check.Equals, nil)
126 c.Assert(cr.LogUUID, check.Equals, logUUID)
128 // Get the log collection
129 var coll arvados.Collection
130 err = ac.RequestAndDecode(&coll, "GET", "arvados/v1/collections/"+cr.LogUUID, nil, nil)
131 c.Assert(err, check.IsNil)
133 // Create a node.json file -- the fixture doesn't actually contain the contents of the collection.
134 fs, err := coll.FileSystem(ac, kc)
135 c.Assert(err, check.IsNil)
136 f, err := fs.OpenFile("node.json", os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0777)
137 c.Assert(err, check.IsNil)
138 _, err = io.WriteString(f, nodeJSON)
139 c.Assert(err, check.IsNil)
141 c.Assert(err, check.IsNil)
143 // Flush the data to Keep
144 mtxt, err := fs.MarshalManifest(".")
145 c.Assert(err, check.IsNil)
146 c.Assert(mtxt, check.NotNil)
148 // Update collection record
149 err = ac.RequestAndDecode(&coll, "PUT", "arvados/v1/collections/"+cr.LogUUID, nil, map[string]interface{}{
150 "collection": map[string]interface{}{
151 "manifest_text": mtxt,
154 c.Assert(err, check.IsNil)
157 func (*Suite) TestUsage(c *check.C) {
158 var stdout, stderr bytes.Buffer
159 exitcode := Command.RunCommand("costanalyzer.test", []string{"-help", "-log-level=debug"}, &bytes.Buffer{}, &stdout, &stderr)
160 c.Check(exitcode, check.Equals, 1)
161 c.Check(stdout.String(), check.Equals, "")
162 c.Check(stderr.String(), check.Matches, `(?ms).*Usage:.*`)
165 func (*Suite) TestTimestampRange(c *check.C) {
166 var stdout, stderr bytes.Buffer
167 resultsDir := c.MkDir()
168 // Run costanalyzer with a timestamp range. This should pick up two container requests in "Final" state.
169 exitcode := Command.RunCommand("costanalyzer.test", []string{"-output", resultsDir, "-begin", "2020-11-02T00:00:00", "-end", "2020-11-03T23:59:00"}, &bytes.Buffer{}, &stdout, &stderr)
170 c.Check(exitcode, check.Equals, 0)
171 c.Assert(stderr.String(), check.Matches, "(?ms).*supplied uuids in .*")
173 uuidReport, err := ioutil.ReadFile(resultsDir + "/" + arvadostest.CompletedDiagnosticsContainerRequest1UUID + ".csv")
174 c.Assert(err, check.IsNil)
175 uuid2Report, err := ioutil.ReadFile(resultsDir + "/" + arvadostest.CompletedDiagnosticsContainerRequest2UUID + ".csv")
176 c.Assert(err, check.IsNil)
178 c.Check(string(uuidReport), check.Matches, "(?ms).*TOTAL,,,,,,,,,0.00916192")
179 c.Check(string(uuid2Report), check.Matches, "(?ms).*TOTAL,,,,,,,,,0.00588088")
180 re := regexp.MustCompile(`(?ms).*supplied uuids in (.*?)\n`)
181 matches := re.FindStringSubmatch(stderr.String()) // matches[1] contains a string like 'results/2020-11-02-18-57-45-aggregate-costaccounting.csv'
183 aggregateCostReport, err := ioutil.ReadFile(matches[1])
184 c.Assert(err, check.IsNil)
186 c.Check(string(aggregateCostReport), check.Matches, "(?ms).*TOTAL,0.01492030")
189 func (*Suite) TestContainerRequestUUID(c *check.C) {
190 var stdout, stderr bytes.Buffer
191 resultsDir := c.MkDir()
192 // Run costanalyzer with 1 container request uuid
193 exitcode := Command.RunCommand("costanalyzer.test", []string{"-output", resultsDir, arvadostest.CompletedContainerRequestUUID}, &bytes.Buffer{}, &stdout, &stderr)
194 c.Check(exitcode, check.Equals, 0)
195 c.Assert(stderr.String(), check.Matches, "(?ms).*supplied uuids in .*")
197 uuidReport, err := ioutil.ReadFile(resultsDir + "/" + arvadostest.CompletedContainerRequestUUID + ".csv")
198 c.Assert(err, check.IsNil)
199 // Make sure the 'preemptible' flag was picked up
200 c.Check(string(uuidReport), check.Matches, "(?ms).*,Standard_E4s_v3,true,.*")
201 c.Check(string(uuidReport), check.Matches, "(?ms).*TOTAL,,,,,,,,,7.01302889")
202 re := regexp.MustCompile(`(?ms).*supplied uuids in (.*?)\n`)
203 matches := re.FindStringSubmatch(stderr.String()) // matches[1] contains a string like 'results/2020-11-02-18-57-45-aggregate-costaccounting.csv'
205 aggregateCostReport, err := ioutil.ReadFile(matches[1])
206 c.Assert(err, check.IsNil)
208 c.Check(string(aggregateCostReport), check.Matches, "(?ms).*TOTAL,7.01302889")
211 func (*Suite) TestCollectionUUID(c *check.C) {
212 var stdout, stderr bytes.Buffer
214 resultsDir := c.MkDir()
215 // Run costanalyzer with 1 collection uuid, without 'container_request' property
216 exitcode := Command.RunCommand("costanalyzer.test", []string{"-output", resultsDir, arvadostest.FooCollection}, &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.*")
220 // Update the collection, attach a 'container_request' property
221 ac := arvados.NewClientFromEnv()
222 var coll arvados.Collection
224 // Update collection record
225 err := ac.RequestAndDecode(&coll, "PUT", "arvados/v1/collections/"+arvadostest.FooCollection, nil, map[string]interface{}{
226 "collection": map[string]interface{}{
227 "properties": map[string]interface{}{
228 "container_request": arvadostest.CompletedContainerRequestUUID,
232 c.Assert(err, check.IsNil)
237 // Run costanalyzer with 1 collection uuid
238 resultsDir = c.MkDir()
239 exitcode = Command.RunCommand("costanalyzer.test", []string{"-output", resultsDir, arvadostest.FooCollection}, &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,,,,,,,,,7.01302889")
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,7.01302889")
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,,,,,,,,,7.01302889")
267 uuidReport2, err := ioutil.ReadFile(resultsDir + "/" + arvadostest.CompletedContainerRequestUUID2 + ".csv")
268 c.Assert(err, check.IsNil)
269 c.Check(string(uuidReport2), check.Matches, "(?ms).*TOTAL,,,,,,,,,42.27031111")
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,49.28334000")
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,,,,,,,,,7.01302889")
308 uuidReport2, err = ioutil.ReadFile(resultsDir + "/" + arvadostest.CompletedContainerRequestUUID2 + ".csv")
309 c.Assert(err, check.IsNil)
310 c.Check(string(uuidReport2), check.Matches, "(?ms).*TOTAL,,,,,,,,,42.27031111")
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,49.28334000")
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.00588088\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.01492030\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,,,,,,,,,0.00916192")
356 uuidReport2, err := ioutil.ReadFile(resultsDir + "/" + arvadostest.CompletedDiagnosticsContainerRequest2UUID + ".csv")
357 c.Assert(err, check.IsNil)
358 c.Check(string(uuidReport2), check.Matches, "(?ms).*TOTAL,,,,,,,,,0.00588088")
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,0.01492030")